#silverlight #xaml #silverlight-4.0 #binding
#silverlight #xaml #silverlight-4.0 #привязка
Вопрос:
У нас есть экран и его модель представления:
public class ScreenViewModel : BaseViewModel
{
[NotifyPropertyChanged]
public List<Node> Nodes { get; set; }
public ICommand NodeClickedCommand { get; set; }
public ScreenViewModel()
{
NodeClickedCommand = new RelayCommand(NodeClicked);
// ....
// Some code that binds Nodes.
// ....
}
private void NodeClicked()
{
MessageBox.Show("This is never shown");
}
}
На этой странице у нас есть пользовательский элемент управления (CustomControl) и следующий xaml для привязки команды:
<UserControl x:Class="ScreenView"
x:Name="Screen"
>
<CustomControl Nodes="{Binding Nodes}">
<CustomControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding ElementName=Screen,
Path=DataContext.NodeClickedCommand}">
<TextBlock>hello</TextBlock>
</Button>
</DataTemplate>
</CustomControl.ItemTemplate>
</CustomControl>
Наш пользовательский элемент управления SL использует приведенный выше шаблон (DataTemplate) для отображения его дочерних элементов:
foreach(Node node in Nodes)
{
FrameworkElement frameworkElement = (FrameworkElement)ItemTemplate.LoadContent();
frameworkElement.DataContext = node ;
this._canvas.Children.Add(frameworkElement);
}
Мы уверены, что:
- ViewModel правильно привязан к View
- Все узлы отображаются правильно
- Обычная привязка работает корректно
- В VS нет предупреждений о привязке
- Привязка команды работает, если мы привязываем с помощью Command=»{Привязка NodeClickedCommand}», но, конечно, это привязывается к команде, которая должна существовать на одном узле, и мы хотим привязать к команде, которая существует в модели просмотра экрана.
- Аналогичный сценарий работает со списком и ListBox.ItemTemplate
Проблема в том, что NodeClickedCommand никогда не привязывается, почему?
Ответ №1:
Я думаю, что проблема может быть в порядке генерации элементов и привязки команд. Элементы вашего пользовательского узла могут быть добавлены в дерево компоновки позже, чем привязка попытается разрешить команду, поэтому привязка в datatemplate не может перемещаться по дереву компоновки для разрешения вашего элемента.
Вы написали, что ListBox работает с этой настройкой, поэтому попробуйте немного вникнуть в нее, чтобы увидеть, в какой момент она генерирует элементы, и убедитесь, что вы следуете аналогичному шаблону.
Комментарии:
1. Я использовал reflector, чтобы посмотреть, как это делает ListBox, и я должен сказать, что через 30 минут я был полностью потерян. Похоже, они не используют DataTemplate. Метод LoadContent вообще.
2. Это метод PrepareContainerForItemOverride (см.: msdn.microsoft.com/en-us/library / … ) где и происходит все волшебство 🙂 AFAIK, вы также можете сделать element. ContentTemplate = ItemTemplate;
Ответ №2:
Это контейнер именования. Элементы управления ItemTemplates будут отображены элементом управления «позже» в визуальном цикле, поэтому область именования контейнера отличается от местоположения в UserControl. Следовательно, Screen не является допустимым элементом в области видимости.
Поскольку мы не видим внутреннюю работу вашего пользовательского элемента управления, мое лучшее решение для вас прямо сейчас — обернуть ваши узлы в отдельные модели представления и заставить эти модели представления ссылаться на NodeClickedCommand.
public class NodeViewModel : BaseViewModel
{
public Node Node { get; set; }
public ICommand NodeClickedCommand { get; set; }
}
public class ScreenViewModel : BaseViewModel
{
[NotifyPropertyChanged]
public List<NodeViewModel> Nodes { get; set; }
public ICommand NodeClickedCommand { get; set; }
public ScreenViewModel()
{
NodeClickedCommand = new RelayCommand(NodeClicked);
// ....
// Some code that binds Nodes.
// ....
// This code here whatever it does, when it gets the list of
// nodes, wrap them inside a NodeViewModel instead like this
var nvm = new NodeViewModel()
{
NodeClickedCommand = this.NodeClickedCommand,
Node = Node
};
nodes.Add(nvm);
}
private void NodeClicked()
{
MessageBox.Show("This is never shown");
}
}
Тогда ваш XAML будет выглядеть следующим образом:
<UserControl x:Class="ScreenView"
x:Name="Screen"
>
<CustomControl Nodes="{Binding Nodes}">
<CustomControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding NodeClickedCommand}">
<TextBlock>hello</TextBlock>
</Button>
</DataTemplate>
</CustomControl.ItemTemplate>
</CustomControl>
Вы все равно будете ссылаться на одну и ту же ICommand из ScreenViewModel, поэтому вы не создаете несколько экземпляров этой конкретной команды.
Комментарии:
1. Это именно то, чего я пытаюсь избежать, я не хочу иметь ICommand на уровне узла.
Ответ №3:
Похоже, что использование ContentPresenter вместо ItemTemplate.LoadContent решает эту проблему:
foreach(Node node in Nodes)
{
ContentPresenter contentPresenter = new ContentPresenter();
contentPresenter.Content = node;
contentPresenter.ContentTemplate = ItemTemplate;
this._canvas.Children.Add(contentPresenter);
// FrameworkElement frameworkElement = (FrameworkElement)ItemTemplate.LoadContent();
// frameworkElement.DataContext = node ;
// this._canvas.Children.Add(frameworkElement);
}
Спасибо Дейну, поскольку он указал мне правильное направление.