DependencyProperty for UserControl with DataContext - c#

I have a ListBox where I'm using a UserControl as DataTemplate. My UserControl has a ViewModel. I have a DependencyProperty in my UserControl so that I can bind item from my ListBox to my UserControl.
It does not work unless I do not set any DataContext to my UserControl.
How can I use DP and custom DataContext in my UC ?
My ListBox:
<ListBox ItemsSource="{Binding Path=ListItems, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<local:MyCustomUC MyObject="{Binding Path=.}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
My UserControl XAML:
<UserControl x:Class="UserControlDataTemplate.MyCustomUC"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="Auto" Width="Auto">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=FromViewModel}" />
<Button Content="{Binding ElementName=MyObject, Path=FromParent}" />
</StackPanel>
</UserControl>
My UserControl CS:
public MyClass MyObject
{
get { return (MyClass)GetValue(MyObjectProperty); }
set
{
SetValue(MyObjectProperty, value);
}
}
// Using a DependencyProperty as the backing store. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyObjectProperty =
DependencyProperty.Register("MyObject", typeof(MyClass), typeof(MyCustomUC), new PropertyMetadata(null));
public MyCustomUC()
{
InitializeComponent();
this.DataContext = new MyCustomUCViewModel();
}
My ViewModel:
public class MyCustomUCViewModel : DependencyObject, INotifyPropertyChanged
{
public String FromViewModel { get; set; }
public MyCustomUCViewModel()
{
this.FromViewModel = Guid.NewGuid().ToString();
}
...
}
Item class in ItemSource from ListBox:
public class MyClass : INotifyPropertyChanged
{
public String FromParent { get; set; }
...
}
What did I do wrong ?

Here you are setting the DataContext in the MyCustomUC()
instead you can set DataContext like this
<vm:YourViewModel x:Name="VModel" IfPropertToSet="{BindingFromExistingDC}"/>
<ListBox ItemsSource="{Binding Path=ListItems, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<local:MyCustomUC MyObject="{Binding Path=.}" DataContext="{Binding ElementName=VModel}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
you need to include the namespace
xmlns:vm="clr-namespace:YourViewModelPath"

Related

How to fill each tab of the tab control's itemslist with one user control dynamically from the mainviewmodel

