четверг, 15 октября 2009 г.

Работа с Dictionary. Обнуление ключа.

Что происходит, когда мы вызываем:
dictionary[key] = null;



Что произойдёт если ключа "key" нет в Dictionary?
Мой коллега долго искал баг в программе, потому что был уверен, что если в словаре нет ключа "key", то программа вернёт ошибку. Из-за этого он не обращал на этот вызов внимания.
А вот что происходит на самом деле:
Dictionary проверяет наличие ключа и если не находит, то _создаёт_ его! И присваивает указанное значение. В нашем случае получается пара "key; null" .

Мораль сей басни такова: внимательнее обращайтесь со свойствами, реализация которых скрыта! И с Dictionary в частности. =)

Читать дальше......

вторник, 15 сентября 2009 г.

Задача на собеседование

Задача звучит следующим образом:
Есть окошко в котором содержатся редактируемое текстовое поле и кнопка.
Внимание, вопрос:
Сколько у нас окон? Дайте ответ в контексте WinForms, Win32 и WPF. Читать дальше......

понедельник, 6 июля 2009 г.

Binding can only be set on a DependencyProperty of a DependencyObject.

Если вы когда-нибудь получали сообщение об ошибке с текстом:

A 'Binding' cannot be set on the 'Geometry' property of type 'ConnectionByLine'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.

То уже знаете, что решение проблемы элементарно до безобразия, хотя некоторые, такие как я, всё же упускают детали.
Чаще всего это возникает, когда название свойства (в примере - Geometry) не соответствует регистрируемому свойству: DependencyProperty.Register("Geometry", ...);

Обычно DependencyProperty имеет следующий вид:

public static readonly DependencyProperty GeometryProperty = DependencyProperty.Register("Geometry", typeof(PathGeometry), typeof(BaseShape));

public PathGeometry Geometry
{
get { return (PathGeometry)GetValue(GeometryProperty); }
set { SetValue(GeometryProperty, value); }
}

После этого можно производить привязку к данному свойству следующим образом в XAML-коде:

...
Label
Content="{Binding ElementName=MyLine, Path=Geometry}"
...

Но помните, если вы примените какое-нибудь послабление в именовании свойств, то получите ошибку с сообщением о том, что привязку можно производить только на зависимые свойства.
Читать дальше......

вторник, 23 июня 2009 г.

DataGrid. Неверное отображение привязанных элементов после прокрутки.

Предусловия:
WPF, DataGrid, Binding.
Вы используете DataGrid, ячейки таблицы меняют свои состояния динамически, и привязаны к неким свойствам посредством Binding.
Симптомы:
При скролировании строк DataGrid'а, состояния некоторых свойств отображаются неверно.
Решение:

Чтобы решить проблему достаточно переключить свойство VirtualizingStackPanel.VirtualizationMode на значение "Standard".
Что при этом происходит?
VirtualizingStackPanel используется, как видно из названия, для виртуализации объектов, в нашем случае, DataGrid. Т.е. когда вы смещаете видимую область таблицы, строки, которые не отображаются виртуализируются с целью экономии. Но и тут есть особенности - одно дело виртуализировать объект, другое - его состояния.
Значение свойства "Standard" отключает виртуализацию. Это приводит к тому, что все объекты таблицы хранятся в памяти целиком. Минусы: память расходуется не оптимально, плюсы: не требуется восстанавливать и виртуализировать объекты, и как следствие - работа с объектами происходит быстрее.

Listing...
DataGrid
Style="{StaticResource DataGridStyle}"
MaxWidth="500"
MaxHeight="500"
SelectionMode="Single"
SelectionUnit="FullRow"
Loaded="MainGrid_Loaded"
VirtualizingStackPanel.VirtualizationMode="Standard"
Читать дальше......

четверг, 18 июня 2009 г.

WPF. Создание UserControl со свойством типа коллекция. Некорректная работа XamlReader при восстановлении такого объекта.

Предусловия:
Вы создали свой контрол. В своём контроле создали своё DependencyProperty с объектом коллекции, например ObservableCollection<>:

Listing 1:

public static readonly DependencyProperty ElementPortsProperty = DependencyProperty.Register("CollectedProperty",
typeof(ObservableCollection<>), typeof(MyClass), new FrameworkPropertyMetadata(new ObservableCollection<>());

Проблема:
Возможна следующая ситуация когда вы восстанавливаете такой объект при помощи XamlReader: свойство-коллекция элемента содержит дублированные
элементы
.


Пояснение:
Если в контроле есть custom свойство-коллекция, которое заполнено в XAML-файле вашего контрола то в момент дессериализации будет происходить следующее:

Восстановится ваш контрол целиком (с заполненным по-умолчанию свойством-коллекцией), затем в Xaml-файле обнаружится коллекция с объектами и эти объекты добавятся в восстановленный Custom Control.

Как с этим бороться? Два очевидных способа:
1. Не заполнять в Конструкторе коллекцию, а инициализировать её заполнение из .cs-файла когда это нужно.
2. В элементы коллекции добавить уникальный ключ, который позволит проверять наличие этого элемента в коллекции.

Примеры:
Binary of uncorrect working DP
Binary of correct working DP
Sources of correct working DP

Читать дальше......

Запуск WPF-приложения с параметрами

Что нужно сделать, чтобы доступиться к входным параметрам WPF-приложения?

Классическое WinForms-приложение имеет точку входа следующего вида:

public static void Main(string[] parameters)
где parameters и есть те самые входные параметры.

Чтобы доступиться до входных параметров WPF-приложения, нужно использовать перегрузку метода:

OnStartup(StartupEventArgs e)
В общем виде ваш стартовый класс может выглядеть так:

Listing 1:
Listing...

private static string[] _arguments;

public static string[] AppArguments
{
get { return _arguments;
}


public App()
{
InitializeComponent();
}

protected override void OnStartup(StartupEventArgs e)
{
_arguments = new string[e.Args.Length];
for (int i = 0; i <>
{
_arguments[i] = e.Args[i];
}
}


Входные параметры сохраняются в статическом свойстве и будут легко доступны из любой точки приложения.

Примеры:
Source of the example "How to Run WPF Application with parameters".
Binary of the example "How to Run WPF Application with parameters".
Читать дальше......

пятница, 12 июня 2009 г.

Ошибки дессериализации (воссоздания объектов при помощи XamlReader)

Чтобы охватить большее количество ситуации, я абстрагируюсь от конкретной ситуации, в которой столкнулся с вопросом восстановления объектов в WPF.
Представим, что у вас есть разработанный вами элемент управления, он же UserControl или CustomControl.

Часть его может быть описана в XAML файле вашего класса, часть в коде на языке C# (VB).
Зачастую мы будем работать с нашими объектами и не встретимся с проблемами, но ситуации всё же возможны.

Если в процессе его использования, вы зададите ему именованный Content или измените имя существующего, то это может привести к ошибке, примерно следующего содержания:
"Cannot set Name attribute value '' on element ''. '' is under the scope of element 'ParentName', which already had a name registered when it was defined in another scope."

Ещё ситуация, когда подобное может произойти: вы анимируете объект или часть объекта. Это требует задавать имена объектам подверженным анимации, и, как следствие, десериализация (Например, Copy/Paste) такого объекта может дать туже ошибку. Яркий пример подобной ситуации - использование InkCanvas и его внутренних методов CopySelection()/Paste(), результатом которых может быть ошибка в случаях когда имя объекта изменялось.

Решение:

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

В качестве решения, я решил залезть в буфер обмена и подредактировать данные, которые там содержатся.

Listing 1:
Listing...
public static string ProcessNamedXamlInCilpboard(string mask)
{
if (string.IsNullOrEmpty(mask))
mask = "Name=\"";
string retval = "";
IDataObject provider = Clipboard.GetDataObject();
object xaml = provider.GetData("Xaml", true);
if (xaml == null)
return retval;
retval = xaml as string;

retval = ProcessRemoveMaskedXaml(retval, mask);

try
{
DataObject data = new DataObject(DataFormats.Xaml, retval);
Clipboard.SetDataObject(data, false);
}
catch (Exception E)
{
Console.WriteLine(E.Message);
}
return retval;
}

private static string ProcessRemoveMaskedXaml(string source, string mask)
{
if (!source.Contains(mask))
return source;
int startIndex = source.IndexOf(mask);
int finishIndex = startIndex;
char finish = ' ';
do
{
finish = source[finishIndex];
finishIndex++;
}
while (finish != ' ');
source = source.Remove(startIndex, finishIndex - startIndex);
source = ProcessRemoveMaskedXaml(source, mask);
return source;
}


Первый метод называется ProcessNamedXamlInCilpboard в качестве параметра принимает строку (текст, который надо найти и удалить из описания Xaml), возвращает уже обработанную строку Xaml.

Что происходит в этом методе:
Мы доступаемся до буфера обмена, получаем xaml-код серилизованного объекта, передаём xaml-код и маску непосредственно обработчику.

В методе обработчике:
Проверяем, есть ли необходимость обрабатывать xaml-код, получаем индекс начала маски в xaml-коде, в цикле получаем закрывающий индекс маски, удаляем блок текста из xaml-кода, рекурсивно проверяем наличие маски далее в xaml-коде и возвращаем обработанный код.

Область для оптимизации: поиск маски в xaml-коде.
Дело в том, что производя проверку на наличие в строке маски, мы проходим всю строку, затем ещё раз, когда получаем позицию маски в строке. И так каждый раз, а если маска встречается в конце "миллионсимвольной" строки? Поэтому попробуем сделать следующим образом:

Listing 2:
Listing...
private static string ProcessRemoveMaskedXaml(string source, string mask, int position)
{
//if (!source.Contains(mask))
// return source;
int startIndex = source.IndexOf(mask, position);
if (startIndex == -1)
return source;
int finishIndex = startIndex;
char finish = ' ';
do
{
finish = source[finishIndex];
finishIndex++;
}
while (finish != ' ');
source = source.Remove(startIndex, finishIndex - startIndex);
source = ProcessRemoveMaskedXaml(source, mask, startIndex);
return source;
}


Какие изменения были внесены в код:
Мы убрали проверку на наличие маски в строке, так как нам всё равно надо лезть в неё - будь-то проверка или поиск.
Для поиска используем индекс, откуда начинать искать - это позволит не производить поиск в той части строки, которая уже была проверена. В случае, когда ничего не найдено, мы проверим возвращаемое значение на -1 и обработаем выход из функции.
Обратите внимание на то, что изменилось количество параметров у функции, а соответственно и её вызов:
retval = ProcessRemoveMaskedXaml(retval, mask, 0);
в качестве индекса начала поиска передадим ноль.

P.S. Код не претендует на идеальность и уверен, вы ещё сможете оптимизировать его под свои нужды.
Читать дальше......