WPF: TreeViewItem bound to an ICommand - c#

I am busy creating my first MVVM application in WPF.
Basically the problem I am having is that I have a TreeView (System.Windows.Controls.TreeView) which I have placed on my WPF Window, I have decide that I will bind to a ReadOnlyCollection of CommandViewModel items, and these items consist of a DisplayString, Tag and a RelayCommand.
Now in the XAML, I have my TreeView and I have successfully bound my ReadOnlyCollection to this. I can view this and everything looks fine in the UI.
The issue now is that I need to bind the RelayCommand to the Command of the TreeViewItem, however from what I can see the TreeViewItem doesn't have a Command. Does this force me to do it in the IsSelected property or even in the Code behind TreeView_SelectedItemChanged method or is there a way to do this magically in WPF?
This is the code I have:
<TreeView BorderBrush="{x:Null}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TreeView.Items>
<TreeViewItem
Header="New Commands"
ItemsSource="{Binding Commands}"
DisplayMemberPath="DisplayName"
IsExpanded="True">
</TreeViewItem>
</TreeView.Items>
and ideally I would love to just go:
<TreeView BorderBrush="{x:Null}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TreeView.Items>
<TreeViewItem
Header="New Trade"
ItemsSource="{Binding Commands}"
DisplayMemberPath="DisplayName"
IsExpanded="True"
Command="{Binding Path=Command}">
</TreeViewItem>
</TreeView.Items>
Does someone have a solution that allows me to use the RelayCommand infrastructure I have.
Thanks guys, much appreciated!
Richard

I know this was "answered" a while ago, but since the answers weren't ideal, I figured I'd put in my two cents. I use a method that allows me to not have to resort to any "styled button trickery" or even using code-behind and instead keeps all my separation in MVVM. In your TreeView add the following xaml:
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding TreeviewSelectedItemChanged}" CommandParameter="{Binding ElementName=treeView, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In your xaml header add:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
and then you'll have to add a reference to the above assembly in your project.
After that, everything acts just the same as any other command would on say a button or something.

Thanks for the input into the issue, and yes, I did say I didn't want a Code behind solution, however at that time I was still very much under the impression that I was simply missing something... so I ended up using the TreeView_SelectedItemChanged event.
Even though Will's approach seems like a good work around, for my personal situation I decided that I would use the code behind. The reason for this is so that the View and XAML would remain as it would be if the TreeViewItem had a "Command" property to which my Command could be bound. Now I do not have to change the Templates or the Views, all I have to do is add the code and the Event for the TreeView_SelectedItemChanged.
My solution:
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (sender != null)
{
var treeView = sender as TreeView;
if (treeView != null)
{
var commandViewModel = treeView.SelectedItem as CommandViewModel;
if (commandViewModel != null)
{
var mi = commandViewModel.Command.GetType().GetMethod("Execute");
mi.Invoke(commandViewModel.Command, new Object[] {null});
}
}
}
}
As I already have the RelayCommand attached to the TreeViewItem, all I am now doing is to just manually invoke the "Execute" method on that specific RelayCommand.
If this is the completely wrong way of going about it then please let me know...
Thanks!

What I'd do is set the Header of the TreeViewItem to be a button, then skin the button so that it doesn't look or act like one, then perform my command binding against the button.
You might need to do this via a DataTemplate, or you might need to change the template of the TreeViewItem itself. Never done it, but this is how I've done similar things (such as tab page headers).
Here's an example of what I'm talking about (you can drop this in Kaxaml and play around with it):
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Style x:Key="ClearButan" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Name="border"
Padding="4"
Background="transparent">
<Grid >
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center">
</ContentPresenter>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
<Grid>
<TreeView>
<TreeViewItem>
<Button Style="{StaticResource ClearButan}">
easy peasy
</Button>
</TreeViewItem>
</TreeView>
</Grid>
</Page>
I've created a new clear style for a button. I then just drop a button in the TVI and set its style. You can do the same thing using data templates, of course.

