понедельник, 25 октября 2010 г.

Пользовательские элементы управления. WPF vs WinForms. Часть 1.

Сейчас на работе заставляют писать на WPF, но чтобы не забыть WinForms, буду рассматривать реализацию одних и тех же задач и в той, и в другой технологии.


Переопределение стандартных элементов управления

Как правило, стандартных элементов управления вполне хватает, чтобы написать приложение с UI. Если вдруг хочется сделать интерфейс нестандартным, то можно взять чужую библиотеку элементов, например DevExpress.

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


CheckBox на WinForms

Здесь один способ. Надо реализовать свой класс:

class MyCheckBox: CheckBox
{
  protected override void OnPaint(PaintEventArgs e)
  {
    // рисуем фон, текст, графику
  }
}



CheckBox на WPF

В нашем распоряжение имеется уже два способа.

Первым идет дедовский способ:

class MyCheckBox: CheckBox
{
  protected override void OnRender(DrawingContext dc)
  {
    //рисуем программно, аналогично WinForms
  }
}


Второй вариант – переопределить шаблон элемента:

<Checkbox Template="{StaticResource myCheckBox}"/>
<Controltemplate x:Key="myCheckBox" TargetType="CheckBox">
  <Border>…</Border>
  <ControlTemplate.Triggers>…</ControlTemplate.Triggers>
</ControlTemplate>




Собственно вкратце это все. Исходные коды примера.

Во второй части буду рассматривать написание составных элементов.

четверг, 10 июня 2010 г.

Эволюция языка C#

Прочитав посты из блога soumya, решил написать заметку по языку C#.

Каждые 2-3 года выходит новая версия .NET. В каждой версии придумывают новые технологии (WPF) и улучшают старые (WinForms, ASP.NET). Изменения эти значительны и требуют много времени на изучение. Синтаксис же языка C# меняется несильно.

Обычно пишут списком: что появилось в C# 2.0, 3.0 и т.д. Чтобы не копипастить материал с других сайтов, я сгруппирую его по языковым конструкциям. Примеры максимально простые, многие детали языка опущены.

Буду писать C# 1.0, при этом подразумевать реализацию этого языка в Visual Studio .NET 2003 (C# 2.0 – VS 2005, C# 3.0 - VS 2008, C# 4.0 - VS 2010).

Переменная

Именованная величина, которая может принимать различные значения.

Объявление переменной в C# 1.0:

int i = 0;

Для типов-значений в C# 2.0 можно использовать знак ‘?’:

int? i = null; //можем присвоить null

В C# 3.0 компилятор позволяет явно не указывать тип переменной, тип определяется по выражению справа от знака равенства:

var i = 0; //неявно типизированная переменная

Функция

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

Рассмотрим возможности функций в C# 1.0:

void foo(int i, ref int j, out int k, params int[] l)
{
  if (l.Length > 0)
    j = l[0];
  k = 10;
}
int j = 5;
int k;
foo(10, ref j, out k, 1, 2, 3);

Ключевые слова ref и out позволяют вернуть значение в аргументе, а с помощью params можно сделать функцию с переменным числом аргументов.

В C# 3.0 появились функции-расширения. Они объявляются в одном классе, а вызываются как методы другого класса:

static class C // класс должен быть статическим
{
  public static string foo(this int i) //статический метод и ключевое слово this
  {
    return "Мое значение: " + i;
  }
}
MessageBox.Show(10.foo()); //у типа int появилась новая функция

В C# 4.0 ввели аргументы по-умолчанию и произвольный порядок аргументов при вызове функции:

void foo(int i, int j, int k = 0) //аргументы по-умолчанию должны быть в конце
{}
foo(i: 10, j: 20); //аргументы идут не по порядку

Класс

Тип данных, характеризуемый своими функциями и переменными.

Самый простой класс в C# 1.0:

class C
{} //нет членов класса

В C# 2.0 появились статические конструкторы:

class C
{
  static C()
  {} //вызовется до первого обращения к классу
}

В C# 2.0 допускается частичное объявление классов:

partial class C
{
  int i;
}
partial class C //продолжение объявления того же самого типа
{
  int j;
}

В C# 3.0 можно создавать анонимные типы:

