Silverlight. Первое знакомство.

Технология Silverlight является прямым конкурентом Flash, позволяющая создавать интерактивное содержимое на стороне клиента. Что же, конкретно для меня, интересно в этой технологии?
  • разработка Silverlight-приложений возможна на управляемом языке программирования C# и VB.NET (ну конечно же самым привлекательным здесь выступает C#, ну вы понимаете о чем я :)    );
  • CLR среда;
  • Silverlight поддерживает основные компоненты .NET Framework, что позволяет использовать те же подходы при разработке, что и в WinForms приложениях;
  • и конечно же все интерактивное, "красивенькое" - анимация, мультимедиа, всякие drag and drop - одно удовольствие для разработки корпоративных решений, где пользователю важны не только функционал, а и user friendly интерфейс (ведь для него важно, чтоб "красивенький" - иначе в топку этот Silverlight).
Microsoft много усилий и времени потратили на развитие этой технологии. Если посмотреть, на то, что было в первом Silverlight, а он явно был "убогим", т.к. поддерживал только двумерную графику, воспроизводил мультимедиа, но никак не поддерживал .NET (и никакого CLR), из-за чего приходилось использовать JScript (а как по мне это совсем не интересно...),  то сейчас стало гораздо приятнее за ним наблюдать. Число людей, у которых установлен Silverlight, стремительно растет (или уже выросло), как, например, показывают тут (я правда не совсем понял, что же они показывают, но определенно видно, что Silverlight крутеет.... не так ли?)


Оставим эту болтовню, ведь про Silverlight можно почитать гораздо больше и интереснее в более достоверных источниках, а лучше попробуем перейти к первому творению на Silverlight.

Что я попытаюсь сделать? Напишем приложение, которое будет работать с внешним источником данных и дадим клиенту возможность их редактировать/просматривать. Т.к. Silverlight это клиентское приложение, то оно не имеет прямого доступа к данным, следовательно, мы должны воспользоваться услугой взаимодействия с базой данных. Поэтому будет использоваться следующее:
  • LINQ для работы с данными, которые будет использовать наше приложение;
  • WCF сервис - откуда и будет происходить работа с данными;
  • Использование Silverlight элементов управления для ввода и отображения данных;
  • И напоследок попробую приукрасить элементы интерфейса в Expression Blend, который входит в состав Expression Studio Ultimate 4.0;
И наверное пора бы уточнить, что речь таки пойдет о пятой версии этого чуда. Приступим!

Создадим приложение в Visual Studio 2010, которое назовем очень просто - SLData.



После создания проекта появится следующий диалог:



При выборе таких параметров будет автоматически создан ASP.NET проект. Выбираем версию Silverlight 5. Галочка возле "Enable WCF RIA Services" позволит использовать RIA Services, но нам это не нужно.

Созданный проект имеет следующую структуру:



В приложение включены Silverlight 5 и ASP.NET  Web applications.

Перед тем, как начать работать с базой данных, создадим ее:



Поставим значение Recovery model в Simple, чтобы наша тестовая база кушала меньше дискового пространства



Затем, добавим в проект SLData.Web класс LINQ to SQL:



После создания LINQ to SQL класса появится окно дизайнера:



Затем, в Server Explorer добавим новое подключение к ранее созданной БД:






После создадим таблицу в базе данных (на данном этапе наша БД пустая).


Так как наша база называется CustomerDB, то создадим таблицу, в которой будут содержаться какие-либо данные о клиенте.


И назовем таблицу соответственно:



Чтобы значение поля CustomerID формировалось автоматически, при добавлении записей в таблицу, необходимо выставить следующие свойства:


Для того, чтобы автоинкремент работал, то обязательно в CustomerDataClasses.dbml необходимо для свойства AutoGeneratedValue поля CustomerID определить значение true, иначе при добавлении данных в таблицу средствами Linq ничего хорошего не выйдет.




Что же! Мы успешно подключились к базе данных. Теперь нам необходим сервис, который будет общаться (мне нравиться will communicate, потому что гладиолус WCF (Windows Communication Foundation)), а точнее связывать клиента Silverlight с базой данных.



После того, как мы создали наш сервис, добавим в него пару методов, с помощью которых мы сможем получать и вставлять данные о клиенте.


Метод GetCustomerList() возвращает список клиентов


    
        [OperationContract]
        public List<Customer> GetCustomerList()
        {
            CustomerDataClassesDataContext db = new CustomerDataClassesDataContext();
            var query = from d in db.Customers
                        select d;

            return query.ToList();
        }

Метод AddNewCustomer() выполняет добавление новых данных в таблицу.


        
        [OperationContract]
        public void AddNewCustomer(string name, string surname)
        {
            CustomerDataClassesDataContext db = new CustomerDataClassesDataContext();

            Customer customer = new Customer() { Name = name, Surname = surname };

            db.Customers.InsertOnSubmit(customer);

            try
            {
                db.SubmitChanges();
            }
            catch (Exception)
            {
                
            }
        }

Теперь добавим в Silverlight проект SLData  ссылку на WCF сервис - нажатие правой кнопкой мыши на Reference - Add Service Reference