My MainView contains a TabControl with an ItemTemplate and a ContentTemplate.
The TabControl's ItemsSource is bound to the Property ObservableCollection<TabViewModel> TabCollection in my MainViewModel.
TabViewModel:
namespace LuxUs.ViewModels
{
public class TabViewModel
{
public string Name { get; set; }
public object VM {get; set;}
public TabViewModel(string name)
{
Name = name;
}
public TabViewModel(string name, object vm)
{
Name = name;
VM = vm;
}
}
}
I want to create the tabs with their tab's header AND content dynamically from the MainViewModel like this...:
MainViewModel:
using System.Collections.ObjectModel;
namespace LuxUs.ViewModels
{
public class MainViewModel : ObservableObject, IPageViewModel
{
public ObservableCollection<TabViewModel> TabCollection { get; set; }
public MainViewModel()
{
TabCollection = new ObservableCollection<TabViewModel>();
TabCollection.Add(new TabViewModel("Dachdefinition", new DachdefinitionViewModel()));
TabCollection.Add(new TabViewModel("Baukörperdefinition"));
TabCollection.Add(new TabViewModel("Fassade"));
TabCollection.Add(new TabViewModel("Raumdefinition"));
TabCollection.Add(new TabViewModel("Treppenloch | Galerieöffnung"));
}
}
}
View:
<UserControl x:Class="LuxUs.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:LuxUs.Views"
xmlns:models="clr-namespace:LuxUs.Models"
xmlns:vm="clr-namespace:LuxUs.ViewModels"
mc:Ignorable="d">
<Grid>
<Grid>
<TabControl Style="{DynamicResource TabControlStyle}" ItemsSource="{Binding TabCollection}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<UserControl>
<ContentControl Content="{Binding VM}" />
</UserControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Grid>
</UserControl>
... but the content of each tab won't show. Instead, I get this text. The ViewModel ist the correct one but it should load the view instead of showing this text, of course:
The first tab's ViewModel DachdefinitionViewModel has only an empty constructor:
using System.Collections.ObjectModel;
namespace LuxUs.ViewModels
{
public sealed class DachdefinitionViewModel : ObservableObject
{
public DachdefinitionViewModel()
{
}
}
}
And here is its view Dachdefinition.xaml:
<UserControl x:Class="LuxUs.Views.Dachdefinition"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:LuxUs.Views"
xmlns:vm="clr-namespace:LuxUs.ViewModels"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:DachdefinitionViewModel></vm:DachdefinitionViewModel>
</UserControl.DataContext>
<Grid Margin="50">
...
...
...
</Grid>
</UserControl>
Is the binding correct here or do I need to bind differently? Why is the view not showing up inside the first tab?
you need to connect view model with correct view via DataTemplate.
DataTemplate provides visual representation for data type which doesn't have it (your view model). If you don't specify DataTemplate, you will get default one: TextBlock with string representation of object (result of ToString() method, default to type name).
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type vm:DachdefinitionViewModel}">
<views:Dachdefinition />
</DataTemplate>
</Grid.Resources>
<TabControl Style="{DynamicResource TabControlStyle}"
ItemsSource="{Binding TabCollection}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<UserControl>
<ContentControl Content="{Binding VM}" />
</UserControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
Your TabControl declaration should look like this:
<TabControl ItemsSource="{Binding TabCollection}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type vm:DachdefinitionViewModel}">
<local:Dachdefinition/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="Content" Value="{Binding VM}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
And the Dachdefinition UserControl must not set its own DataContext property, because the DataContext value is supposed to be inherit from the control's parent element, i.e. the TabItem.
<UserControl x:Class="LuxUs.Views.Dachdefinition" ...>
<!--
do not set UserControl.DataContext here
-->
<Grid Margin="50">
...
</Grid>
</UserControl>
Yes there is a problem in databinding.
at
<ContentControl Content="{Binding VM}" />
This line would just display ToString() value of the object bound to
it. (VM in this case).
Instead you can try using ContentTemplateSelection where you can choose the type of ContentTemplate in run time based on the type of object bound to it.
class TabContentTemplateSelector:DataTemplateSelector
{
public DataTemplate DefaultTemplate { get; set; }
public DataTemplate DachdeTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is TabViewModel tabViewModel)
{
if (tabViewModel.VM != null && tabViewModel.VM is DachdefinitionViewModel)
{
return DachdeTemplate;
}
else
{
return DefaultTemplate;
}
}
return base.SelectTemplate(item, container);
}
}
<DataTemplate x:Key="DachdeTemplate">
</DataTemplate>
<DataTemplate x:Key="SomeOtherTemplate">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<local:TabContentTemplateSelector x:Key="myTabContentTemplateSelector"
DachdeTemplate="{StaticResource DachdeTemplate}"
DefaultTemplate="{StaticResource
SomeOtherTemplate}"
/>
</UserControl.Resources>
<Grid>
<TabControl ItemsSource="{Binding TabCollection}"
ContentTemplateSelector="{StaticResource
myTabContentTemplateSelector}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
https://www.c-sharpcorner.com/UploadFile/41e70f/dynamically-selecting-datatemplate-for-wpf-listview-way-1/
Check this for how to dynamically change template.. In the template you can use any kind of user control.

WPF change notification of user control inside item template not working