var c = new { I = 10, J = 20 }; //объект с двумя свойствами
MessageBox.Show(c.J.ToString()); //свойство доступно только для чтения

Делегат

Реализует механизм обратного вызова функций. Делегаты можно передавать в параметрах функций.

Объявим делегат и функцию с параметром-делегатом:

delegate int BinaryFunction(int i, int j);
void ShowBinaryFunctionInfo(BinaryFunction op, int i, int j)
{
  MessageBox.Show(op(i, j).ToString());
}

Вызов данной функции в C# 1.0:

int Sum(int i, int j)
{
  return i + j;
}
BinaryFunction op = new BinaryFunction(Sum);
ShowBinaryFunctionInfo(op, 10, 20);

В C# 2.0 появились анонимные методы, позволяющие сэкономить на объявлении функции:

ShowBinaryFunctionInfo(delegate(int i, int j) { return i + j; }, 10, 20);

В C# 3.0 ввели лямбда-выражения, упрощающие анонимные методы:

ShowBinaryFunctionInfo((i, j) => { return i + j; }, 10, 20);

Свойство

Работа с объектом, как правило, осуществляется через интерфейс. Интерфейс не может содержать переменные, вместо переменных используются свойства.

Объявим интерфейс:

interface I
{
  int Data { get; set; } //2 метода: get_Data и set_Data, они же 1 свойство
}

Реализация интерфейса в C# 1.0:

class C: I
{
  int data = 0;
  public int Data //реализовали свойство
  {
    get
    {
      return data;
    }
    set
    {
      Data = value;
    }
  }
} 
I obj = new C();
obj.Data = 10;
MessageBox.Show(obj.Data.ToString());

Благодаря автоматическим свойствам в C# 3.0 код сильно укорачивается:

class C: I
{
  public int Data {get; set;} //компилятор сам пишет нужный код
}
I obj = new C();
obj.Data = 10;
MessageBox.Show(obj.Data.ToString());

Кроме того, в C# 3.0 свойства можно инициализировать при создании объекта:

I obj1 = new C() { Data = 10 };

Обобщенное программирование

Согласно этой парадигме алгоритмы должны применяться к различным типам объектов.

Самая простая реализация этой парадигмы – использование типа object в C# 1.0:

void Swap(ref object item1, ref object item2)
{
  object tmp = item1;
  item1 = item2;
  item2 = tmp;
}
object i = 10, j = 20;
Swap(ref i, ref j);

В C# 2.0 ввели параметризованные классы и функции:

class C<T> //пример класса
{
  public T data;
}
C<int> c = new C<int>();
c.data = 10;

void Swap<T>(ref T item1, ref T item2) //пример функции
{
  T tmp = item1;
  item1 = item2;
  item2 = tmp;
}
int i = 10, j = 20;
Swap<int>(ref i, ref j);

В C# 4.0 допускается преобразование обобщенных типов:

delegate void Action<in T>(T item); //ключевое слово in
void Display(object obj)
{
  MessageBox.Show(obj.ToString());
}
class C
{ }
Action<C> display = Display; //аргумент функции object, а не C
display(new C());

Позднее связывание

На этапе компиляции может быть неизвестно точное поведение кода. В этом случае контроль корректности вызова методов осуществляется на этапе выполнения программы.

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

class C
{
  public int foo(int i)
  {
    return i + 1;
  }
}

В C# 1.0 есть библиотека System.Reflection, предоставляющая информацию о типах:

Assembly a = Assembly.GetExecutingAssembly();
object obj = a.CreateInstance("WindowsFormsApplication1.C");
MethodInfo mi = obj.GetType().GetMethod("foo");
int i = (int)mi.Invoke(obj, new object[] { 0 });

В C# 4.0 данный пример можно укоротить:

Assembly a = Assembly.GetExecutingAssembly();
dynamic obj = a.CreateInstance("WindowsFormsApplication1.C"); //динамический тип
int I = obj.foo(0);

Язык запросов

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

В C# 3.0 появился встроенный язык запросов. Пример запроса для объектов в памяти:

var i1 = from c in new int[] {1,2,3} where c > 2 select c;
MessageBox.Show(i1.First().ToString()); //выведет 3

Пост кода в блог

Нашел очень полезный преобразователь кода C# в html для блога:
http://www.manoli.net/csharpformat/

вторник, 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