Как установить DataContext для элемента <StaticResource />?
Моя ближайшая цель, учитывая FrameworkElement
который объявлен как ресурс в XAML, а затем несколько раз на него ссылаются в макете XAML (используя <StaticResource .../>
синтаксис и в объявлении ресурса x:Shared="False"
атрибут, так что каждая ссылка генерирует свой собственный экземпляр), как я могу в XAML установить DataContext
свойство для данного экземпляра?
Слияние трех факторов создало ситуацию, которую я не нашел, как исправить ситуацию, которая мне кажется вполне подходящей. Вполне возможно, что на самом деле я ошибся в фундаментальном дизайне и есть "правильный способ" сделать это.
Ситуация включает в себя эти факторы
- Желание повторно использовать
MenuItem
элемент в двух разных контекстах: как элемент в обычномMenu
закреплен в верхней части моего окна, а также как отдельный элемент в диалоговом окне. - Необходимость того, чтобы класс модели был различным в каждом из этих двух разных контекстов, из-за разницы в окружающем
DataContext
в каждом. Окно имеетDataContext
объект, который имеет свойство, ссылающееся на объект, который сам по себе имеет свойство, используемоеMenuItem
в то время как диалоговое окно просто имеетDataContext
объект со свойством, используемымMenuItem
, Чтобы еще больше сбить с толку, хотя свойство имеет одинаковое имя в обоих контекстах, на самом деле это не тот же тип модели. - Желание импортировать
MenuItem
элемент через<StaticResource/>
, Я мог бы использовать<Style/>
, с установщиками свойств для настройкиMenuItem
по желанию, но есть две проблемы с этим:
а. ОбъявлениеMenuItem
как фактическийMenuItem
объект легче написать и прочитать позже, так как не всеProperty="…"
а такжеValue="…"
атрибуты загромождают вещи.
б.Resources
свойство не является свойством зависимости и поэтому не может быть установлено в<Style/>
сеттер. Итак, используя<Style/>
настроитьMenuItem
будет исключать использование любых ресурсов, специфичных для этого элемента.
Конечно, ни одна из проблем, влияющих на третий фактор, не является фатальной. Код можно заставить работать даже при таких проблемах. Но я хочу пойти по этому пути, только если нет альтернативы. Я надеюсь, что есть чистый способ установить в XAML DataContext
свойство для элемента, который включается через <StaticResource/>
элемент.
Вот пример, который показывает, что я имею в виду:
Во-первых, модель классов:
class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateProperty<T>(
ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
if (!object.Equals(field, newValue))
{
field = newValue;
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
class ViewModelA : ViewModelBase
{
private string _value;
public string Value
{
get { return _value; }
set { _UpdateProperty(ref _value, value); }
}
}
class ViewModelB : ViewModelBase
{
private string _value;
public string Value
{
get { return _value; }
set { _UpdateProperty(ref _value, value); }
}
}
class MainViewModel : ViewModelBase
{
private ViewModelA _viewModelA;
public ViewModelA ViewModelA
{
get { return _viewModelA; }
set { _UpdateProperty(ref _viewModelA, value); }
}
}
И автономный Window
это иллюстрирует основную проблему:
<Window x:Class="TestSharedMenuItem.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:TestSharedMenuItem"
xmlns:s="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<RoutedUICommand x:Key="command1" Text="Command #1"/>
<RoutedUICommand x:Key="command2"/>
<DataTemplate x:Key="stringMenuItemTemplate">
<MenuItem Command="{StaticResource command2}">
<MenuItem.Style>
<p:Style TargetType="MenuItem">
<Setter Property="IsChecked">
<Setter.Value>
<MultiBinding Mode="OneWay">
<MultiBinding.Converter>
<l:ComparisonConverter/>
</MultiBinding.Converter>
<Binding/>
<Binding Path="DataContext.Value" Mode="OneWay"
RelativeSource="{RelativeSource AncestorType=MenuItem}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</p:Style>
</MenuItem.Style>
</MenuItem>
</DataTemplate>
<l:StringMenuItemContainerSelector x:Key="containerSelector1"/>
<CompositeCollection x:Key="menuItemsCollection1">
<CollectionContainer>
<CollectionContainer.Collection>
<x:Array Type="{x:Type s:String}">
<s:String>value #1</s:String>
<s:String>value #2</s:String>
<s:String>value #3</s:String>
</x:Array>
</CollectionContainer.Collection>
</CollectionContainer>
<Separator/>
<StaticResource ResourceKey="command1"/>
</CompositeCollection>
<MenuItem x:Key="menuItem1" x:Shared="False"
Header="Root menu item (static)"
UsesItemContainerTemplate="True"
ItemContainerTemplateSelector="{StaticResource containerSelector1}"
ItemsSource="{StaticResource menuItemsCollection1}"/>
<p:Style x:Key="menuItemStyle1" TargetType="MenuItem">
<Setter Property="Header" Value="Root menu item (style)"/>
<Setter Property="UsesItemContainerTemplate" Value="True"/>
<Setter Property="ItemContainerTemplateSelector" Value="{StaticResource containerSelector1}"/>
<Setter Property="ItemsSource" Value="{StaticResource menuItemsCollection1}"/>
</p:Style>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource command1}"
Executed="Command1Binding_Executed"/>
</Window.CommandBindings>
<StackPanel>
<StackPanel.Resources>
<p:Style TargetType="Border">
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="Padding" Value="5"/>
</p:Style>
<p:Style TargetType="Menu">
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Top"/>
</p:Style>
</StackPanel.Resources>
<Border>
<Border.CommandBindings>
<CommandBinding Command="{StaticResource command2}"
Executed="Command2MainViewModelBinding_Executed"/>
</Border.CommandBindings>
<Border.DataContext>
<l:MainViewModel x:Name="mainViewModel1">
<l:MainViewModel.ViewModelA>
<l:ViewModelA>
<l:ViewModelA.Value>initial text</l:ViewModelA.Value>
</l:ViewModelA>
</l:MainViewModel.ViewModelA>
</l:MainViewModel>
</Border.DataContext>
<StackPanel>
<Menu>
<!-- How can I set the DataContext for this StaticResource element? -->
<StaticResource ResourceKey="menuItem1"/>
</Menu>
<Menu>
<MenuItem Style="{StaticResource menuItemStyle1}"
DataContext="{Binding ViewModelA}"/>
</Menu>
<TextBlock Text="{Binding ViewModelA.Value}"/>
</StackPanel>
</Border>
<Border>
<Border.CommandBindings>
<CommandBinding Command="{StaticResource command2}"
Executed="Command2ViewModelBBinding_Executed"/>
</Border.CommandBindings>
<Border.DataContext>
<l:ViewModelB x:Name="viewModelB1">
<l:ViewModelB.Value>initial text</l:ViewModelB.Value>
</l:ViewModelB>
</Border.DataContext>
<StackPanel>
<Menu>
<StaticResource ResourceKey="menuItem1"/>
</Menu>
<Menu>
<MenuItem Style="{StaticResource menuItemStyle1}"/>
</Menu>
<TextBlock Text="{Binding Value}"/>
</StackPanel>
</Border>
</StackPanel>
</Window>
Примечание: в приведенном выше примере я показываю обе техники вместе, чтобы иметь возможность противопоставить поведение. Конструктивно (хотя, конечно, не визуально) первая коробка (т.е. в первой <Border/>
элемент) эквивалентен моему сценарию "окно с закрепленным меню", а второй эквивалентен сценарию "диалог с элементом меню".
Обратите внимание, что в первом окне, пока команды в "статическом" меню работают, проверки пунктов меню не настроены на отображение текущего состояния значения. Это связано с привязкой, которая отслеживает это состояние, не находя Value
свойство, которое устанавливается, потому что DataContext
неправильно. Во втором блоке пункт меню проверяет трек правильно, потому что окружающий DataContext
уже правильно и поэтому не нужно устанавливать явно для MenuItem
будучи включенным
Некоторые альтернативы, кроме того, как показано выше, используя <Style/>
вместо <StaticResource/>
, что я уже рассмотрел, включают в себя:
- Настройка
DataContext
изMenu
содержащий<StaticResource/>
элемент. Это работает хорошо, но мне кажется это немного хаком, так как это приводит кDataContext
для всехMenuItem
объекты в этом меню. Пока я не вижу очевидных проблем с этим, но это означает, что я не смогу повторить<StaticResource/>
Техника в одном меню для двух разныхMenuItem
элементы. Более того, кажется, что неправильно устанавливатьDataContext
в целом, когда действительно есть только один элемент, который должен использовать этот объект какDataContext
значение. - Использование шаблонов для определения
MenuItem
, При таком подходе я бы объявил новый класс модели представления специально дляMenuItem
и<DataTemplate/>
идти вместе с ним, в котором естьMenuItem
разметка хочу. Эта новая модель представления будет действовать как посредник для окружающегоDataContext
иMenuItem
шаблон. Но так какMenuItem
на самом деле необходимо для работы с двумя различными классами модели (которые имеют одинаковое имя свойства), общее свойство будет простоobject
без какой-либо проверки типа во время компиляции. Конечно, у меня уже есть такая ситуация вMenuItem
Само по себе, но я не в восторге от этой части дизайна, и кажется, что я только ухудшаю ее, распространяя ее и на модель представления.
В любом случае, я не смог понять, как заставить это работать; когда я пытался, я получал меню, которое выглядело неправильно, и не было заполнено подпунктами, которые мне нужны. Вывод отладки показывает классическое сообщение "Не удается найти управляющий FrameworkElement или FrameworkContentElement для целевого элемента", так что, похоже, что-то идет не так с моей попыткой связать с окружающимDataContext
Это свойство моего "прокси" объекта модели. - Настройка
DataContext
изMenuItem
через код позади. Это будет работать, конечно, но это будет включать объявление<StaticResource/>
элемент сx:Name
атрибут (то есть я должен создать личное поле для каждого такого импорта), или обобщение с помощью кода, который должен пройти по дереву макета, чтобы найтиMenuItem
в вопросе, а затем настройкуDataContext
соответственно. XAML уже "знает" дерево макетов, поэтому я чувствую, что если есть способ сделать это в самом XAML, это было бы гораздо предпочтительнее.
Итак: может кто-нибудь сказать мне, как я могу использовать сам XAML для установки DataContext
свойство для этого элемента, который, используя <StaticResource/>
элемент, который я включил в макет?
В качестве альтернативы (хотя и по общему признанию), если вы поймете, что я делаю, как проблему, которую лучше всего решить, используя более подходящую идиому и / или дизайн, я был бы признателен за любые идеи в этом направлении.