Возникла задача разместить окно приложения в правом нижнем углу.
Можно решить задачу так:
this.Left = SystemParameters.WorkArea.Width - this.Width;
this.Top = SystemParameters.WorkArea.Height - this.Height;
Если применить:
this.Top = SystemParameters.VirtualScreenHeight - this.Height;
то часть окна будет скрыта панелью задач.
понедельник, 13 июля 2009 г.
вторник, 23 июня 2009 г.
Путь к каталогу от куда запущено приложение в WPF
Каждый раз не могу вспомнить как же его получать. Запишу сюда, вдруг запомню:
Environment.CurrentDirectory
Environment.CurrentDirectory
Патерн "Фабрика объектов" на примере "Фабрики форм"
При работе над проектом "Электронный университет" давно используем "Фабрику форм", но там задачи спецефические, да и времени небыло, поэтому документация в достаточно разрозненном виде, да и отягчена работой с AzMan-ом, Web-сервисами и прочими вещами. Поэтому в данной статье попробую расказать основные идеи данного патерна. Итак, начнем.
Задача:
Необходимо реализвать приложение загружающее формы в соответствии с правами пользователя. Или, говоря простым языком, каждый пользователь после аутентификации должен получать главное меню ориентированное под его задачи. Ингода формы связанные с пунктами меню должны быть сразу загружены.
Средства достижения:
1. Reflection - используется для динамического создания объектов
2. Интерфейc IFactoryForm - для того чтобы мы спросили у созданной формы, нужен ли ей пункт меню, а также получили возможность передавать форме команды из главного приложения, а также получать команды из формы, для передачи в другие формы (во завернул) .
3. Enum содержащий все команды - для организации взаимодействия форм между собой в процессе работы.
4. Интерфейс IParam для передачи параметров команды. Кстати этот интерфейс ничего не содержит и создан только для того, чтобы при передаче параметорв команды разработчик работающий с фабрикой понимал, что он передает не просто объект, а объект параметр команды.
5. Ну и собственно сама фабрика - часть кода выполняющая работу с загружаемымми формами.
Давайте начнем в таком порядке и обсуждать.
1. Reflection - написано очень много всего хорошего, поэтому здесь я останавливаться не буду. Кто с Reflection не работал может посмотреть в msdn.
2. Как я уже сказал интерфейс используется для взаимодействия фабрики с формой. Для ростоты использования реализовывать лучше в отдельной dll. В самом простом случае должен иметь вид:

Код интерфейса будет иметь вид:
3. Перечисление Commands (см. предыдущий рисунок) будет содержать команды которыми обмениваются формы в процессе работы приложения, для начала это всего одна команда которая нам нужна для показа тестовой формы в нашем приложении.
4. Опять же, как видно из рисунка IParam ничего не содержит ;)
5. Ну а теперь перейдем к фабрике.
Фабрика будет реализованна в виде пары методов и вспомогательного события.
Итак первый метод отвечает за создание форм. В нашем проекте идентификация пользователей идет при помощи штрих-кодов с бейджиков. Поэтому главная форма приложения после запуска имеет вид:

Для получения списка сборок и форм для загрузки используется типизированный DataSet содержащий табличку вида:

Код метода:
Реализация интерфейса у тестовой формы (конечно размещеной в другой сборке, иначе фабрика теряет смысл):
Вуаля, все заработало!
Теперь задание на дом, придумать как при помощи имеющегося события NewCommand, метода DoCommand и списка List formsFromFactory реализовать взаимодействие порожденных фабрикой форм между собой, если известно что ссылок друг на друга они не имеют.
Задача:
Необходимо реализвать приложение загружающее формы в соответствии с правами пользователя. Или, говоря простым языком, каждый пользователь после аутентификации должен получать главное меню ориентированное под его задачи. Ингода формы связанные с пунктами меню должны быть сразу загружены.
Средства достижения:
1. Reflection - используется для динамического создания объектов
2. Интерфейc IFactoryForm - для того чтобы мы спросили у созданной формы, нужен ли ей пункт меню, а также получили возможность передавать форме команды из главного приложения, а также получать команды из формы, для передачи в другие формы (во завернул) .
3. Enum содержащий все команды - для организации взаимодействия форм между собой в процессе работы.
4. Интерфейс IParam для передачи параметров команды. Кстати этот интерфейс ничего не содержит и создан только для того, чтобы при передаче параметорв команды разработчик работающий с фабрикой понимал, что он передает не просто объект, а объект параметр команды.
5. Ну и собственно сама фабрика - часть кода выполняющая работу с загружаемымми формами.
Давайте начнем в таком порядке и обсуждать.
1. Reflection - написано очень много всего хорошего, поэтому здесь я останавливаться не буду. Кто с Reflection не работал может посмотреть в msdn.
2. Как я уже сказал интерфейс используется для взаимодействия фабрики с формой. Для ростоты использования реализовывать лучше в отдельной dll. В самом простом случае должен иметь вид:

