Я что-то пропустил на шаге вверх от .net 3.5 к .net 4, потому что я вижу на первый взгляд ошибочное поведение, которое кажется противоречащим цели системы.
Я пытаюсь собрать простую библиотеку MVVM для работы, используя несколько примеров. Я использую его в клиентском приложении Twitter для дополнительного изучения и столкнулся с большим камнем преткновения.
Сценарий таков. Моему корневому объекту ViewModel (TwitterClientViewModel) предоставляется экземпляр объекта DialogViewModel для отображения. DialogViewModel добавляется в коллекцию, а для bool HasDialogs устанавливается значение true. События PropertyChanged вызываются для коллекции и флага, если это необходимо. Эта часть работает сказочно.
Представление для TwitterClientViewModel называется TwitterClientTemplate и делает Visible оверлеем для хостинга DialogViewTemplate (представление DialogViewModel). Шаблон хостинга ContentControl ссылается на DialogViewTemplate с расширением DynamicResource. Это отлично отображается в конструкторе и во время выполнения.
Здесь все становится странным. «Тело» DialogViewTemplate содержит содержимое диалога с дополнительным элементом управления содержимым, связанным с DialogViewModel.Content (тип объекта). Была надежда, что с использованием TemplateSelector (о котором я написал хороший декларативный, но закомментировал для целей тестирования), я мог отображать как текстовые, так и интерактивные элементы. Например, запрос информации от пользователя при аутентификации учетной записи Twitter. В этом случае PIN-код.
На данный момент у меня есть два вложенных элемента управления контентом для реализации диалога. В целях тестирования contentcontrol в теле DialogViewTemplate использует расширение staticresource для извлечения EnterPINDialogTemplate (представление для EnterPINDialogViewModel). И EnterPINDialogTemplate, и DialogViewTemplate находятся в одном файле (первый, конечно, определяется первым), хотя изначально они были отдельными.
Во время выполнения расширение staticresource генерирует исключение XamlParseException с сообщением;
'Предоставить значение для' System.Windows.Markup.StaticResourceHolder 'вызвала исключение.'
и внутреннее сообщение об исключении;
'Не удается найти ресурс с именем' EnterPINDialogTemplate '. Имена ресурсов чувствительны к регистру '
Использование динамического ресурса возвращает значение NULL и отображает полное имя типа EnterPINDialogViewModel в contentcontrol - как и ожидалось, когда ресурс не разрешен. Разбивая мой собственный TemplateSelector как вызов FrameWorkElement.FindResource (), выдает похожее исключение (TryFindResource возвращает ноль).
Моей первой мыслью было, что логическое дерево разбивается, когда создаются шаблоны данных, и я вспомнил проблему в этой области из более раннего проекта. Я попытался использовать свойство MergeDictionaries ResourceDictionary, чтобы сделать словари ресурсов доступными из DataTemplate, но дизайнеру не понравился этот бит, и ошибка описана здесь:
http://connect.microsoft.com/VisualStudio/feedback/details/498844/wpf-designer-throws-invalidcastexception
Поцарапайте эту идею. Я попытался объединить словари на уровнях Application, Window и TwitterClientTemplate, но безуспешно.
Ниже приведены файлы xaml.
DialogTemplates.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:VM="clr-namespace:EpicTweet.ViewModel"
xmlns:ET="clr-namespace:EpicTweet"
xmlns:T="clr-namespace:EpicTweet.Tools"
xmlns:MV="clr-namespace:MVVM;assembly=MVVM"
xmlns:Loc="clr-namespace:EpicTweet.Localization"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d">
<DataTemplate DataType="VM:EnterPINDialogViewModel" x:Key="EnterPINDialogTemplate">
<Grid d:DesignWidth="453.89" d:DesignHeight="78.92" Loc:ResXManagerProperty.ResourceManager="{x:Static ET:Language.ResourceManager}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="{Loc:ResxExtension ResourceName=String_PIN, FallbackValue='<PIN>'}"/>
<TextBox Grid.Column="1"/>
<TextBlock Grid.Row="1" Grid.RowSpan="2"></TextBlock>
</Grid>
</DataTemplate>
<DataTemplate x:Key="DialogViewTemplate" DataType="MV:DialogViewModel">
<Border BorderBrush="Black" BorderThickness="1">
<Grid d:DesignWidth="277.419" d:DesignHeight="74.96" Background="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}" Height="Auto" Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border d:LayoutOverrides="Width, Height" BorderThickness="0,0,0,1" BorderBrush="Black">
<Label Content="{Binding DisplayName, FallbackValue=Header}" VerticalAlignment="Center" HorizontalAlignment="Left"/>
</Border>
<ContentControl Content="{Binding Content, FallbackValue=Body}" ContentTemplate="{StaticResource EnterPINDialogTemplate}" HorizontalAlignment="Stretch" d:LayoutOverrides="Height" Grid.Row="1" Margin="5">
<!--<ContentControl.ContentTemplateSelector>
<T:TypeTemplateSelector>
<T:TemplateTypeRelationship Type="{x:Type VM:EnterPINDialogViewModel}" ResourceKey="EnterPINDialogTemplate"/>
</T:TypeTemplateSelector>
</ContentControl.ContentTemplateSelector>-->
</ContentControl>
<ItemsControl Grid.Row="2" Margin="10"
ItemsSource="{Binding Commands, Mode=OneTime, FallbackValue={x:Static VM:TwitterClientViewModel.DEFAULT_DIALOG_COMMANDS}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Content="{Binding DisplayName, FallbackValue=CommandName, Mode=OneWay}"
Command="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</Border>
</DataTemplate>
TwitterClientDataTemplate.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:VM="clr-namespace:EpicTweet.ViewModel"
xmlns:ET="clr-namespace:EpicTweet"
xmlns:MV="clr-namespace:MVVM;assembly=MVVM"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="DialogTemplates.xaml"/>
</ResourceDictionary.MergedDictionaries>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
<DataTemplate x:Key="TwitterClientTemplate" DataType="MV:TwitterClientViewModel">
<ScrollViewer d:DesignWidth="285.083" d:DesignHeight="119.96">
<Grid>
<StackPanel d:LayoutOverrides="Width, Height">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding AddAccountCommand.Command}" Content="{Binding AddAccountCommand.DisplayName, FallbackValue=<Add Account>}"/>
</StackPanel>
<ContentControl/>
</StackPanel>
<Border BorderThickness="1" Background="#80000000" Visibility="{Binding HasDialogs, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed, Mode=OneWay}">
<Grid VerticalAlignment="Stretch" MinWidth="50" MaxWidth="200">
<ContentControl Content="{Binding Dialogs[0], Mode=OneWay}" ContentTemplate="{DynamicResource DialogViewTemplate}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Border>
</Grid>
</ScrollViewer>
</DataTemplate>
Помоги мне переполниться стеком, ты моя единственная надежда!
РЕДАКТИРОВАТЬ: проделал дополнительную работу по этому вопросу. Если оба шаблона находятся в одном и том же файле, то динамические и статические расширения ресурсов разрешают ресурс без проблем. Если они находятся в отдельных файлах, ресурс не будет разрешен независимо от того, как я объединяю словари; каждое расширение возвращает ноль.
Очевидно, что решение состоит в том, чтобы выбросить оба ресурса в один и тот же словарь, но, насколько я понимаю, это взлом, а не предполагаемое поведение системы поиска логических ресурсов. Я не счастливый кролик прямо сейчас. Это кажется довольно недокументированным ...