После добавления референса будет создан файл ServiceReferences.ClientConfig - это XML файл, который содержит информацию и настройки для использования нашего ServiceReference со стороны клиента.


Теперь займемся дизайном нашего интерфейса. У нас уже есть страница MainPage.xaml. Добавим еще форму InputData.xaml и пусть на ней разместится форма для ввода данных, а для просмотра данных создадим форму - DataView.xaml. Почему именно так будет понятно дальше.




Немного попытаюсь описать структуру страницы или наверное правильнее компоновку страницы xaml.  После добавления новой страницы открывается следующий код:


Модель компоновки в Silverlight позволяет организовать содержимое в наборе различных контейнеров компоновки (несуразица какая-то). Что это значит? Элемент UserControl может умещать в себе только один элемент. Для того, чтобы на странице можно было уместить больше, необходимо в UserControl  размещать еще один контейнер, а в нем другие элементы (ну как-то так....). В данном случае таким контейнером выступает Grid.

Silverlight предоставляет три класса для организации компоновки, это:
  • StackPanel - позволяет размещать элементы в горизонтальном или вертикальном виде;
  • Grid - является самым крутым контейнером для компоновки, элементы в нем размещаются в виде элементов некоторой сетки, с которой можно делать множество разных хитростей, но не будем об этом...
  • Canvas - позволяет размещать элементы с использованием точных координат;
Прежде чем создавать какие-либо элементы управления давайте позаботимся о том, как будет реализовано перемещение по страницам в нашем приложении. 

Начиная с Silverlight 3 появился замечательный Navigation Framework, который делает решение задачи навигации по Silverlight приложению достаточно простым и удобным (до этого счастливого момента приходилось заниматься извращениями, чтобы обеспечить нормальную навигацию по сайту).

Для того, чтобы все это работало, в заголовке .xaml файла необходимо объявить пространство имен navigation:

Если вы заметили, то на вновь созданной нами странице DataView.xaml это пространство имен уже есть изначально.

Давайте на странице MainPage создадим необходимые элементы управления для навигации. Для этого вставим следующий код в контейнер Grid:

<Grid.RowDefinitions>
      <RowDefinition  Height="50"></RowDefinition>
      <RowDefinition  Height="Auto"></RowDefinition>
</Grid.RowDefinitions> 
       
<StackPanel x:Name="NavBtn" Orientation="Horizontal" Grid.Row="0" FlowDirection="LeftToRight">
<HyperlinkButton Content="Input" Height="23" NavigateUri="/InputData.xaml" x:Name="InputButton" Width="50" />
<HyperlinkButton Content="View" Height="23" NavigateUri="/DataView.xaml" x:Name="ViewButton" Width="50" />
</StackPanel>

<navigation:Frame x:Name="ContentFrame" Grid.Row="1" Source="/InputData.xaml" HorizontalAlignment="Stretch" VerticalContentAlignment="Stretch" Background="White">

</navigation:Frame>



Откровенно говоря задолбался  устал, пока нормально вставил этот XML, чтобы было читабельно и юзабельно....


В этом коде мы определили две строки в компоненте Grid - это <RowDefinition>. StackPanel, в котором находятся кнопки для навигации по страницам, свойство Grid.Row элемента определяет в какой строке элемента Grid он находится. NavigateUri указывает ссылку на страницу, на которую ведет эта кнопка.

Далее идет интересный элемент Frame, в котором и будут отображаться наши страницы. Этот элемент является как бы хостом для страниц Silverlight приложения. Свойство Source указывает какая страница будет открываться по умолчанию. Давайте создадим в наших страницах какой-нибудь контент, чтобы можно было понять где мы находимся. Я в каждой странице поместил метку "Input Page!" и "View Page!" соответственно.

Что же, попробуем запустить наше приложение.


Заголовок вкладки страницы называется InputData Page, в адресной строке мы видим, что открыта страница InputData.xaml и соответственно текстовая метка "Input Page!"

Если мы нажмем на кнопку View, то получим следующий эффект:


Как видите, весь контент отображается в нашем фрейме (Frame).

Попробуйте использовать кнопки "Вперед" / "Назад" вашего браузера! Работает? Круто!!! Это сложно недооценить, если только не использовать Navigation Framework.

Кажется отлично, ведь навигация работает. Давайте попробуем немного ее доработать. Если представить, что у нашего приложения достаточно большая вложенность страниц или длинные имена, то не совсем комфортно будет наблюдать за длинной и растянутой ссылкой в адресной строке. Чтобы сделать все красиво и феншуйно нам поможет прекрасный Navigation Framework, который предоставляет нам объект UriMapper, позволяющий использовать короткие метки для страниц. Для того, чтобы его использовать, добавим следующее пространство имен в файле MainPage.xaml:

xmlns:uriMapper="clr-namespace:System.Windows.Navigation;assembly=System.Windows.Controls.Navigation"

И затем добавим следующий код:

<navigation:Frame x:Name="ContentFrame" Grid.Row="1" Source="/InputData" HorizontalAlignment="Stretch" VerticalContentAlignment="Stretch" Background="White">
          <navigation:Frame.UriMapper>
              <uriMapper:UriMapper>                   
                  <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/{pageName}.xaml"/>
              </uriMapper:UriMapper>
          </navigation:Frame.UriMapper>