Код интерфейса будет иметь вид:
public interface IFactoryForm
{
event CommandHandler NewCommand;
/// <summary>
/// Возвращает с каким пунктом меню необходимо связать форму.
/// </summary>
/// <returns>
/// Путь до пункта меню добавляется форма. Путь имеет вид:
/// Item->Item->Item
/// Если строка пустая, то пункт меню создавать не надо и форма из пункта меню не вызывается
/// </returns>
string GetMenuItem();
/// <summary>
/// Вызывается при клике пользователя на пункте меню связанном с формой
/// </summary>
void Activate(object sender, EventArgs e);
/// <summary>
/// Вызывается главной формой приложения для передачи команд в форму
/// </summary>
void DoCommand(Commands p_command, IParam p_param);
}
* This source code was highlighted with Source Code Highlighter.3. Перечисление Commands (см. предыдущий рисунок) будет содержать команды которыми обмениваются формы в процессе работы приложения, для начала это всего одна команда которая нам нужна для показа тестовой формы в нашем приложении.
4. Опять же, как видно из рисунка IParam ничего не содержит ;)
5. Ну а теперь перейдем к фабрике.
Фабрика будет реализованна в виде пары методов и вспомогательного события.
Итак первый метод отвечает за создание форм. В нашем проекте идентификация пользователей идет при помощи штрих-кодов с бейджиков. Поэтому главная форма приложения после запуска имеет вид:

Для получения списка сборок и форм для загрузки используется типизированный DataSet содержащий табличку вида:

