понедельник, 29 февраля 2016 г.

GitHub

Открыл хранилище для своих небольших разработок.
https://github.com/miptleha
Буду выкладывать туда веб-компоненты, которые могут пригодится разработчикам.
Блог теперь будет посвящен веб-разработке, WPF пока больше не занимаюсь.
Первый проект, который выложил в хранилище - это автоматический переводчик для ASP.NET приложений.
Предположим, у Вас есть проект на русском языке и вам нужно сделать скриншоты на английском. Как это сделать, если изначально поддержка многих языков не закладывалась в архитектуру приложения?
Есть очень простое решение, не требующее никаких изменений в вашем сайте. Используется дополнительный обработчик перед отправкой содержимого страницы на клиент. Обработчик подменяет все найденные тексты (находит сам, но можно и самому указать) на другие.
Использовать очень просто, смотрите подробную инструкцию и исходники здесь:
https://github.com/miptleha/TranslatorFilter

четверг, 3 февраля 2011 г.

многопоточность в wpf

Как и в WinForms в WPF взаимодействовать с элементами на форме может только основной поток.
Чтобы вывести в текстбокс значение из другого потока в WinForms используется выражение:

this.Invoke((MethodInvoker)(() => { tb.Text = "новое значение"; }));


В WPF вместо метода Control.Invoke есть метод Window.Dispather.Invoke:

this.Dispatcher.Invoke((Action)(() => { tb.Text = "новое значение"; }));


Небольшое пояснение. Выражение () => { ... } означает анонимный делегат без параметров. Это тоже самое что определить где-то в коде функцию и вместо выражения указать ее имя.

Удивительно, но оказывается можно без особых усилий писать оконные приложения, в которых у каждого окна свой главный поток!
Running WPF Application with Multiple UI Threads

Немного поигрался с потоками и написал такой пример

понедельник, 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