Если посмотреть на существующие современные фреймворки для реализации пользовательских интерфейсов (WPF, GTK, Qt, wxWidgets, да даже модные веб-решения), то легко заметить, что они похожи друг на друга как близнецы. Любой фреймворк содержит кнопки, поля, чекбоксы, переключатели, характеризуемые идентичной с точки зрения пользователя логикой. Отличия заключаются только в низкоуровневой реализации.
Когда где-то в мире программирования появляется что-то слишком похожее, то это стремятся обернуть в слой абстракции. И вот я случайно наткнулся на подобное решение, созданное парнями из Xamarin. Теми самыми, которые продают C# фреймворк для iOS и Android. Решение это назвали Xwt — судя по всему, это расшифровывается как Xamarin Window Toolkit.
Абстрактно
Идея проста. Контролы вместе с базовыми свойствами оборачиваются в интерфейс, который реализуется средствами существующих фреймворков. При этом реализации располагаются в отдельных сборках и на каждую платформу достаточно разместить любую одну сборку.
Сначала про интерфейс: поскольку задачей Xwt было портирование MonoDevelop с Gtk на Cocoa, интерфейс Xwt крайне близок к интерфейсу Gtk#. Контролы размещаются в автоматически масштабируемых строчных, столбцовых и табличных layout'ах в соответствии с унаследованными от GTK правилами fill и expand. Также от Gtk сохранилось устройство TreeView/TreeStore, хоть и несколько приблизилось к стандартам .NET.
В существующей версии Xwt есть реализации для WPF, Gtk и Cocoa. При этом никто не запрещает использовать Gtk на Windows или Mac OS X.
Забрать фреймворк можно в исходниках отсюда. Интерфейс отдельно собирается в Xwt.dll, а сборки — в Xwt.Gtk.dll, Xwt.Mac.dll, Xwt.WPF.dll. При этом варианты реализации и ссылки на них жестко прописаны в головном файле, так что добавить свои реализации можно только форкнув весь проект.
Попробуем сделать маленькое приложение на Xwt:
class Program
{
[STAThread]
static void Main(string[] args)
{
Xwt.Application.Initialize(Xwt.ToolkitType.Wpf);
Xwt.Window MainWindow = new Xwt.Window()
{
Title = "Xwt Test"
};
MainWindow.CloseRequested += (o, e) =>
{
Xwt.Application.Exit();
};
Xwt.Menu MainMenu = new Xwt.Menu();
Xwt.RichTextView TextView = new Xwt.RichTextView();
Xwt.MenuItem FileOpenMenuItem = new Xwt.MenuItem("Открыть");
Xwt.Menu FileMenu = new Xwt.Menu();
FileOpenMenuItem.Clicked += (o,e) => {
Xwt.OpenFileDialog Dialog = new Xwt.OpenFileDialog("Открыть файл");
if (Dialog.Run(MainWindow)) {
TextView.LoadFile(Dialog.FileName, Xwt.Formats.TextFormat.Markdown);
}
};
Xwt.MenuItem FileMenuItem = new Xwt.MenuItem("Файл") { SubMenu = FileMenu };
FileMenu.Items.Add(FileOpenMenuItem);
MainMenu.Items.Add(FileMenuItem);
MainWindow.MainMenu = MainMenu;
MainWindow.Content = TextView;
MainWindow.Show();
Xwt.Application.Run();
}
}
Обратите внимание, что соответствующие интерфейсы сделаны не только для контролов, но и для стандартных диалоговых окон. В качестве бонуса в контрол RichTextView встроен парсер Markdown'а :)
Вот что мы увидим, выполнив это приложение:
Заменяем одну строку на Xwt.Application.Initialize(Xwt.ToolkitType.Gtk);
и получаем другой результат.
Еще больше абстракции
Авторы пошли еще дальше и включили абстрактный интерфейс рисования, близкий по внешнему интерфейсу к Gdk. В рамках этого интерфейса присутствует, например, Xwt.Drawing.Color
, отличный и от System.Drawing.Color
, и от Gdk.Color
.
Вот простой код, рисующий кружок. Он успешно выполняется на всех платформах.
class DrawCircle : Xwt.Canvas
{
protected override void OnDraw(Xwt.Drawing.Context ctx, Xwt.Rectangle dirtyRect)
{
ctx.SetColor(Xwt.Drawing.Colors.Black);
ctx.SetLineWidth(1);
ctx.Arc(50, 50, 30, 0, 350);
ctx.Stroke();
}
}
class Program
{
[STAThread]
static void Main(string[] args)
{
Xwt.Application.Initialize(Xwt.ToolkitType.Wpf);
Xwt.Window MainWindow = new Xwt.Window()
{
Title = "Xwt Test"
};
MainWindow.CloseRequested += (o, e) =>
{
Xwt.Application.Exit();
};
DrawCircle Canvas = new DrawCircle();
MainWindow.Content = Canvas;
MainWindow.Show();
Xwt.Application.Run();
}
}
С одной стороны, кроссплатформенный интерфейс рисования — это вещь, безусловно, необходимая. С другой стороны, существующий код нельзя перенести, ни если он написан для System.Drawing, ни для Cairo.
Большие претензии
Кроме абстрактного движка рисования, авторы фреймворка замахнулись на создание абстракции для анимаций. На текущем этапе он представляет что-то похожее на $.animate() ранних версий: простая обертка над таймером. Мне сложно было представить анимированный Gtk#, поэтому я просто попробовал сделать простой shake, как в модных окнах логина.
class Program
{
[STAThread]
static void Main(string[] args)
{
Xwt.Application.Initialize(Xwt.ToolkitType.Gtk);
Xwt.Window W = new Xwt.Window() { };
Xwt.Button B = new Xwt.Button("Animate");
W.Content = B;
W.Show();
B.Clicked += (o, e) =>
{
#region Костыль
//Авторы чего-то напутали в системах координат, в результате чего безобидный код W.X = W.X сдвигает окно на какое-то расстояние, разное для каждого окна. Маленький костыль исправляет ситуацию. Мелькание окна иногда проскакивает, иногда нет.
double CurX = W.X, CurY = W.Y;
W.X = CurX;
W.Y = CurY;
double DiffX = W.X - CurX, DiffY = W.Y - CurY;
W.X = CurX - DiffX;
W.Y = CurY - DiffY;
#endregion
W.Animate("", (X) =>
{
W.Location = new Point((CurX - DiffX) + 8 * Math.Sin(20 * X), (CurY - DiffY));
}, length: 750, repeat: () => false);
};
Xwt.Application.Run();
}
}
Вышеприведенный пример успешно сработал и с ToolkitType.Gtk, и с ToolkitType.Wpf.
Вместо заключения
Сейчас это весьма сырой, open-source проект, распространяемый по лицензии MIT, у него вообще нет документации, никакой, вообще. Тем не менее, на нем написана целая IDE (MonoDevelop 4). В рамках проекта все еще нет графического дизайнера, и пока непонятно, по какому пути пойдут разработчики с декларативным языком — форк Glade или адаптация XAML. Уже сейчас фреймворк можно использовать для academic проектов в связке с Mono, а с появлением хотя бы самого простого декларативного языка это будет еще и удобно. Забавно, на мой взгляд, было бы реализовать бэкенд на jQuery с разворачиванием локального веб-сервера: это позволило бы выполнять такие academic решения вообще без какой-либо платформы UI, например, на виртуальной машине в облаке. При этом сохранялся бы принцип для слабаков «пиши один раз, запускай везде». А еще прямо в облаке можно было бы запустить сам MonoDevelop :) Есть маленькая вероятность, что Xamarin в процессе дружбы с Microsoft со временем представит Xwt как основное решение Microsoft для кросс-платформенных UI, и тогда этот сырой бесплатный проект будет везде.
Некоторые эксперты полагают, что пытаться заинтерфейсить существующие тулкиты UI на разных платформах — задача по определению не имеющая адекватного решения, потому что различные платформы обладают различным user experience. Xwt, впрочем, вполне работоспособен. Его минус в том, что он охватывает жесткий набор контролов и поведений, и тем самым требует от бэкенда реализацию всех этих интерфейсов. Значительно лучше было бы, если бы каждый контрол представлял собой подобие контракта; приложение указывало бы набор таких контрактов и выполнялось бы только на тех фреймворках, где эти контракты доступны.
А еще хотелось бы попросить владельцев макинтошей попробовать потестировать работу фреймворка в различных режимах.
Автор: ksigne