</navigation:Frame>

Как видим, мы создаем метку для страницы и привязываем к существующему адресу страницы.


Uri  - это метка. MappedUri - реальный адрес страницы.

В данном случае значение метки привязывается к имени страницы. Можно указывать явно имя метки, тогда для каждой страницы придется создавать метку.

Запустим!



Как видим все работает также, только отображаются короткие имена страниц в адресной строке. Мне кажется так значительно лучше.

Мы уже многое сделали. Осталось только обработать наши данные.

Добавим следующие элементы управления на форму InputData:

<Grid x:Name="LayoutRoot" Height="278">
      <sdk:Label Height="28" HorizontalAlignment="Left" Margin="12,12,0,0" Name="label1" VerticalAlignment="Top"
          Width="616" Content="Введите данные о клиенте" />
      <TextBox Height="23" HorizontalAlignment="Left" Margin="192,92,0,0" Name="NameText" VerticalAlignment="Top" 
          Width="320" />
      <TextBox Height="23" HorizontalAlignment="Left" Margin="192,139,0,0" Name="SurnameText" VerticalAlignment="Top" 
          Width="320" />
      <sdk:Label Height="28" HorizontalAlignment="Left" Margin="63,92,0,0" Name="label2" VerticalAlignment="Top"
          Width="120" Content="Имя" />
      <sdk:Label Height="28" HorizontalAlignment="Left" Margin="63,139,0,0" Name="label3" VerticalAlignment="Top"
          Width="120" Content="Фамилия" />
      <Button Content="Применить" Height="23" HorizontalAlignment="Left" Margin="437,194,0,0" Name="AcceptBtn" 
          VerticalAlignment="Top" Width="75" />
</Grid>

Сразу добавим обработчик на нашу кнопку AcceptButton:

        private void AcceptBtn_Click(object sender, RoutedEventArgs e)
        {
            CustomerServiceClient serv = new CustomerServiceClient();

            serv.AddNewCustomerAsync(NameText.Text, SurnameText.Text);            
        }

Здесь мы подключаемся к нашей службе и вызываем метод для добавления данных в БД.

В итоге получится вот такая вот форма. Запустим и попробуем вставить данные, нажав на кнопку "Применить"!


Вуаля! Работает! (бывает же такое...). Как видим данные попали в базу.



Теперь добавим компоненты для просмотра наших данных. Это будет стандартный элемент Grid. Вставим следующий код в файл ViewData.xaml.

<Grid x:Name="LayoutRoot">
    <sdk:Label Height="28" Content="Данные о клиентах" HorizontalAlignment="Left" Margin="10,10,0,0" Name="label1" 
                  VerticalAlignment="Top" Width="618" />
    <sdk:DataGrid AutoGenerateColumns="True" Height="424" HorizontalAlignment="Left" Margin="10,44,0,0" Name="dataGrid1" 
                  VerticalAlignment="Top" Width="618" />   
</Grid>

Для того, чтобы загрузить данные в таблицу необходимо подключиться к сервису и вызвать метод, который вытягивает данные из базы. Добавим вызов GetCustomerList в метод, который сформирован автоматически при создании страницы OnNavigatedTo, он будет вызываться, когда пользователь перенаправляется на страницу.


        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            CustomerServiceClient service = new CustomerServiceClient();
            
            service.GetCustomerListAsync();
                
        }

И тут Внимание! Есть одна закавырка!

Если навести курсор мыши на вызываемый метод - вы увидите, что он ничего не возвращает! Как это? (тут должен быть facepalm). Как же получить результат выполнения этого метода? А результат выполнения этого метода будет получен внутри обработчика события, в момент,  когда придет ответ от сервера. Сделаем мы это следующим образом. Необходимо подключиться к этому событию - это событие описано внутри сервиса и называется GetCustomerListCompleted. Необходимо создать ему обработчик. Чтобы это сделать легко и быстро, пишем "service.GetCustomerListCompleted +=" и после того, как поставили знак "=", нажимаем дважды клавишу "Tab", после чего автоматически будет добавлен код обработки события. 

В результате у нас получится следующий код:

protected override void OnNavigatedTo(NavigationEventArgs e)

        {
            CustomerServiceClient service = new CustomerServiceClient();

            service.GetCustomerListCompleted += service_GetCustomerListCompleted;
            service.GetCustomerListAsync();
        }


        void service_GetCustomerListCompleted(object sender, GetCustomerListCompletedEventArgs e)
        {
            dataGrid1.ItemsSource = e.Result;
        }

Внутри обработчика события мы заполняем нашу таблицу данными.

Посмотри что получилось! При переходе на страницу View мы видим заполненную данными таблицу. Ура!


Считаю, что пример завершен удачно. Было рассмотрено много вопросов и достаточно интересных нюансов, как для первого опыта, думаю, нормально :)

В следующий раз поковыряем Expression Bland. Я конечно не дизайнер, но инструмент очень хороший и интересный.


Комментариев нет:

Отправить комментарий