вторник, 27 апреля 2010 г.

Медленно но трудно

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

Если вырвешь волосы их не вернешь назад

Как я писал в предыдущем посте проект впф состоит из файлов 2-х видов: xml-файл с описанием интерфейса и cs-файл с исполняемым кодом. В проекте cs-файл вложен в xaml-файл. Но на жестком диске эти файлы лежат отдельно. Так вот, связи между файлами не определяются динамически, а прописаны в csproj-файле в таком вот виде:

<ItemGroup>
    <ApplicationDefinition Include="App.xaml">
        <Generator>MSBuild:Compile</Generator>
        <SubType>Designer</SubType>
    </ApplicationDefinition>
    <Compile Include="App.xaml.cs">
        <DependentUpon>App.xaml</DependentUpon>
        <SubType>Code</SubType>
    </Compile>
    <Page Include="Window1.xaml">
        <Generator>MSBuild:Compile</Generator>
        <SubType>Designer</SubType>
    </Page>
    <Compile Include="Window1.xaml.cs">
        <DependentUpon>Window1.xaml</DependentUpon>
        <SubType>Code</SubType>
    </Compile>
</ItemGroup>

Если случайно выкинуть из проекта один из файлов, то чтоб его добавить обратно придется поредактировать в блокноте csproj-файл. Из студии добавить опцию DependentUpon не представляется возможным.

Необработанные исключения

Некрасиво когда приложение завершается с сообщением: «Я совершил недопустимую операцию и закрыт операционной системой». Обработчики в каждый метод лениво ставить. Выход есть, в app.xaml прописываем:

<Application x:Class="ADODataSet2.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    DispatcherUnhandledException="HandleException"
    StartupUri="Window1.xaml">
</Application>

А в файл app.xaml.cs добавляем код обработчика:

private void HandleException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
  MessageBox.Show(e.Exception.ToString(), "Что-то случилось");
}

Впф-матрешка

Как меня учил незабвенный Вася: «Основная фишка впф – это то, что внутри любого контрола может находиться другой контрол». Например, внутри чекбокса может быть кнопка, внутри кнопки список и т.д. Можно наваять городок в табакерке.
Но описывать весь пользовательский интерфейс одним xaml-файлом неудобно. Хочется разбить описание на несколько файлов. Если окно содержит панели, то содержимое панелей можно вынести в отдельные xaml-файлы. Вот как может выглядеть содержимое Window1.xaml (главное окно программы):

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:c="clr-namespace:WpfApplication1"
    Title="Window1" Height="300" Width="300">
    <StackPanel>
        <c:UserControl1/>
        <c:UserControl2/>
    </StackPanel>
</Window>

UserControl1 и UserControl2 – это пользовательские контролы, которые мы создали нажав правую кнопку мыши на проекте и выбрав пункт Add->User Control… Вот что студия сгенерила:
UserControl.xaml:

<UserControl x:Class="WpfApplication1.UserControl1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="300" Width="300">
    <Grid>
    </Grid>
</UserControl>

UserControl.xaml.cs:

namespace WpfApplication1
{
  /// <summary>
  /// Interaction logic for UserControl1.xaml
  /// </summary>
  public partial class UserControl1 : UserControl
  {
    public UserControl1()
    {
      InitializeComponent();
    }
  }
}

Можем накидать в промежуток между <Grid> … </Grid> то, что нам нужно. Можно даже сделать класс наследником не UserControl, а другого класса, например, Button. Для этого слово ‘UserControl’ в xaml- и cs-файлах меняем на ‘Button’. Получится пользовательская кнопка.

понедельник, 5 апреля 2010 г.

Простой проект на WPF

Одно окошко — два файла