Код метода:
/// <summary>
/// Список форм созданных при помощи фабрики
/// </summary>
List<IFactoryForm> formsFromFactory;
private void CreateForms()
{
// Показываем форму идентификации
FrmLogin login = new FrmLogin();
login.StartPosition = FormStartPosition.CenterParent;
if (login.ShowDialog() == DialogResult.OK)
{
// если штрих-корд считан, то получаем перечень форм для загрузки.
// Реализация метода может быть любая. Мы, например, берем из базы данных
FactoryFormData.FormsDataTable forms = GetFormsForPerson(new Guid(login.tbPersonId.Text));
// Строка для сбора сообщений об ошибках работы
StringBuilder errors = new StringBuilder();
// Пробегаем по всем формам и загружаем их в приложение
foreach (FactoryFormData.FormsRow item in forms)
{
// Получаем и загружаем сборку
Assembly dll = null;
try
{
dll = Assembly.LoadFile(Application.StartupPath + '\\' + item.Assembly);
}
catch{}
if (dll != null)
{
// Сборка загружена. загружаем форму
Type currentFormClass = dll.GetType(item.FormFullName);
if (currentFormClass != null)
{
// Создаем объект и приводим его к IFactoryForm
ConstructorInfo ci = currentFormClass.GetConstructor(new Type[] { });
IFactoryForm currentForm = ci.Invoke(new object[] { }) as IFactoryForm;
formsFromFactory.Add(currentForm);
((Form)currentForm).MdiParent = this;
if (currentForm != null)
{
// Собственно форма в памяти, осталось только создать меню и привязать форму к нему
string menuItemText = currentForm.GetMenuItem();
switch (menuItemText)
{
case "":
// Собственно делать ничего не нужно
break;
case "_show_":
// Форме пункт меню не нужен, форма требует немедленного показа
((Form)currentForm).Show();
break;
default:
// Создаем пункт меню
ToolStripMenuItem mi = null;
string[] path = menuItemText.Split(new string[] { "->" }, StringSplitOptions.RemoveEmptyEntries);
foreach (string currentMenuItemText in path)
{
ToolStripItemCollection itemsForCheck = null;
if (mi == null)
{
// если мы на вершине иерархии, то ищем пункт меню в главном меню
itemsForCheck = msMain.Items;
}
else
{
// а если нет, то в подпунктах текущего пункта
itemsForCheck = mi.DropDownItems;
}
ToolStripMenuItem childItem = itemsForCheck.Cast<ToolStripMenuItem>().FirstOrDefault(x => x.Text == currentMenuItemText);
if (childItem == null)
{
// элемент с таким имененм не найден, содаем
childItem = new ToolStripMenuItem(currentMenuItemText);
if (mi == null)
{
msMain.Items.Insert(1, childItem);
}
else
{
mi.DropDownItems.Add(childItem);
}
mi = childItem;
}
mi = childItem;
}
// Итак сейчас в mi должна быть ссылка на пункт меню к которому привязана форма
// Подписываем форму на клик по этому пункту меню
mi.Click += currentForm.Activate;
break;
}
}
else
{
errors.Append(string.Format("Ошибка загрузки формы: {0}. Форма не поддерживает IFactoryForm.\n", item.FormFullName));
}
}
else
{
errors.Append(string.Format("Ошибка загрузки Формы: {0}. Форма не найдена в сборке.\n", item.FormFullName));
}
}
else
{
errors.Append(string.Format("Ошибка загрузки бибилотеки: {0}. Бибилотека не найдена.\n", item.Assembly));
}
if (errors.Length != 0)
{
MessageBox.Show(errors.ToString());
}
}
}
}
* This source code was highlighted with Source Code Highlighter.Реализация интерфейса у тестовой формы (конечно размещеной в другой сборке, иначе фабрика теряет смысл):
public partial class TestForm : Form, IFactoryForm
{
public TestForm()
{
InitializeComponent();
}
#region Члены IFactoryForm
public event CommandHandler NewCommand;
public string GetMenuItem()
{
return "Тестовые формы->Первая тестовая";
}
public void Activate(object sender, EventArgs e)
{
DoCommand(Commands.ShowFirstForm, null);
}
public void DoCommand(Commands p_command, IParam p_param)
{
switch (p_command)
{
case Commands.ShowFirstForm:
this.Show();
break;
}
}
#endregion
}
* This source code was highlighted with Source Code Highlighter.Вуаля, все заработало!
Теперь задание на дом, придумать как при помощи имеющегося события NewCommand, метода DoCommand и списка List
четверг, 4 июня 2009 г.
Мысль в слух
Какая же замечательная штука LINQ и расширяющие методы.
Ну вот, например, выбор в ComboBox данных из таблицы с сортировкой:
cbItemOfExpenses.ItemsSource = _budget.ItemOfExpenses.Where(dr => dr.IsReceipts == _isReceipts).OrderBy(dr => dr.Name);
Или вот, формирование агрегатного запроса:
Ну разве не замечательно?
Ну вот, например, выбор в ComboBox данных из таблицы с сортировкой:
cbItemOfExpenses.ItemsSource = _budget.ItemOfExpenses.Where(dr => dr.IsReceipts == _isReceipts).OrderBy(dr => dr.Name);
Или вот, формирование агрегатного запроса:
var items = from item in first
group item by item.IdItemOfExpenses into gr
select
new
{
Name = _budget.ItemOfExpenses.First(dr => dr.IdItemOfExpenses == gr.Key).Name,
Receipts = gr.Sum(dr => dr.IsReceipts ? (double?)dr.Money : 0d),
Expense = gr.Sum(dr => dr.IsReceipts ? 0d : (double?)dr.Money)
};
* This source code was highlighted with Source Code Highlighter.Ну разве не замечательно?
воскресенье, 31 мая 2009 г.
четверг, 28 мая 2009 г.
Шаблоны в WPF
В основе идеи шаблонов лежит концепция отделения функциональности от внешнего вида.
Посмотрите на вот этот рисунок:

