среда, 15 июля 2009 г.

MultiVox и Silverlight

В общем после небольшого изучения Silverlight 3, написал сверхпростую смотрелку презентации на сайте АРИС MultiVox. Кода три-четыре функции + асинхронная загрузка списка картинок для просмотра, остальное на себя берет silverlight. В общем впечатления очень приятные.

суббота, 27 июня 2009 г.

Стили и проблема с DataGrid

Чуть с ума не сошел пока не нашел в чем подвох.
Использую DataGrid в своем проекте наблюдал как подвисает вижуал студия в Cider - е.
При этом колонки дата-грида весело улетали вправо на бесконечность (видимо именна эта бесконечность и вешала cider). Оказалось дело в том что на данном окне у меня в ресурсах был прописан стиль для кнопки, в котором устанавливался Margin в 2 или в 3. Вот такой вот сюрприз :)

среда, 22 октября 2008 г.

RefreshRate и WPF

cgvictor нашел решение старой проблемы WPF.
Вот его решение

вторник, 21 октября 2008 г.

SelfHostedSilverlight сервис

Часто бывает следующая ситуация, необходимо написать windows службу и специальный конфигуратор к этой службе. Подобные задачи заставляют решать много мелких проблем, например необходимо чтобы служба опрашивала файловую систему или реестр для того чтобы узнать - изменилась ли конфигурация и подобное. С выходом Silverlight мне пришло в голову достаточно удобное решение, которое на мой взгляд является и оптимальным с точки зрения использования и удобным с точки зрения программирования. Представим себе что:
    Служба кроме как своей основной деятельности при помощи WCF регистрирует сервис, который в своем контенте содержит все методы для конфигурации службы
    Служба внутри себя содержит embedded resource - XAP silverlight файл в котором по сути происходит подсоединению к WCF сервису и вся логика конфигурации
     Далее практически с любого компутера где стоит IE/Firefox + Silverlight плагин можно конфигурировать эту службу запросив через Web http XAP у службы.
   
   
Как же подобное реализовать. Начнем с самого сложного - WCF части данного проекта:

Silverlght плагин обязан крутиться в рамках политики браузеров - ничего тут не поделаешь, поэтому основное, что требуется от нашей службы - это предоставление правильного crossdomain - документа о доступе к данным службы. Чтобы подобное реализовать нам понадобится:
    WebHttpBinding - для доставки подобного контента по запросу браузера
    Метод который будет претворяться документом: /clientaccesspolicy.xml

Посмотрим на контракт для сервиса и на наш базовый класс для нашего сервиса:

    [ServiceContract]
    public interface ISelfHostedPolicy
    {
        [OperationContract, WebGet(UriTemplate = "/clientaccesspolicy.xml")]
        Stream GetSilverlightPolicy();

        [OperationContract, WebGet(UriTemplate = "/index.html")]
        Stream GetHomePage();

        [OperationContract, WebGet(UriTemplate = "/SilverlightPage.xap")]
        Stream GetSilverlightPage();
    }

Как видим метод GetSilverlightPolicy - должен вернуть текст policy. Метод GetHomePage вернет текст стартовой страницы в которой будет прописан скрипт для загрузки silverlight плагина а сам xap файл вернется в методе GetSilverlightPage. Базовый класс обязан быть совместимым с Asp.Net поэтму на нем висит аттрибут  AspNetCompatibilityRequirementsAttribute:

    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public abstract class SelfHostedConfiguration<T1, T2> : ISelfHostedPolicy  where T2 : ISelfHostedPolicy
    {
        private static byte[] silverlightPage;
        private static byte[] homePage;

        public static ServiceHost InitServiceHost(string title, Stream silverlightPageStream, Uri baseAddress)
        {
            if (silverlightPageStream == null)
                throw new ArgumentNullException("Silverlight stream is null");

            SelfHostedConfiguration<T1, T2>.silverlightPage = StreamToByteArray(silverlightPageStream);
            using (var reader = new StreamReader(typeof(ISelfHostedPolicy).Assembly.GetManifestResourceStream("Alda.Framework35.SelfHostedServiceConfig.IndexTemplate.html")))
            {
                string html = reader.ReadToEnd();
                html = html.Replace("$(TITLE)", title);
                homePage =  StreamToByteArray(new MemoryStream(Encoding.UTF8.GetBytes(html)));
            }

            ServiceHost host = new ServiceHost(typeof(T2), baseAddress);
            host.AddServiceEndpoint(typeof(T1), new BasicHttpBinding(), "basic");
            host.AddServiceEndpoint(typeof(ISelfHostedPolicy), new WebHttpBinding(), "").Behaviors.Add(new WebHttpBehavior());
            var smb = new ServiceMetadataBehavior { HttpGetEnabled = true };
            host.Description.Behaviors.Add(smb);
            return host;
        }

        private static byte[] StreamToByteArray(Stream stream)
        {
            using(MemoryStream r = new MemoryStream((int)stream.Length))
            {
                stream.Seek(0, SeekOrigin.Begin);
                while (true)
                {
                    byte[] buff = new byte[32000];
                    int readed = stream.Read(buff, 0, buff.Length);
                    r.Write(buff, 0, readed);
                    if(readed == 0)
                        break;
                }
                return r.ToArray();
            }
        }

        #region IPolicyRetriever Members

        public Stream GetSilverlightPolicy()
        {
            string result =
                @"<?xml version=""1.0"" encoding=""utf-8""?>
<access-policy>
    <cross-domain-access>
        <policy>
            <allow-from http-request-headers=""*"">
                <domain uri=""*""/>
            </allow-from>
            <grant-to>
                <resource path=""/"" include-subpaths=""true""/>
            </grant-to>
        </policy>
    </cross-domain-access>
</access-policy>";
            WebOperationContext.Current.OutgoingResponse.ContentType = "application/xml";
            return new MemoryStream(Encoding.UTF8.GetBytes(result));
        }

        public Stream GetHomePage()
        {
            WebOperationContext.Current.OutgoingResponse.ContentType = "text/html";
            return new MemoryStream(homePage);
        }

        public Stream GetSilverlightPage()
        {
            WebOperationContext.Current.OutgoingResponse.ContentType = "application/x-silverlight";
            return new MemoryStream(silverlightPage);
        }

        #endregion       
    }