В Windows Forms проект состоит из набора форм. Каждая форма — это пара файлов: Form1.cs, Form1.Designer.cs. В Form1.Disigner.cs хранится код, созданный дизайнером форм, а в Form1.cs обработчки событий для элементов формы.
В WPF-проекте форма также хранится в двух файлах: Window1.xaml, Window1.xaml.cs. Window1.xaml создается дизайнером WPF, а в Window1.xaml.cs можем поместить cs код, относящийся к формочке. Формочки в WPF называются окнами. Поэтому в названии файла фигурирует слово 'Window'.
Обратим внимание на Window1.xaml. Это xml файл, который перед компиляцией транслируется в cs код. Почему же сразу не писать на cs? Писать можно, но дизайнер генерирует код в xaml-формате. Без дизайнера никак, иначе визуальное программирование превратится в программирование в notepad. Чтож, пускай будет xml.
Хочу продемонстрировать, что xaml-код — это на самом деле cs-код.

Конвертация xaml в cs


Создадим WPF-проект (File→New→Project…→WPF Application). Будет создан проект, в котором 2 элемента App.xaml (приложение) и Window1.xaml (единственное окно). Если нажать плюсики, то увидим, оставшиеся 2 элемента: App.xaml.cs, Window1.xaml.cs. В дизайнере открываем Window1.xaml и на окошко кидаем Label с надписью «Hello WPF». Причесываем внешний вид и получаем:
  1.  
  2. <Window x:Class="WpfApplication4.Window1"
  3.    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  4.    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  5.    Title="Window1" Height="300" Width="300">
  6.     <Grid>
  7.         <Label Name="label1" FontSize="36" VerticalAlignment="Center" HorizontalAlignment="Center">Hello WPF</Label>
  8.     </Grid>
  9. </Window>
  10.  

Запускаем приложение и видим нашу формочку. Ага, оно работает!
Теперь идем в папку obj\Debug и обнаруживаем там файл Window1.g.cs. Это и есть cs-код, построенный по xaml.
  1.  
  2. public partial class Window1 : System.Windows.Window, System.Windows.Markup.IComponentConnector {
  3.     #line 6 "..\..\Window1.xaml"
  4.     internal System.Windows.Controls.Label label1;
  5.    
  6.     #line default
  7.     #line hidden
  8.    
  9.     private bool _contentLoaded;
  10.    
  11.     /// <summary>
  12.     /// InitializeComponent
  13.     /// </summary>
  14.     [System.Diagnostics.DebuggerNonUserCodeAttribute()]
  15.     public void InitializeComponent() {
  16.         if (_contentLoaded) {
  17.             return;
  18.         }
  19.         _contentLoaded = true;
  20.         System.Uri resourceLocater = new System.Uri("/WpfApplication4;component/window1.xaml", System.UriKind.Relative);
  21.        
  22.         #line 1 "..\..\Window1.xaml"
  23.         System.Windows.Application.LoadComponent(this, resourceLocater);
  24.        
  25.         #line default
  26.         #line hidden
  27.     }
  28.    
  29.     [System.Diagnostics.DebuggerNonUserCodeAttribute()]
  30.     [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
  31.     [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
  32.     void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) {
  33.             switch (connectionId)
  34.             {
  35.             case 1:
  36.             this.label1 = ((System.Windows.Controls.Label)(target));
  37.             return;
  38.             }
  39.             this._contentLoaded = true;
  40.     }
  41. }
  42.  

Если присмотреться повнимательнее, то окажется, что код не полный. Нет установки свойств Content, Windth, Height, FontSize и др. Оказывается, что вся инициализация свойств элементов делается в строчке:
  1.  
  2. System.Windows.Application.LoadComponent(this, resourceLocater);
  3.  
Если откроем файл Window1.baml, то обнаружим в нем значения наших свойств. Понять baml-файл достаточно трудно, поскольку он бинарный.
Но тем не менее, мы убедились что xaml — это всего лишь промежуточный код, который приводится к cs-коду.

Декомпиляция xaml

С помощью .NET Reflector можно просматривать cs-код из exe-файлов. Открыв в нем наш exe-файл обнаружим выше приведенный код из Window1.g.cs. Но ресурсы, хранимые в baml-файле отображены не будут. Если установим плагинчик BamlViewer, то можем увидеть исходный xaml-файл.

______________________
Текст подготовлен в Редакторе Блогов от © SoftCoder.ru

Тест вывода кода

  1.  
  2. int i = 0;
  3. Console.WriteLine("hello");
  4.  

______________________
Текст подготовлен в Редакторе Блогов от © SoftCoder.ru