Как видно на кнопке размещается CheckBox, картинка, набор RadioButton и даже еще одна кнопка! И все это, как не странно, работает. Достигается это за счет того, что основная масса компонентов имеет свойство Content, в рамках которого мы можем создавать практически любое наполнение. Основной недостаток, мы не можем переопределить саму базовую кнопку. Например, сделать ее треугольной или круглой, хотя цвет, размер и много другое изменить достаточно легко.
Для изменения всего способа отображения и используются шаблоны.
В WPF существует несколько видов шаблонов:
1. ControlTemplate - позволяет задавать шаблон для любого визуального компонента.
2. ItemsPanelTemplate - позволяет задавать шаблон компоновки для контейнеров.
3. DataTemplate - задает шаблоны отображения данных.
4. HierarchicalDataTemplate - задает шаблоны древовидных структур.
Рассмотрим самый простой случай ControlTemplate:
Как видно мы задаем новый стиль (строка 2), который переопределяет свойство Template - как раз тот самый шаблон (строка 7 и далее). Все остальное достаточно понятно, кроме наверно строки 17 где цвет задается не как конкретный цвет (напрмер - Red), а как переход к цвету подложки - Transparent. И конечно строка 21 в которой мы задаем, что в нашем шаблоне кнопки в качестве контента должно быть то, что помещено в контент кнопки к которой применен шаблон. В строках 24 и 25 задаются тригеры для изменения цвета кнопки при наведении мышки и при нажатии.
Вот так все это выглядит в обычном режиме, при наведении мышки и нажатии.

Кстати, попробуйте сделать круглую кнопку ;)
Посмотрите на вот этот рисунок:

Как видно на кнопке размещается CheckBox, картинка, набор RadioButton и даже еще одна кнопка! И все это, как не странно, работает. Достигается это за счет того, что основная масса компонентов имеет свойство Content, в рамках которого мы можем создавать практически любое наполнение. Основной недостаток, мы не можем переопределить саму базовую кнопку. Например, сделать ее треугольной или круглой, хотя цвет, размер и много другое изменить достаточно легко.
Для изменения всего способа отображения и используются шаблоны.
В WPF существует несколько видов шаблонов:
1. ControlTemplate - позволяет задавать шаблон для любого визуального компонента.
2. ItemsPanelTemplate - позволяет задавать шаблон компоновки для контейнеров.
3. DataTemplate - задает шаблоны отображения данных.
4. HierarchicalDataTemplate - задает шаблоны древовидных структур.
Рассмотрим самый простой случай ControlTemplate:
- <Window.Resources>
- <Style TargetType="{x:Type Button}">
- <Setter Property="Background" Value="Black" />
- <Setter Property="Height" Value="40" />
- <Setter Property="Foreground" Value="White" />
- <Setter Property="Margin" Value="3" />
- <Setter Property="Template">
- <Setter.Value>
- <ControlTemplate TargetType="{x:Type Button}">
- <Grid>
- <Rectangle Name="GelBackground" RadiusX="9" RadiusY="9" Fill="Black">
- </Rectangle>
- <Rectangle Name="GelShine" Margin="2,2,2,0" VerticalAlignment="Top" RadiusX="6" RadiusY="6" Height="15px">
- <Rectangle.Fill>
- <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
- <GradientStop Offset="0" Color="#ccffffff" />
- <GradientStop Offset="1" Color="Transparent" />
- </LinearGradientBrush>
- </Rectangle.Fill>
- </Rectangle>
- <ContentPresenter Name="GelButtonContent" VerticalAlignment="Center" HorizontalAlignment="Center" Content="{TemplateBinding Content}" />
- </Grid>
- <ControlTemplate.Triggers>
- <Trigger Property="IsMouseOver" Value="True">
- <Setter Property="Rectangle.Fill" TargetName="GelBackground">
- <Setter.Value>
- <RadialGradientBrush>
- <GradientStop Offset="0" Color="Lime" />
- <GradientStop Offset="1" Color="DarkGreen" />
- </RadialGradientBrush>
- </Setter.Value>
- </Setter>
- <Setter Property="Foreground" Value="Black" />
- </Trigger>
- <Trigger Property="IsPressed" Value="True">
- <Setter Property="Rectangle.Fill" TargetName="GelBackground">
- <Setter.Value>
- <RadialGradientBrush>
- <GradientStop Offset="0" Color="#ffcc00" />
- <GradientStop Offset="1" Color="#cc9900" />
- </RadialGradientBrush>
- </Setter.Value>
- </Setter>
- </Trigger>
- </ControlTemplate.Triggers>
- </ControlTemplate>
- </Setter.Value>
- </Setter>
- </Style>
- </Window.Resources>
- <Grid>
- <Button Width="124">Кнопка с шаблоном</Button>
- </Grid>
* This source code was highlighted with Source Code Highlighter.Как видно мы задаем новый стиль (строка 2), который переопределяет свойство Template - как раз тот самый шаблон (строка 7 и далее). Все остальное достаточно понятно, кроме наверно строки 17 где цвет задается не как конкретный цвет (напрмер - Red), а как переход к цвету подложки - Transparent. И конечно строка 21 в которой мы задаем, что в нашем шаблоне кнопки в качестве контента должно быть то, что помещено в контент кнопки к которой применен шаблон. В строках 24 и 25 задаются тригеры для изменения цвета кнопки при наведении мышки и при нажатии.
Вот так все это выглядит в обычном режиме, при наведении мышки и нажатии.