На что здесь необходимо обратить внимание, что InitServiceHost выполняет задачу по регистрации сервиса. Silverlight умеет работать только с BasicHttpBinding - поэтому основной наш сервис ставится в точку с basicHttpBinding. Также важно отметить что наши стартовая страница (index.html) и сам XAP модуль переносятся в byte[] во время инициализации сервиса. Сам IndexTemplate.html добавляется в проект как Embedded Resource и достается во время инициализации сервиса, в нем $(TITLE) заменяется на ваш собственный title. Вот его текст:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<!-- saved from url=(0014)about:internet -->
<head>
    <title>$(TITLE)</title>

    <style type="text/css">
    html, body {
        height: 100%;
        overflow: auto;
    }
    body {
        padding: 0;
        margin: 0;
    }
    #silverlightControlHost {
        height: 100%;
    }
    </style>
   
    <script type="text/javascript">
        function onSilverlightError(sender, args) {
       
            var appSource = "";
            if (sender != null && sender != 0) {
                appSource = sender.getHost().Source;
            }
            var errorType = args.ErrorType;
            var iErrorCode = args.ErrorCode;
           
            var errMsg = "Unhandled Error in Silverlight 2 Application " +  appSource + "\n" ;

            errMsg += "Code: "+ iErrorCode + "    \n";
            errMsg += "Category: " + errorType + "       \n";
            errMsg += "Message: " + args.ErrorMessage + "     \n";

            if (errorType == "ParserError")
            {
                errMsg += "File: " + args.xamlFile + "     \n";
                errMsg += "Line: " + args.lineNumber + "     \n";
                errMsg += "Position: " + args.charPosition + "     \n";
            }
            else if (errorType == "RuntimeError")
            {          
                if (args.lineNumber != 0)
                {
                    errMsg += "Line: " + args.lineNumber + "     \n";
                    errMsg += "Position: " +  args.charPosition + "     \n";
                }
                errMsg += "MethodName: " + args.methodName + "     \n";
            }

            throw new Error(errMsg);
        }
    </script>
</head>

<body>

    <!-- Runtime errors from Silverlight will be displayed here.
    This will contain debugging information and should be removed or hidden when debugging is completed -->
    <div id='errorLocation' style="font-size: small;color: Gray;"></div>

    <div id="silverlightControlHost">
        <object data="data:application/x-silverlight," type="application/x-silverlight-2" width="100%" height="100%">
            <param name="source" value="SilverlightPage.xap"/>
            <param name="onerror" value="onSilverlightError" />
            <param name="background" value="white" />
            <param name="minRuntimeVersion" value="2.0.31005.0" />

            <param name="autoUpgrade" value="true" />
            <a href="http://go.microsoft.com/fwlink/?LinkID=124807" style="text-decoration: none;">
                 <img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight" style="border-style: none"/>
            </a>
        </object>
        <iframe style='visibility:hidden;height:0;width:0;border:0px'></iframe>
    </div>
</body>
</html>

как видно стартовая страница будет подгружать SilverlightPage.xap которым у нас притворяется метод GetSilverlightPage.


Двинемся дальше. Создадим тестовый проект (например консольное приложение) который будет у нас нашим сервисом. Добавим ссылки на сборки System.ServiceModel и System.ServiceModel.Web

вот код нашего пробного сервиса:

    [ServiceContract]
    public interface ILocatorConfig
    {
        [OperationContract]
        string GetVersion();
    }

    public class LocatorConfig : SelfHostedConfiguration<ILocatorConfig, LocatorConfig>, ILocatorConfig
    {
        public static void InitConfigService()
        {
            string baseAddress = "http://" + Environment.MachineName + ":4040";
            SelfHostedConfiguration<ILocatorConfig, LocatorConfig>.InitServiceHost("Locator config",
                typeof(LocatorConfig).Assembly.GetManifestResourceStream("Alda.LocatorService.Configuration.Alda.LocatorConfig.xap"), new Uri(baseAddress)).Open();
        }


        public string GetVersion()
        {
            return "v1.0";
        }       
    }

сам сервис у нас ничего особенно нового не сообщает кроме как версии сервиса. Сам метод main у нас может содержать вызов LocatorConfi.InitConfigService() и например Console.ReadKey();
  Так как на настоящий момент мы еще не написали сам Silverlight.XAP модуль для конфигурации вместо typeof(LocatorConfig).Assembly.GetManifestResourceStream("Alda.LocatorService.Configuration.Alda.LocatorConfig.xap") мы передадим пустой new MemoryStream() чтобы InitServiceHost не вылетал с ArgumentNullException. Далее если при запущенном сервисе вы запустите ваш браузер с установленным плагином Silverlight и наберете в нем http://localhost:4040/index.html то должны увидеть пустой экран кликнув правой кнопкой в левый верхний угол которого должны увидеть контекстное меню SilverlightConfiguration.

Теперь пришло время написать наш XAP модуль, здесь мы практически кода писать и не будем - создадим SilverlightApplication проект в Visual Studio 2008 выберем способ хостинга ASP.Net (это нужно только для удобства отладки) и воспользуемся добавлением reference: "Add Service Reference" где в окне самой службы напишем: http://localhost:4040 (важно чтобы в этот момент ваша служба была запущена). Для общения с вашим сервисом вам необходимо будет создать клиента и при помощи асинхронных вызовов общаться со службой - например вот так:


        public Page()
        {

            InitializeComponent();

            LocatorConfigClient client = new LocatorConfigClient();
            client.OpenCompleted += client_OpenCompleted;
            client.OpenAsync();
        }

        void client_OpenCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {
            var client = ((LocatorConfigClient) sender);
            client.OpenCompleted -= client_OpenCompleted;
            client.GetVersionCompleted += client_GetVersionCompleted;
            client.GetVersionAsync();
        }

        void client_GetVersionCompleted(object sender, GetVersionCompletedEventArgs e)
        {
            var client = ((LocatorConfigClient)sender);
            client.GetVersionCompleted -= client_GetVersionCompleted;
            client.CloseAsync();
        }


Вы можете добавить на вашу страницу настройки все необходимые контроли управления службой и общаться при помощи WCF контрактов с нею. Скомпилированный XAP модуль помещаете в  Embedded Resource службы и при вызове метода
SelfHostedConfiguration<ILocatorConfig, LocatorConfig>.InitServiceHost("Locator config",

                typeof(LocatorConfig).Assembly.GetManifestResourceStream("Alda.LocatorService.Configuration.Alda.LocatorConfig.xap"), new Uri(baseAddress)).Open(); указываете правильный Stream на ваш XAP модуль.


Важно что базовый адрес не может быть например http://localhost/config - так как clientaccesspolicy.xml обязан лежать на самом верхнем уровне адресов.
Поэтому если у вас нет возможности на самом верхнем уровне прописать clientaccesspolicy.xml то адрессация только при помощи портов.

вторник, 29 апреля 2008 г.

Сборки со строгим именем

В случае когда необходимо загрузить ResourceDictionary из другой сборки часто приходится писать что-то вроде такого:

Source="/Alda.WPF;component/CommonConverters.xaml"

однако могут возникнуть проблемы в случае, когда сборка имеет строгое имя. Тогда необходимо указывать версию и public key token

Source="/Alda.WPF;V1.0.0.0;0e2fb8bcf6bca8e4;component/CommonConverters.xaml"

понедельник, 28 апреля 2008 г.

CustomWPF library

У меня долгое время вылетали непонятные ошибки типа XamlParseException или невозможности загрузить ResourceDictionary из сборки в случае когда одна сборка ссылается на другую и использует UserControl из другого namespace.



проблема решилась когда в аттрибутах сборки с UserControl - ем был добавлен следующий аттрибут:

[assembly: XmlnsDefinition(
"http://multivox.ru/MyAssembly.dll",
"MyNamespace")].

С таким объявлением аттрибута у сборки проблемы вроде как исчезли.

я не активный блоггер

Добрый день, я не активный блоггер, но, иногда, у меня получается решить некоторые
проблемы связанные с WPF. Так что может кому будет полезным.