This is a good example of how the MVVM is very much an after-thought in WPF. You expect there to be Command support of certain gui items, but there isn't, so you're forced to go through an elaborate process (as shown in Will's example) just to get a command attached to something.
Let's hope they address this in WPF 2.0 :-)

I improve good solution from Richard via common Tag property:
MyView.xaml:
<TreeView SelectedItemChanged="TreeView_SelectedItemChanged" Tag="{Binding SelectTreeViewCommand}" >
<TreeViewItem Header="Item1" IsExpanded="True" Tag="Item1" />
<TreeViewItem Header="Item2" IsExpanded="True">
<TreeViewItem Header="Item21" Tag="Item21"/>
</TreeViewItem>
</TreeView>
MyView.xaml.cs
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var treeView = (TreeView)sender;
var command = (ICommand)treeView.Tag;
TreeViewItem selectedItem = (TreeViewItem)treeView.SelectedItem;
if (selectedItem.Tag != null)
{
command.Execute(selectedItem.Tag);
}
}
MyViewModel.cs
public RelayCommand selectTreeViewCommand;
[Bindable(true)]
public RelayCommand SelectTreeViewCommand => selectTreeViewCommand ?? (selectTreeViewCommand = new RelayCommand(CanSelectTreeViewCommand, ExecuteSelectTreeViewCommand));
private void ExecuteSelectTreeViewCommand(object obj)
{
Console.WriteLine(obj);
}
private bool CanSelectTreeViewCommand(object obj)
{
return true;
}

The answer provided by Shaggy13spe is very good. But still, it took me some additional time to understand it so I will extend the answer.
Whole TreeView xaml can look like this:
<TreeView x:Name="treeView" Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Tree}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding FilterMeetingsCommand}" CommandParameter="{Binding ElementName=treeView, Path=SelectedItem}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Nodes}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"></TextBlock>
<TextBlock Text="{Binding Id}"></TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
In my View I have a Tree collection
public ObservableCollection<TreeNode> Tree { get; set; }
TreeNode is defined as a simple class:
public class TreeNode
{
public int Id { get; set; }
public string Name { get; set; }
public List<TreeNode> Nodes { get; set; }
public TreeNode(string name)
{
this.Name = name;
this.Nodes = new List<TreeNode>();
}
}
First important point: CommandParameter is not bind to the property on the ViewModel but it is passed to the method. So the method should look like:
private async void FilterMeeting(object parameter){}
Second important point: if you will pass the selected item (in my case object will be TreeNode type) and you will have the hierarchical structure you will face event bubbling. So selecting an item will fire the event for this particular item and for all parents. To resolve this you need to understand that you can pass only one object to the method in ViewModel (not two as in standard event handler) and this object needs to be an event.
In this case change the XAML to following (PassEventArgsToCommand="True" is important here)
<TreeView x:Name="treeView" Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Tree}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding FilterMeetingsCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Nodes}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"></TextBlock>
<TextBlock Text="{Binding Id}"></TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Then in your handling method, you won't receive the model object, but event args, which have a model object inside.

Related

C# WPF Caliburn Micro TreeViewItem.Expanded Event Not Firing

I'm using Caliburn Micro Message.Attach through XAML to try and bind Events to a View Model, but I cannot get the TreeViewItem.Expanded Event to fire. Other events like SetSelectedItem work fine.
I found another question on this on SO here but it was not helpful in my case as no context for the response was provided.
The only other information I can find is the following GitHub issue.
Internally Caliburn.Micro turns
<Button cm:Message.Attach="[Event Click] = [Action Test]" />
into
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cm:ActionMessage MethodName="Test" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
As you noted, EventTrigger doesn't support attached events. A quick look around brought up How to attached an MVVM EventToCommand to an Attached event which shows how to create a RoutedEventTrigger that you could plug into the full syntax.
Again, I tried this approach, but don't fully understand how to implement this. It fires the event in the custom class, but never gets passed on to my handler in the View Model.
Here is my XAML (without the GitHub suggestion):
<TreeView x:Name="FolderView"
cal:Message.Attach="[Event TreeViewItem.Expanded] = [Action Expanded($this)];
[Event SelectedItemChanged] = [Action SetSelectedItem($this.SelectedItem)]">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type models:LogicalDriveItem}"
ItemsSource="{Binding Directories}" >
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{Binding Path=DriveLetter}"></TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type models:DirectoryItem}"
ItemsSource="{Binding Directories}">
<TextBlock VerticalAlignment="Center" Text="{Binding Path=Path}"></TextBlock>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
And my view model code:
public void Expanded(object sender, RoutedEventArgs e)
{
// This won't fire
}
public void Expanded(object sender)
{
// Or this
}
public void SetSelectedItem(object sender)
{
// But this will
}
The link provided by mm8 resolved my issue.
The OP in that question is using the same RoutedEventTrigger helper class that I found on GitHub, but the additional context provided by their answer was helpful. Using the RoutedEventTrigger helper class, I updated my XAML to the following:
<i:Interaction.Triggers>
<!--in the routed event property you need to put the full name space and event name-->
<helpers:RoutedEventTrigger RoutedEvent="TreeViewItem.Expanded">
<cal:ActionMessage MethodName="Expanded">
<cal:Parameter Value="$eventArgs" />
</cal:ActionMessage>
</helpers:RoutedEventTrigger>
</i:Interaction.Triggers>
which now successfully fires my event in the ViewModel code.
Note that $this did not work for me because the data item in my case is a string. In my case, it's a File Explorer style Tree View. For context, here is the full XAML:
<TreeView x:Name="FolderView">
<i:Interaction.Triggers>
<!--in the routed event property you need to put the full name space and event name-->
<helpers:RoutedEventTrigger RoutedEvent="TreeViewItem.Expanded">
<cal:ActionMessage MethodName="Expanded">
<cal:Parameter Value="$eventArgs" />
</cal:ActionMessage>
</helpers:RoutedEventTrigger>
</i:Interaction.Triggers>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type models:LogicalDriveItem}"
ItemsSource="{Binding Directories}" >
<StackPanel Orientation="Horizontal">
<!--<Image MaxWidth="20" Source="Images/Image.png"/>-->
<TextBlock VerticalAlignment="Center" Text="{Binding Path=DriveLetter}"></TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type models:DirectoryItem}"
ItemsSource="{Binding Directories}">
<TextBlock VerticalAlignment="Center" Text="{Binding Path=Name}"></TextBlock>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>