Кстати, попробуйте сделать круглую кнопку ;)
вторник, 26 мая 2009 г.
Взаимодействие WF с программным окружением
Продолжим разработку нашего примера с workflow. В данной части мы посмотрим как из WF взаимодествовать с программным окружением. В качестве конечного автомата возьмем конечный автомат от сюда.
В данном примере, для наглядности, давайте создадим приложение с GUI:

Кнопки соответствуют событиям, прямоугольники показывают состояние двери и замка (красный - закрыто, зеленый открыто).
Для взаимодействия с программным окружением используются следующие Activity:
1. СallExternalMethodActivity – используется для вызова метода внешнего по отношению к WF (основные свойства InterfaceType и MethodName)

2. EventDrivenActivity – используется для перехвата событий из кода внешнего по отношению к WF
3. HandleExternalEventActivity – помещается в EventDrivenActivity и ожидает события (основные свойства InterfaceType и EventName)

К событиям передаваемым из внешнего окружения в WF предъявляются следующие требования:
1. Событие должно быть потомок EventHandler
2. В качестве T должен быть любой класс потомок ExternalDataEventArgs
Кстати, конструктор ExternalDataEventArgs принимает Guid потока которому предназначается сообщение.
Для взаимодействия с WF объекты должны являться, во-первых, наследниками интерфейса помеченного атрибутом [ExternalDataExchange].
Введем два интерфейса:
Во-вторых, если ссылка на вызывающий событие объект будет передаваться в WF объект должен быть сериализуемым.
Кстати, если методо вызываемый из WF возвращает значение, то его можно непосредственно в дизайнере привязать к полю WF, использовав для этгого свойство ReturnValue:

Добавив везде где это надо вместо CodeActivity вызов внешних методов и перехват внешних событий, получим конечный автомат вида:

Реализация интерфейса IDoor может иметь вид:
Ну и в завершении как же создать host для WF. На самом деле все достаточно просто:
В строках 7-13 мы создаем среду выполнения WF и подписываемся на основные ее обработчики.
В строках 14-16 создаем сервис для взаимодействия с WF, регистрируем его в среде выполнения, и добавляем объект методы которого необходимо вызывать и события обрабатывать (в данном случае это текущая форма).
В строках 18,20 создается WF и запускается на выполнение.
В строке 19 получаем ссылку на WF в виде StateMachine которую можно использовать для проверки текущего состояния конечного автоамта, принудительного перевода из состояния в состояние и т.д.
В данном примере, для наглядности, давайте создадим приложение с GUI:

Кнопки соответствуют событиям, прямоугольники показывают состояние двери и замка (красный - закрыто, зеленый открыто).
Для взаимодействия с программным окружением используются следующие Activity:
1. СallExternalMethodActivity – используется для вызова метода внешнего по отношению к WF (основные свойства InterfaceType и MethodName)

2. EventDrivenActivity – используется для перехвата событий из кода внешнего по отношению к WF
3. HandleExternalEventActivity – помещается в EventDrivenActivity и ожидает события (основные свойства InterfaceType и EventName)

