пятница, 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. Код не претендует на идеальность и уверен, вы ещё сможете оптимизировать его под свои нужды.
Читать дальше......

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

Let's Start

Доброе время суток.

Меня зовут Константин.
Я начинаю блог общей программерской тематикой с уклоном на .NET, технологии WPF, а там как пойдёт. Читать дальше......