среда, 23 мая 2007 г.

Почему тупит дизайнер

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

    Экземпляра любого класса соответствующего типа (при наличии конструктора без параметров, конечно же)
    Возвращаемого значения любого статического метода (также без параметров)
    .. статического свойства
    .. статического поля

+ Создавался еще custom CollectionEditor для хранения задач.

В процессе разработки всего этого пришлось познакомиться со следующими прелестями дизайнера:

Если из UITypeEditor'а возвращается ссылка на тот же объект, он не сериализуется

Это проявилось в дизайнере коллекции, т.к. коллекция, передаваемая в редактор, только меняла свое содержимое, объект оставался тот же самый и сравнив их по ссылкам, дизайнер не сериализовал "не изменившуюся" коллекцию.
Для решения нужно использовать методы ITypeDescriptorContext.OnComponentChanging и ITypeDescriptorContext.OnComponentChanged, следующим образом:


    public class TasksEditor : UITypeEditor
{
private IWindowsFormsEditorService editorService;

public override object EditValue(ITypeDescriptorContext context,
IServiceProvider provider, object value)
{
object newValue = value;

if (context != null &&
context.Instance != null &&
provider != null)
{
editorService = (IWindowsFormsEditorService)provider.
GetService(typeof(IWindowsFormsEditorService));

if (editorService != null)
{
TasksEditorDialog d = new TasksEditorDialog();
d.Tasks = (TasksGroupsCollection)value;

context.OnComponentChanging();
if (editorService.ShowDialog(d) == DialogResult.OK)
{
newValue = d.Tasks;
context.OnComponentChanged();
}
}
}

return newValue;
}

//...
}




В статьях MSDN, посвященных UITypeEditor упоминаний этого не нашел и попался. Благо, гугл затем направил на верный путь.




Асинхронные вызовы в UITypeEditor'е


В диалоге редактора асинхронно перебирались Reflection'ом сборки. И выполнялось это, естественно, асинхронно.





Поначало с помощью BackgroundWorker'а, который ввел себя несовсем предсказуемо. В какой-то момент времени по завершении DoWork диалог просто закрывался без каких либо пояснений. К сожалению отладка дизайнера в Express-студии ограничена, а MessageBox'ами выяснилось только то, что при этом не вызывается ни OnRunWorkerCompleted, ни даже FormClosing.





ОК, с BackgroundWorker'ом нам не по пути, реализовал асинхронность вручную с помощью делегатов и BeginInvoke. Результат из метода возвращался с помощью IAsyncResult. Здесь лучше не стало, в тот момент, когда метод завершал работу, студия просто валилась с предложением др.Ватсона отправить отчет.





С чувством, что меня здесь не ждали, реализовал следующее: выбросил IAsyncResult и организовал передачу в асинхронный метод делегата EndProcessingCallback, который вызывался в конце метода. И... заработало!)




Сложности отладки designtime-компонентов


Если единожды воспользоваться в дизайнере таким компонентом и изменить его состояние, то при следующем билде компонента, и открывании дизайнера того компонента, в котором используется наш designtime-компонент, студия может заявить, что не может привести тип X к типу X. Почему?


Как выяснилось, designtime-компонент закешировался в (LocalApplicationData)\Microsoft\(Visual Studio | VCSExpress | VBExpress)\8.0\ProjectAssemblies и будет там оставаться неизменным по перезапуска студии. И никакие Cleanup'ы и Rebuild'ы здесь не помогут.




MSB4018: The "GenerateResource" task failed unexpectedly.


Также, если сильно увлечься designtime'ом можно словить и такую ошибку. Это баг в MSBuild, лечится только переустановкой .NET Framweork'а. Связана эта ошибка, как понятно, с ресурсами, у меня таким образом MSBuild не нашел понимания в находящимися там System.Reflection.Runtime[Constructor|Field|Method|Property]Info, сериализующимися наследниками System.Reflection.[Constructor|Field|Method|Property]Info, запиханными туда дизайнером студии.


И не нужно спешить добавлять в файл проекта ноду GenerateResourceNeverLockTypeAssemblies, как предложено на форуме MSDN — я добавил было — не помогло, переставил фреймворк — та же фигня. И лишь после удаления этого ключа, MSBuild дал добро.