I'm trying to bind each view model in an ObservableCollection<FilterControlViewmodel> as DataContext to a user control FilterControl in an ItemsControl.
The binding itself works fine. "InitialFilterName" is displayed correctly from FilterControlViewmodel.FilterName but any updates on the property are not notified to the UI.
Also adding elements to ObservableCollection<FilterControlViewmodel> is working find and adding additional user controls. But again the values inside the FilterControlViewmodel are not updated to the UI.
Any hint on where the notification is missing is appreciated. Thanks.
MainWindow.xaml
<Window.DataContext>
<local:MainWindowViewmodel/>
</Window.DataContext>
<Grid>
<ItemsControl ItemsSource="{Binding FilterViewmodel.FilterControls}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<filter:FilterControl DataContext="{Binding}"></filter:FilterControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
FilterControl.xaml
<UserControl.DataContext>
<local:FilterControlViewmodel/>
</UserControl.DataContext>
<Grid>
<Label Grid.Column="0" Grid.Row="0" Content="{Binding FilterName}"></Label>
<ComboBox Grid.Column="0" Grid.Row="1" ItemsSource="{Binding FilterValues}" SelectedItem="{Binding FilterValueSelected}"></ComboBox>
<Button Grid.Column="1" Grid.Row="1" Content="X" Command="{Binding ResetFilterCommand}"></Button>
</Grid>
MainWindowViewmodel.cs
public class MainWindowViewmodel : INotifyPropertyChanged
{
public FilterViewmodel FilterViewmodel
{
get => _filterViewmodel;
set
{
if (Equals(value, _filterViewmodel)) return;
_filterViewmodel = value;
OnPropertyChanged();
}
}
FilterViewmodel.cs
public class FilterViewmodel : INotifyPropertyChanged
{
public ObservableCollection<FilterControlViewmodel> FilterControls
{
get => return _filterControls;
set
{
if (Equals(value, _filterControls)) return;
_filterControls = value;
OnPropertyChanged();
}
}
FilterControlViewmodel.cs
public class FilterControlViewmodel : INotifyPropertyChanged
{
private string _filterName = "InitialFilterName";
public string FilterName
{
get => _filterName;
set
{
if (value == _filterName) return;
_filterName = value;
OnPropertyChanged();
}
}
You should remove the following markup as it creates another instance of FilterControlViewmodel:
<UserControl.DataContext>
<local:FilterControlViewmodel/>
</UserControl.DataContext>
The FilterControl will then inherit its DataContext from the current item (FilterControlViewmodel) in the ItemsControl without you having to set the DataContext property explicitly:
<ItemsControl ItemsSource="{Binding FilterViewmodel.FilterControls}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<filter:FilterControl/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Error binding CustomControl to Items of a ObservableCollection inside the Datatemplate of a DataGrid

I have a DataGrid that have as Source an ObservableCollection of Site. I have some columns that are defined in a DataTemplate binding to properties of Site. The binding works perfectly when using builtin controls like TextBlock but I am not able to do it with a custom control. I have seen that this error appears in the console but I do not understand exactly what I have to do:
System.Windows.Data Error: 40 : BindingExpression path error: 'Port' property not found on 'object' ''TestControl' (Name='')'. BindingExpression:Path=Port; DataItem='TestControl' (Name=''); target element is 'TestControl' (Name=''); target property is 'CameraName' (type 'String')
Here I attach snippets of the code I use;
DataGrid:
<DataGrid Name="SitesList" CanUserReorderColumns="True"
ItemsSource="{Binding ViewModel.Sites}"
PreparingCellForEdit="DataGrid_PreparingCellForEdit"
>
<DataGrid.Resources>
<DataTemplate x:Key="CameraTemplate">
<Grid>
<hv:TestControl CameraName="{Binding Path=Port}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="PortTemplate">
<TextBox x:Name="PortTextBox"
Text="{Binding Path=Port, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Camera"
CellTemplate="{StaticResource CameraTemplate}"
MinWidth="100"/>
<DataGridTemplateColumn Header="Port"
CellTemplate="{StaticResource PortTemplate}"
MinWidth="70"/>
</DataGrid.Columns></DataGrid>
ObservableCollection:
private ObservableCollection<Site> m_sites = new ObservableCollection<Site>();
public ObservableCollection<Site> Sites
{
get
{
return m_sites;
}
set
{
m_sites = value;
}
}
Site.cs:
namespace Wizard.View
{
public class Site: INotifyPropertyChanged
{
#region properties
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private string m_port = "0";
public string Port
{
get
{
return m_port;
}
set
{
m_port = value;
NotifyPropertyChanged("Port");
}
}
#endregion
}
}
TestControl.xaml.cs:
namespace Wizard.View.HelpersViews
{
public partial class TestControl: UserControl
{
public TestControl()
{
InitializeComponent();
}
public string CameraName
{
get { return (string)GetValue(CameraNameProperty); }
set {
Console.WriteLine(value);
SetValue(CameraNameProperty, value);
CameraNameTextBlock.Text = value;
}
}
public static readonly DependencyProperty CameraNameProperty =
DependencyProperty.Register("CameraName",
typeof(string),
typeof(TestControl),
new PropertyMetadata("0"));
}
}
TestControl.xaml:
<UserControl
x:Class="Wizard.View.HelpersViews.TestControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
d:DesignHeight="100" d:DesignWidth="300">
<StackPanel Orientation="Horizontal" Height="Auto" HorizontalAlignment="Left">
<CheckBox Margin ="5,0" Name="AddCameraCheck"
VerticalAlignment="Center" Style="{StaticResource ConfiguratorCheckBox}" />
<TextBlock x:Name="CameraNameTextBlock" Width="175" Text="{Binding CameraName}" />
</StackPanel>
</UserControl>
The column Port is bound correctly but Camera is not.
The TextBlock in the TestControl should bind to the CameraName property of itself:
<TextBlock x:Name="CameraNameTextBlock" Width="175" Text="{Binding CameraName,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
...but you should remove this for the control to inherit the DataContext from the DataGridRow:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Also the CLR wrapper of a dependency property should only call GetValue and SetValue:
public string CameraName
{
get { return (string)GetValue(CameraNameProperty); }
set { SetValue(CameraNameProperty, value); }
}

Binding DataTemplate Control Property

I have a UserControl like the following:
<UserControl>
<Expander>
<Expander.HeaderTemplate>
<DataTemplate>
<Grid HorizontalAlignment="{Binding Path=HorizontalAlignment, RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}}, Mode=OneWayToSource}">
<TextBlock Text="{Binding Path=Service, Mode=TwoWay}"/>
</Grid>
</DataTemplate>
</Expander.HeaderTemplate>
</Expander>
</UserControl>
I want to bind the Text property of the TextBlock control to a property of my UserControl class, like for example:
public string Service
{ get; set; }
How can I do?
Try setting the DataContext to the UserControl so you can access the properties
In this situation I have named the UserControl "UI" (Name="UI") so you can bind using ElementName Text="{Binding ElementName=UI, Path=Service}"
Example:
<UserControl x:Class="WpfApplication8.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" Name="UI">
<Expander>
<Expander.HeaderTemplate>
<DataTemplate>
<Grid HorizontalAlignment="{Binding Path=HorizontalAlignment, RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}}, Mode=OneWayToSource}">
<TextBlock Text="{Binding ElementName=UI, Path=Service}" />
</Grid>
</DataTemplate>
</Expander.HeaderTemplate>
</Expander>
</UserControl>
Code:
I have implemented INotifyPropertyChanged this will allow the UI to be updated when Service string changes
public partial class UserControl1 : UserControl, INotifyPropertyChanged
{
public UserControl1()
{
InitializeComponent();
Service = "Test";
}
private string _service;
public string Service
{
get { return _service; }
set { _service = value; NotifyPropertyChanged("Service"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Result:

Custom Treeview User Control MVVM Double Click Bubbling Event WPF

I'm trying to make a custom TreeView and make it a user control. When I wrap the user control in another window, I tried to get the TreeView item double click event in the main window.
<Window xmlns:avalondock="http://avalondock.codeplex.com" x:Class="WellsVisualizationWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:well="clr-namespace:VisualizationWPF.ViewModel.ViewUserControl"
Title="e-IFD" Height="408" Width="558" WindowState="Maximized"
>
<Grid MinWidth="100" **TreeViewItem.MouseLeftButtonClick=<EventHandler>**> <-- Trying to override but failed :p
<local:CustomTreeView />
</Grid>
I tried to get the bubbling mouse double click from CustomTreeView item and intercept the event in the grid wrapper outside the usercontrol. I tried to add TreeViewItem. TreeViewItem.MouseLeftButtonDown="Grid_MouseLeftButtonDown and failed. Any ideas to solve my problem ?
Here is my code of custom user control for treeview
<UserControl x:Class="WellsVisualizationWPF.ViewModel.ViewUserControl.WellsTreeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VisualizationWPF.ViewModel"
>
<Grid MinWidth="100">
<Grid.RowDefinitions>
<RowDefinition MaxHeight="500" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="1">
<TextBlock TextWrapping="Wrap" FontSize="12">
Text1
</TextBlock>
<Button Height="24" Content="Add New" Name="btn_add" Click="btn_add_Click" />
</StackPanel>
<ScrollViewer>
<DockPanel>
<TreeView Grid.Row="0" ItemsSource="{Binding Data}">
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type local:MainViewModel}"
ItemsSource="{Binding Children}"
>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:ParamsViewModel}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
</DockPanel>
</ScrollViewer>
</Grid>
You don't need to publish double-click event outside from the user control at all.
You need to add some InputBinding (MouseBinding in this particular case) into InputBindings collection of the TreeView.SelectedItem.
The problem is that you can't do that in normal, obvious way - set InputBindings via TreeView.ItemContainerStyle, because InputBindings collection is read-only. Sad, but true.
Good news is that you can use attached property to accomplish that.
The sample:
View models.
a) this is what will be displayed as items in tree view:
public class Node : ViewModelBase
{
public String Text
{
get { return text; }
set
{
if (text != value)
{
text = value;
OnPropertyChanged("Text");
}
}
}
private String text;
public ObservableCollection<Node> Nodes { get; set; }
}
b) this is "main" view model:
public class ViewModel : ViewModelBase
{
public ViewModel()
{
this.selectedNodeDoubleClickedCommand = new RelayCommand<Node>(node =>
{
Debug.WriteLine(String.Format("{0} clicked!", node.Text));
});
}
public ObservableCollection<Node> Nodes { get; set; }
public RelayCommand<Node> SelectedNodeDoubleClickedCommand
{
get { return selectedNodeDoubleClickedCommand; }
}
private readonly RelayCommand<Node> selectedNodeDoubleClickedCommand;
}
User control code-behind. Basic idea - we're adding one attached property to set input binding though it in XAML, and another one - to allow external world bind command, when input binding fires:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public ICommand SelectedItemDoubleClickedCommand
{
get { return (ICommand)GetValue(SelectedItemDoubleClickedCommandProperty); }
set { SetValue(SelectedItemDoubleClickedCommandProperty, value); }
}
public static readonly DependencyProperty SelectedItemDoubleClickedCommandProperty = DependencyProperty.Register(
"SelectedItemDoubleClickedCommand", typeof(ICommand),
typeof(UserControl1),
new UIPropertyMetadata(null));
public static ICommand GetSelectedItemDoubleClickedCommandAttached(DependencyObject obj)
{
return (ICommand)obj.GetValue(SelectedItemDoubleClickedCommandAttachedProperty);
}
public static void SetSelectedItemDoubleClickedCommandAttached(DependencyObject obj, ICommand value)
{
obj.SetValue(SelectedItemDoubleClickedCommandAttachedProperty, value);
}
public static readonly DependencyProperty SelectedItemDoubleClickedCommandAttachedProperty = DependencyProperty.RegisterAttached(
"SelectedItemDoubleClickedCommandAttached",
typeof(ICommand), typeof(UserControl1),
new UIPropertyMetadata(null, SelectedItemDoubleClickedCommandAttachedChanged));
private static void SelectedItemDoubleClickedCommandAttachedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var item = d as TreeViewItem;
if (item != null)
{
if (e.NewValue != null)
{
var binding = new MouseBinding((ICommand)e.NewValue, new MouseGesture(MouseAction.LeftDoubleClick));
BindingOperations.SetBinding(binding, InputBinding.CommandParameterProperty, new Binding("SelectedItem")
{
RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(TreeView), 1)
});
item.InputBindings.Add(binding);
}
}
}
}
User control XAML:
<Grid>
<TreeView ItemsSource="{Binding Nodes}">
<TreeView.Resources>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}" DataType="{x:Type local:Node}">
<TextBlock Text="{Binding Text}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="local:UserControl1.SelectedItemDoubleClickedCommandAttached"
Value="{Binding SelectedItemDoubleClickedCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
Main window XAML:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:UserControl1 SelectedItemDoubleClickedCommand="{Binding SelectedNodeDoubleClickedCommand}"/>
</Grid>
</Window>
Main window code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel
{
Nodes = new ObservableCollection<Node>
{
new Node
{
Text = "Parent 1",
Nodes = new ObservableCollection<Node>
{
new Node { Text = "Child 1.1"},
new Node { Text = "Child 1.2"},
}
},
new Node
{
Text = "Parent 2",
Nodes = new ObservableCollection<Node>
{
new Node { Text = "Child 2.1"},
new Node { Text = "Child 2.2"},
}
},
}
};
}
}

Categories