К событиям передаваемым из внешнего окружения в WF предъявляются следующие требования:
1. Событие должно быть потомок EventHandler
2. В качестве T должен быть любой класс потомок ExternalDataEventArgs
Кстати, конструктор ExternalDataEventArgs принимает Guid потока которому предназначается сообщение.
Для взаимодействия с WF объекты должны являться, во-первых, наследниками интерфейса помеченного атрибутом [ExternalDataExchange].
Введем два интерфейса:
[ExternalDataExchange]
public interface IDoor
{
void OpenLock();
void OpenDoor();
void CloseDoor();
string GetKey();
}
[ExternalDataExchange]
public interface IVisitor
{
event EventHandler<ExternalDataEventArgs> TestKey;
event EventHandler<ExternalDataEventArgs> Open;
event EventHandler<ExternalDataEventArgs> Close;
}
* This source code was highlighted with Source Code Highlighter.Во-вторых, если ссылка на вызывающий событие объект будет передаваться в WF объект должен быть сериализуемым.
Кстати, если методо вызываемый из WF возвращает значение, то его можно непосредственно в дизайнере привязать к полю WF, использовав для этгого свойство ReturnValue:

Добавив везде где это надо вместо CodeActivity вызов внешних методов и перехват внешних событий, получим конечный автомат вида:

Реализация интерфейса IDoor может иметь вид:
#region IDoor Members
public void OpenLock()
{
if (Dispatcher.Thread != Thread.CurrentThread)
{
Dispatcher.Invoke(new NoParamHandler(OpenLock), new object[] { });
}
else
{
rcDoorlock.Fill = new SolidColorBrush(Color.FromRgb(0, 255, 0));
}
}
public void OpenDoor()
{
if (Dispatcher.Thread != Thread.CurrentThread)
{
Dispatcher.Invoke(new NoParamHandler(OpenDoor), new object[] { });
}
else
{
rcDoor.Fill = new SolidColorBrush(Color.FromRgb(0, 255, 0));
}
}
private delegate void NoParamHandler();
public void CloseDoor()
{
if (Dispatcher.Thread != Thread.CurrentThread)
{
Dispatcher.Invoke(new NoParamHandler(CloseDoor), new object[] { });
}
else
{
rcDoorlock.Fill = new SolidColorBrush(Color.FromRgb(255, 0, 0));
rcDoor.Fill = new SolidColorBrush(Color.FromRgb(255, 0, 0));
}
}
public string GetKey()
{
if (Dispatcher.Thread == Thread.CurrentThread)
{
return tbKey.Text;
}
else
{
return (string)Dispatcher.Invoke(new Func<string>(GetKey), new object[] { });
}
}
#endregion
* This source code was highlighted with Source Code Highlighter.Ну и в завершении как же создать host для WF. На самом деле все достаточно просто:
- WorkflowRuntime workflowRuntime;
- WorkflowInstance instance;
- StateMachineWorkflowInstance machine;
-
- private void Window_Loaded(object sender, RoutedEventArgs e)
- {
- workflowRuntime = new WorkflowRuntime();
- workflowRuntime.WorkflowStarted += delegate(object s, WorkflowEventArgs e1) { MessageBox.Show("Рабочий поток начал работу"); };
- workflowRuntime.WorkflowCompleted += delegate(object s, WorkflowCompletedEventArgs e1) { MessageBox.Show("Рабочий поток закончил работу"); };
- workflowRuntime.WorkflowTerminated += delegate(object s, WorkflowTerminatedEventArgs e1)
- {
- MessageBox.Show(e1.Exception.Message);
- };
- ExternalDataExchangeService externalDataExchangeService = new ExternalDataExchangeService();
- workflowRuntime.AddService(externalDataExchangeService);
- externalDataExchangeService.AddService(this);
-
- instance = workflowRuntime.CreateWorkflow(typeof(Workflow1));
- machine = new StateMachineWorkflowInstance(workflowRuntime, instance.InstanceId);
- instance.Start();
-
- }
* This source code was highlighted with Source Code Highlighter.В строках 7-13 мы создаем среду выполнения WF и подписываемся на основные ее обработчики.
В строках 14-16 создаем сервис для взаимодействия с WF, регистрируем его в среде выполнения, и добавляем объект методы которого необходимо вызывать и события обрабатывать (в данном случае это текущая форма).
В строках 18,20 создается WF и запускается на выполнение.
В строке 19 получаем ссылку на WF в виде StateMachine которую можно использовать для проверки текущего состояния конечного автоамта, принудительного перевода из состояния в состояние и т.д.
Подписаться на:
Сообщения (Atom)