ListView item click event MVVM

I have a MenuItem with ListView inside. What I want is when I click on a ListView item, some command fires. Here is my code:
<MenuItem Header="?">
<ListView ItemsSource="{Binding CommentTemplateList}" BorderThickness="0" SelectedItem="{Binding SelectedCommentTemplate, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding PasteTemplate}"
CommandParameter="{Binding SelectedCommentTemplate}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Caption}" ToolTip="{Binding Description}" HorizontalAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</MenuItem>
Everything is ok, but command PasteTemplate fires only when selection is changed, and I need to it fire every time I click on the item. If I change EventName to one from the list (https://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.selector.aspx), for example MouseDown, the command does not fire at all.
To accomplish this, while respecting the MVVM architecture, the best way is to add the specific behavior to your xaml code as follows;
<ListView x:Name="ListView"
ItemsSource="{x:Bind ViewModel.SampleItems, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.SelectedItem, Mode=OneWay}"
IsItemClickEnabled="True">
<i:Interaction.Behaviors>
<ic:EventTriggerBehavior EventName="ItemClick">
<ic:InvokeCommandAction Command="{x:Bind ViewModel.ItemClickCommand}" />
</ic:EventTriggerBehavior>
</i:Interaction.Behaviors>
</ListView>
And in your View Model, after declaring an IComand property as follows,
public ICommand ItemClickCommand
{
get
{
if (_itemClickCommand == null)
{
_itemClickCommand = new RelayCommand<ItemClickEventArgs>(OnItemClick);
}
return _itemClickCommand;
}
}
Define the command as if you were handling the event in the code behind as follows;
private void OnItemClick(ItemClickEventArgs args)
{
ListDataItem item = args?.ClickedItem as ListDataItem;
//DO what ever you want with the Item you selected in the click
}
Note: RelayCommand is used to handled commands using the MVVMLight Framework.
You could handle the PreviewMouseDown event of the ListViewItem as suggested here:
WPF MVVM Light Multiple ListBoxItems bound to same object
<ListView ...>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="OnMouseLeftButtonDown"/>
</Style>
</ListView.ItemContainerStyle>
..
</ListView>
If you don't want to invoke the command of the view model from the code-behind you could wrap the same functionality in an attached behaviour: https://www.codeproject.com/articles/28959/introduction-to-attached-behaviors-in-wpf.
There is more information an example on the link above.
If you wanna use 'SelectionChanged', You can reset the selection after your code. Just add that on your PasteTemplate
if(((ListView)sender).SelectedIndex == -1)return;
//your code
((ListView)sender).SelectedIndex = -1;
So, after your code, ListView has no selected elements. So if you click it again, the selection is changed again and code fires again.
Note: you can use MouseDown for it too, but it's a little tricky. For example, if user clicks none of your items but somewhere else inside your ListView like this, it fires again with your current selection.

UWP Databinding: How to set button command to parent DataContext inside DataTemplate

Short explanation of need: I need to fire the command of a button inside a DataTemplate, using a method from the DataContext of the ViewModel.
Short explanation of problem: The templated button command only seems to be bindable to the datacontext of the item itself. The syntax used by WPF and Windows 8.1 apps to walk up the visual tree doesn't seem to work, including ElementName and Ancestor binding. I would very much prefer not to have my button command located inside the MODEL.
Side Note: This is built with the MVVM design method.
The below code generates the list of items on the VIEW. That list is one button for each list item.
<ItemsControl x:Name="listView" Tag="listOfStories" Grid.Row="0" Grid.Column="1"
ItemsSource="{x:Bind ViewModel.ListOfStories}"
ItemTemplate="{StaticResource storyTemplate}"
Background="Transparent"
IsRightTapEnabled="False"
IsHoldingEnabled="False"
IsDoubleTapEnabled="False"
/>
Inside the page resources of the same VIEW, I have created a DataTemplate, containing the problematic button in question. I went ahead and stripped out most of the formatting inside the button, such as text, to make the code easier to read on this side. Everything concerning the button works, except for the problem listed, which is the binding of the command.
<Page.Resources>
<DataTemplate x:Name="storyTemplate" x:DataType="m:Story">
<Button
Margin="0,6,0,0"
Width="{Binding ColumnDefinitions[1].ActualWidth, ElementName=storyGrid, Mode=OneWay}"
HorizontalContentAlignment="Stretch"
CommandParameter="{Binding DataContext, ElementName=Page}"
Command="{Binding Source={StaticResource Locator}}">
<StackPanel HorizontalAlignment="Stretch" >
<TextBlock Text="{x:Bind StoryTitle, Mode=OneWay}"
FontSize="30"
TextTrimming="WordEllipsis"
TextAlignment="Left"/>
</StackPanel>
</Button>
</DataTemplate>
</Page.Resources>
Because this is a DataTemplate, the DataContext has been set to the individual items that comprise the list (MODEL). What I need to do is select the DataContext of the list itself (VIEWMODEL), so I can then access a navigation command.
If you are interested in the code-behind of the VIEW page, please see below.
public sealed partial class ChooseStoryToPlay_View : Page
{
public ChooseStoryToPlay_View()
{
this.InitializeComponent();
this.DataContextChanged += (s, e) => { ViewModel = DataContext as ChooseStoryToPlay_ViewModel; };
}
public ChooseStoryToPlay_ViewModel ViewModel { get; set; }
}
I've tried setting it by ElementName, among many other attempts, but all have failed. Intellisense detects "storyTemplate" as an option when ElementName is input, which is the name of the DataTemplate shown in the first code block of this question.
I don't believe my problem can be unique, however I'm having great difficulty finding a solution for UWP. Allow me to apologize in advance in this is a simple question, but I've spent nearly two days researching answers, with none seeming to work for UWP.
Thank you guys!
What MVVM toolkit are you using (if any)? In MVVM Light, you can get a hold of ViewModel from DataTemplate same way you set DataContext for your view:
<DataTemplate x:Key="SomeTemplate">
<Button Command="{Binding Main.MyCommand, Source={StaticResource ViewModelLocator}}"/>
</DataTemplate>
It really is unfortunate that there is no ancestor binding in UWP. This makes scenarios like yours much more difficult to implement.
The only way I can think of is to create a DependencyProperty for ViewModel on your Page:
public ChooseStoryToPlay_ViewModel ViewModel
{
get { return (ChooseStoryToPlay_ViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(ChooseStoryToPlay_ViewModel), typeof(MainPage), new PropertyMetadata(0));
Now you can bind to it from your data template:
<DataTemplate x:Name="storyTemplate" x:DataType="local:Story">
<Button
Margin="0,6,0,0"
Width="{Binding ColumnDefinitions[1].ActualWidth, ElementName=storyGrid, Mode=OneWay}"
HorizontalContentAlignment="Stretch"
CommandParameter="{x:Bind Page}"
Command="{Binding ViewModel.NavigateCommand, ElementName=Page}">
<StackPanel HorizontalAlignment="Stretch" >
<TextBlock Text="{x:Bind StoryTitle, Mode=OneWay}"
FontSize="30"
TextTrimming="WordEllipsis"
TextAlignment="Left"/>
</StackPanel>
</Button>
</DataTemplate>
A couple of things to notice:
In CommandParameter I assumed that in your Story class there is a Page property that you want to pass as a parameter to your command. You can bind to any other property of Story class here or the class itself.
You have to set the name of your page to Page (x:name="Page"), so that you can reference it using ElementName in the data template.
I assumed that the command you're calling on the ViewModel is named NavigateCommand and accepts a parameter of the same type as the property bound to CommandParameter:
public ICommand NavigateCommand { get; } =
new RelayCommand<string>(name => Debug.WriteLine(name));
I hope this helps and is applicable to your scenario.
There is a few ways to do that. But i think the Command change better...
Example, you have a (grid,list)view with some itemtemplate like that:
<GridView.ItemTemplate>
<DataTemplate>
<Grid
x:Name="gdVehicleImage"
Height="140"
Width="140"
Background="Gray"
Margin="2"
>
</Grid>
</GridView.ItemTemplate>
And do you want to make a command to for example a FlyoutMenu... But the command it's in the ViewModel and not in GridView.SelectedItem...
What you can do is...
<Grid
x:Name="gdVehicleImage"
Height="140"
Width="140"
Background="Gray"
Margin="2"
>
<FlyoutBase.AttachedFlyout>
<MenuFlyout
Opened="MenuFlyout_Opened"
Closed="MenuFlyout_Closed"
>
<MenuFlyout.MenuFlyoutPresenterStyle>
<Style TargetType="MenuFlyoutPresenter">
<Setter Property="Background" Value="DarkCyan"/>
<Setter Property="Foreground" Value="White"/>
</Style>
</MenuFlyout.MenuFlyoutPresenterStyle>
<MenuFlyoutItem
Loaded="mfiSetAsDefaultPic_Loaded"
CommandParameter="{Binding}"
/>
<MenuFlyoutItem
Loaded="mfiDeletePic_Loaded"
CommandParameter="{Binding}"
/>
</MenuFlyout>
</FlyoutBase.AttachedFlyout>
</Grid>
And in the loaded events:
private void mfiDeletePic_Loaded(object sender, RoutedEventArgs e)
{
var m = (MenuFlyoutItem)sender;
if (m != null)
{
m.Command = Vm.DeleteImageCommand;
//Vm is the ViewModel instance...
}
}
Is not entirely beautiful... But you willnot breake mvvm pattern like this...

Binding a Button to a RelayCommand within an ItemsControl container

In a WPF Application, I have a ViewModel that is exposing a collection of strings that I'm displaying as buttons through an ItemsControl container using a WrapPanel. I haven't been able to bind the RelayCommand in my ViewModel to the buttons, however.
ViewModel (IncidentAddressesViewModel):
public IEnumerable<string> Addresses { get; set; }
public RelayCommand<string> ZoomToAddressCommand { get {
if (this.zoomToAddressCommand == null) this.zoomToAddressComamnd = new RelayCommand<string>(this.ZoomToAddress);
return this.zoomToAddressCommand;
}}
private void ZoomToAddress(string address) { MessageBox.Show (address); }
XAML:
<TabItem x:Name="IncidentAddressesTab">
<ItemsControl ItemsSource="{Binding Addresses}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Command">
<cmd:EventToCommand
Command="{Binding ZoomToAddressCommand}"
CommandParameter="{Binding Text}"
PassEventArgsToCommand="True"
/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</TabItem>
Xaml Code Behind that connects the DataContext
IncidentAddressesTab.DataContext = new IncidentAddressesViewModel();
The buttons are showing with the addresses. When I set a breakpoint at the ZoomToAddressCommand, it does get hit once, but when I click the buttons, the ZoomToAddress method never gets invoked.
UPDATE to include binding details:
I am actually binding to the TabItem. I have updated the XAML to include the additional tag and added the binding code in the XAML Code Behind. I didn't know this was pertinent information or I would have added it in the beginning.. (:
It doesn't work because you have tried to Bind the Command to the ItemsControl and not the Button controls. Have you tried this?:
<DataTemplate>
<Button Content="{Binding}" Command="{Binding DataContext.ZoomToAddressCommand,
RelativeSource={RelativeSource AncestorType={x:Type
YourViewNamespace:YourViewName}}}" />
</DataTemplate>
What we're attempting to do here is to Bind from the DataTemplate to the view model that I am assuming is set as the DataContext of the current view. Please replace "YourViewNamespace:YourViewName" with the actual names of your XML namespace and view.
UPDATE >>>
Ok, after looking at your code again, I can see that you are Binding to the Addresses collection simply using the property name. You say that the DataContext is set on the ItemsControl, so I'm assuming that by that you mean that your view model is set on the ItemsControl.DataContext property. If that is so, then we need to change our Binding to the Command like this:
<DataTemplate>
<Button Content="{Binding}" Command="{Binding DataContext.ZoomToAddressCommand,
RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
</DataTemplate>
If your view model is not set on the ItemsControl.DataContext property, then this will not work and you will need to clearly tell me how you have connected your view model to your view. Before I just assumed that your view model was data bound to the DataContext of the containing view or Window as is normally done... maybe next time, you can provide this information in your question to make it easier for people to answer it?
UPDATE 2 >>>
Ok, you've updated the question with the essential DataContext information... perfect. Now I can answer your question properly without all the guessing... do you see how much easier this would have been if you had added that there in the first place? No matter... we're here now. Try this final example:
<DataTemplate>
<Button Content="{Binding}" Command="{Binding DataContext.ZoomToAddressCommand,
RelativeSource={RelativeSource AncestorType={x:Type TabItem}}}" />
</DataTemplate>
To reiterate... this RelativeSource Binding will look up the visual tree until it finds a TabItem control. Then, it will look at the DataContext property of that control. Finally, it will look for a ZoomToAddressCommand property in the object (your view model) that is set as the DataContext of the TabItem... and there we are.
I ended up having to change the structure a little bit,
I added a class:
public class IncidentAddress {
public string Address { get; set; }
private RelayCommand zoomCommand;
public RelayCommand ZoomCommand {
get {
if (zoomCommand == null)
zoomCommand = new RelayCommand(Zoom);
return zoomCommand;
}
}
public void Zoom() {
MessageBox.Show(Address);
}
}
In my ViewModel,
this:
public IEnumerable<string> Addresses { get; set; }
changed to:
public IEnumerable<IncidentAddress> Addresses { get; set; }
and I removed the RelayCommand from the ViewModel and left it in the new class
The XAML ended up being this:
<TabItem x:Name="IncidentAddressesTab">
<ItemsControl ItemsSource="{Binding Addresses}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Address}" Command="{Binding ZoomCommand}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</TabItem>

WPF TreeView how to add TreeViewItem control template for child elements of TreeViewItem

how do I create treeview like this one:
<TreeViewItem Header="Customers" ItemsSource="{Binding Customers}">
Customers
Anna
Delete
Open
Peter
Delete
Open
Andrew
Delete
Open
I would like to create child item template something like this
<TreeViewItem Header="{Binding Header}">
<TreeViewItem Header="Delete"/>
<TreeViewItem Header="Open"/>
</TreeViewItem>
But it does not quite work that well because I end up having treeviewitem with datatemplate treeviewitem, but I would like to override controltemplate of child elements, but not parent.
Sure, I want to avoid my binding to be TreeViewItem, nor I want to create children with those static obejct "Open", "Delete".
Here is one of the best articles about TreeView I ever read.
Inside TreeView.Resources you could declare several DataTemplates with different DataType if Delete and Open commands were items of some collection. (TargetType for the commands would be ICommand).
But it seems to mee you do not need TreeView at all.
Customers is a header of the list. If you want it to be epxpandable use Expander control.
Then it is sufficient to provide one data template for each customer.
<DataTemplate DataType="CustomerTypeName">
<Expander Header="{Binding CustomerName}">
<Button Command="{Binding DeleteCustomerCmd}" Content="Delete" Margin="15,0,0,0"/>
<Button Command="{Binding OpenCustomerCmd}" Content="Open" Margin="15,0,0,0"/>
<Expander/>
<DataTemplate>
But here you'll have some troubles with selection highlight.
public class CommandWrapper
{
ICommand Command {get;set;}
string CommandName {get;set;}
}
public class CustomerViewModel
{
Customer Customer {get;set;}
IEnumerable<CommandWrapper> Commands {get;}
}
Let Customers be collection of CustomerViewModel.
Then the following XAML can help:
<TreeView ItemsSource="{Binding ...}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="TypeHoldingCustomersCollection"
ItemsSource="{Binding Customers}">
<TextBlock Text="Customers"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="CustomerViewModel"
ItemsSource="{Binding Commands}">
<TextBlock Text="{Binding Path=Customer.Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="CommandWrapper">
<Button Content="{Binding CommandName}" Command="{Binding Command}"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>

Categories

Resources