Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Кроссплатформенный GUI на C# и веб-спецтехнологии

Anna | 17.06.2014 | нет комментариев
Самая первая спецификация продукта, Отчасти устная, содержала требование – присутствие кроссплатформенного(Windows, Linux, Mac) заказчика под десктоп и облегченную версию мобильного(Windows, Android, iPhone). По вероятности интерфейс должен быть максимально схожим на различных ОС.
Вследствие Mono мы можем писать кроссплатформенные приложения, но вопрос с GUI остается открытым. Имеющиеся спецтехнологии под .Net(Windows Forms, WPF) отлично работают только под Windows, и у нас теснее был грустный навык портирования Windows Forms. Под Linux мы можем применять GtkSharp, но идея ставить Mono на Windows при наличии .Net мне не нравится. В результате доводится писать и поддерживать обособленный интерфейс под всякую ОС.
Что в этой обстановки могла придумать команда .Net(с уклоном под веб)? Решили встраивать Webkit и писать GUI на связке html-js-css.
На сегодняшний день мы 2 года удачно используем такой подход для Windows и год – под Linux и Mac. До мобильной платформы пока руки не дошли.

Для чего?

Одинаковый интерфейс под всеми платформами. Допустимы лишь незначительные различия при отрисовке шрифтов, при отображении элементов. Последнее неизменно объясняется ошибками в верстке.
Разработка под одну ОС. Эмпирическим путем нами было выявлено, что довольно вести основную разработку под Windows, а под остальными платформами лишь изредка проверять. Скажем, перед релизом.
Каждая сила веб-разработки. Исключительно это актуально, если команда состоит из веб-разработчиков. Дозволено применять html5, css3, привычные подходы и библиотеки. Мы, кстати, используем знаменитый фреймворк для построения веб-приложений, в результате у нас интерфейс только на js.
Распределение на frontend и backend. Возникает вероятность вести отдельно разработку представления и логики приложения, согласовав апи. Скажем, наш интерфейс — это полновесное веб-приложение, взаимодействующее с «сервером» через ajax-запросы. В десктоп приложении эмулируем обработку этих запросов. Таким образом, дозволено разрабатывать и отлаживать интерфейс с применением инструментов разработчика в Chrome, закинув нужные mock результаты на локальный сервер. Особенно уверенные в себе разработчики, которым довольно доступа к dom и консоли, могут применять firebug lite в десктоп приложении.
Есть о чем написать на прогр. Сходственные эксперименты добавляют энтузиазма при разработке и скрашивают грозные будни программиста.

Как?

Под всякую платформу создаем нативное приложение, GUI которого состоит из одного элемента пользовательского интерфейса – браузера, растянутого во все окно.
Нам необходимо обучиться отображать html в браузере, обнаружить метод осуществлять вызовы js-C# и С#-js. Отличия в вызовах могут показаться необычными, но есть примитивное трактование – в используемых браузерах реализован и работает различный функционал.

Mac OSX

Выбора что встраивать под маком нет. Следственно используем MonoMac и типовой браузер. Но здесь есть подвох в лицензиях. Дозволено вольно распространять приложение без Mono, т.е. пользователь сам должен будет поставить Mono и, следственно, приложение не может попасть в AppStore. Если же мы хотим встроить Mono в приложение, то придется приобретать Xamarin.Mac, тот, что обойдется в 300 либо 1000 баксов в зависимости от размера компании за одного программиста.
Под мак получился самый лаконичный код. Исключительное не подсознательно внятное место — вызов С# из js.
Позже инициализации браузера нам нужно сделать объект, через тот, что js сумеет вызывать способы контроллера из C#. Назовем объект interaction:

    webView.WindowScriptObject.SetValueForKey(this, new NSString("interaction"));

Определяем способы и указываем, какие из них могут быть вызваны из js:

    [Export("callFromJs")]
    public void CallFromJs(NSString message)
    {
        CallJs("showMessage", message   " Результат из C#");
    }

    [Export ("isSelectorExcludedFromWebScript:")]
    public static bool IsSelectorExcludedFromScript(MonoMac.ObjCRuntime.Selector sel)
    {
        if (sel.Name == "callFromJs")
            return false;

        return true; // Воспрещаем вызов всех остальных способов
    }

Сейчас в js мы можем вызвать способ сallFromJs:

    window.interaction.callFromJs('Вызов из js.');
Полный листинг заявленного функционала с комментариями

    public partial class MainWindowController : MonoMac.AppKit.NSWindowController
    {
        /*Автоматически сгенерированный код*/

        //Интерфейс из xib(nib) построен, инициализорован и все ссылки на UI компоненты установлены
        public override void AwakeFromNib ()
        {
            base.AwakeFromNib ();

            // Создаем объект через тот, что js сумеет обращаться к C#. Назовем его interaction
            // window.interaction.callFromJs(param1, param2, param3) - вызываем способ из js.
            webView.WindowScriptObject.SetValueForKey(this, new NSString("interaction"));

            webView.MainFrame.LoadHtmlString (@"
                <html>
                    <head></head>
                    <body id=body>
                        <h1>Интерфейс</h1>
                        <button id=btn>Вызвать C#</button>
                        <p id=msg></p>

                        <script>
                            function buttonClick() {
                                interaction.callFromJs('Вызов из js.');
                            }
                            function showMessage(msg) {
                                document.getElementById('msg').innerHTML = msg;
                            }

                            document.getElementById('btn').onclick = buttonClick;
                        </script>
                    </body>
                </html>", null);

        }

        // Из соображений безопасности указываем, какие способы могут быть вызваны из js
        [Export ("isSelectorExcludedFromWebScript:")]
        public static bool IsSelectorExcludedFromWebScript(MonoMac.ObjCRuntime.Selector aSelector)
        {
            if (aSelector.Name == "callFromJs")
                return false;

            return true; // Воспрещаем вызов всех остальных способов
        }

        [Export("callFromJs")]
        public void CallFromJs(NSString message)
        {
            CallJs("showMessage", new NSObject[] { new NSString(message   " Результат из C#") });
        }

        public void CallJs(string function, NSObject[] arguments)
        {
            this.InvokeOnMainThread(() =>
            {
                webView.WindowScriptObject.CallWebScriptMethod(function, arguments);
            });
        }
    }

Рабочий пример на github.
Этого видео мне дюже не хватало, когда я разбирался: «Как добавить ссылку на WebView в код контроллера».

Ubuntu

Под Mono используем пакет webkit-sharp.
Плавно возрастает число не подсознательно внятного кода.
Для вызова C# из js дозволено перехватывать переход по ссылке.

    browser.NavigationRequested  = (sender, args) =>
    {
        var url = new Uri(args.Request.Uri);
        if (url.Scheme != "mp")
        {
            //mp - myprotocol.
            //Обрабатываем вызовы только нашего особого протокола.
            //Переходы по обыкновенным ссылкам работают как и раньше
            return;
        }

        var parameters = System.Web.HttpUtility.ParseQueryString(url.Query);
        handlers[url.Host.ToLower()](parameters);

        //Отменяем переход по ссылке
        browser.StopLoading();
    };

Вызов из js будет выглядеть так:

    window.location.href = 'mp://callFromJs?msg=Сообщение из js.';

Еще один метод завязывается на событие TitleChanged.
В js устанавливаем title у документа:

    document.title = JSON.stringify({
        method: 'callFromJs',
        arguments: { msg: 'Сообщение из js'}
    });

В С# срабатывает событие TitleChanged, мы десериализуем title и подобно предыдущему подходу вызываем обработчик.

В рассматриваемой обертке WebKit дозволено из С# исполнять всякий js код, что разрешает нам реализовать вызов js из C#:

    public void CallJs(string function, object[] args)
    {
        //Формируем javascript
        var js = string.Format(@"
            {0}.apply(window, {1});
        ", function, new JavaScriptSerializer().Serialize(args));

        Gtk.Application.Invoke(delegate {
            browser.ExecuteScript(js);
        });
    }
Полный листинг заявленного функционала с комментариями

    public partial class MainWindow: Gtk.Window
    {
        private Dictionary<string, Action<NameValueCollection>> handlers;
        private WebView browser;

        public MainWindow (): base (Gtk.WindowType.Toplevel)
        {
            Build ();

            CreateBrowser ();

            this.ShowAll ();
        }

        protected void OnDeleteEvent (object sender, DeleteEventArgs a)
        {
            Application.Quit ();
            a.RetVal = true;
        }

        private void CreateBrowser ()
        {
            //Создаем массив обработчиков доступных для вызова из js
            handlers = new Dictionary<string, Action<NameValueCollection>>
            {
                { "callfromjs", nv => CallJs("showMessage", new object[] { nv["msg"]   " Результат из С#" }) }
            };

            browser = new WebView ();

            browser.NavigationRequested  = (sender, args) =>
            {
                var url = new Uri(args.Request.Uri);
                if (url.Scheme != "mp")
                {
                    //mp - myprotocol.
                    //Обрабатываем вызовы только нашего особого протокола.
                    //Переходы по обыкновенным ссылкам работают как и раньше
                    return;
                }

                var parameters = System.Web.HttpUtility.ParseQueryString(url.Query);

                handlers[url.Host.ToLower()](parameters);

                //Отменяем переход по ссылке
                browser.StopLoading();
            };

            browser.LoadHtmlString (@"
                    <html>
                        <head></head>
                        <body id=body>
                            <h1>Интерфейс</h1>
                            <button id=btn>Вызвать C#</button>
                            <p id=msg></p>

                            <script>
                                function buttonClick() {
                                    window.location.href = 'mp://callFromJs?msg=Сообщение из js.';
                                }
                                function showMessage(msg) {
                                    document.getElementById('msg').innerHTML = msg;
                                }

                                document.getElementById('btn').onclick = buttonClick;
                            </script>
                        </body>
                    </html>
                ", null);

            this.Add (browser);
        }

        public void CallJs(string function, object[] args)
        {
            var js = string.Format(@"
                {0}.apply(window, {1});
            ", function, new JavaScriptSerializer().Serialize(args));

            Gtk.Application.Invoke(delegate {
                browser.ExecuteScript(js);
            });
        }
    }

Рабочий пример на github.

Windows

Основную разработку мы ведем под Windows.
Подробности теснее были описаны моим коллегой год назад и за это время твердо ничего не изменилось. В какой-то степени это свидетельствует о безопасности подхода. Также в статье огромнее деталей, которые абсолютно довольно разглядеть на примере одной ОС.
Я лишь добавлю пример на github.

Особенности

У такого увлекательного метода представления интерфейса есть свои особенности, о которых стоит знать, если вы решите повторить наш путь.
Добавочный расход времени при строительстве. Подготовка для встраивания интерфейса как источника в приложение занимает некоторое время: Saas, склеивание файликов, минификация. Но но при разработке интерфейса в браузере нет необходимости всякий раз перестраивать ни интерфейс, ни само приложение.
Увеличение расхода оперативной памяти. Это исключительный солидный минус данного подхода. Браузер в нашем случае потребляет мегабайт 50 оперативной памяти. С одной стороны это немножко, но если целевая аудитория полагает ветхую технику, то придется принимать во внимание эту специфика. Правда будет ли подобный интерфейс, реализованный на иной спецтехнологии, потреблять поменьше памяти – непостижимо. В любом случае, у нас расход оперативной памяти браузером – черный ящик. Других системных задач либо проседаний продуктивности нами подмечено не было.

Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB