Context Menu Binding to Parent Window's Datacontext - wpf

I have a TreeListControl that binds to a collection in my VM. I also want to define the context menu inside the treelistcontrol having its header text bind to another string in my VM. how can I set the data context in this case? I tried to
<Window.DataContext>
<model:ViewModel></model:ViewModel>
</Window.DataContext>
<Grid>
<Button Grid.Row="1" Command="{Binding CellCheckedCommand}"></Button>
<TextBlock Text="{Binding HeaderText}" Grid.Row="2">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext}" Header="{Binding HeaderText}"></MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</Grid>
but it doesn't work.
Here is the ViewModel
public DelegateCommand CellCheckedCommand { get; set; }
private String _HeaderText;
public String HeaderText
{
get
{
return _HeaderText;
}
set
{
_HeaderText = value;
NotifyPropertyChanged("HeaderText");
}
}
public void NotifyPropertyChanged(String name)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
private void CellCheckedMethod()
{
HeaderText = "Changed";
}

Provide a name for your window and explicitly bind to it such as
<window x:Name="ReportsPage"/>
...
<MenuItem DataContext="{Binding ElementName=ReportsPage}"/>
UPDATE
Since the context menu is actually in its own window, binding is a bit trickier. Hence the best bet is to walk up the RelativeSource to the context's parent and pull the header text from there:
<Window.DataContext>
<local:MainVM HeaderText="Jabberwocky" />
</Window.DataContext>
...
<TextBlock Text="{Binding HeaderText}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding Path=Parent.DataContext.HeaderText,
RelativeSource={RelativeSource Self}}" />
</ContextMenu>
</TextBlock.ContextMenu>
Which for this context produces this

This binds to a Window:
DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
If the command AddItemCommand and property AddItemText are defined on the Window ViewModel, bind to Window DataContext:
DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext}"

Related

CurrentViewModel Property Change Not Reflected in ContentControl

Hello I have looked everywhere and I can't understand what I am doing wrong.
My command is called and the onpropertychanged event is called but the ContentControl in my MainWindow will not show the next view.
I am new to WPF and Xaml and really trying to figure this out.
I have attached a link to the sample application on my Github.
The End game here is I want the Radio button to be checked when the view is active. I also want to be able to change from the home view to other views via a button on that view once I am past this first hurtle.
Github repository
public class ApplicationViewModel : ObservableObject
{
private ICommand _changeViewCommand;
private IViewModel _CurrentViewModel;
private List<IViewModel> _viewModels;
private bool _isActive;
public ApplicationViewModel()
{
ViewModels.Add(new HomeViewModel());
ViewModels.Add(new AccountViewModel());
CurrentViewModel = ViewModels.Find(r => r.Name == "Home");
CurrentViewModel.IsActive = true;
}
public List<IViewModel> ViewModels
{
get
{
if (_viewModels == null)
_viewModels = new List<IViewModel>();
return _viewModels;
}
}
public IViewModel CurrentViewModel
{
get
{
return _CurrentViewModel;
}
set
{
if (_CurrentViewModel != value)
{
_CurrentViewModel = value;
OnPropertyChanged();
}
}
}
public ICommand ChangePageCommand
{
get
{
if (_changeViewCommand == null)
{
_changeViewCommand = new RelayCommand(
p => ChangeViewModel((IViewModel)p),
p => p is IViewModel);
}
return _changeViewCommand;
}
}
private void ChangeViewModel(IViewModel viewModel)
{
CurrentViewModel = ViewModels.Find(r => r.Name == viewModel.Name);
}
public bool IsActive
{
get
{
return _isActive;
}
set
{
_isActive = value;
OnPropertyChanged("IsActive");
}
}
}
<UserControl.DataContext>
<viewModels:ApplicationViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<DataTemplate DataType="{x:Type viewModels:ApplicationViewModel}"/>
<DataTemplate DataType="{x:Type viewModels:HomeViewModel}">
<views:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:AccountViewModel}">
<views:AccountView/>
</DataTemplate>
</UserControl.Resources>
<StackPanel Orientation="Vertical">
<RadioButton Content="Home" IsChecked="{Binding IsActive, Mode=TwoWay}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding }">
<RadioButton.DataContext>
<viewModels:HomeViewModel/>
</RadioButton.DataContext>
</RadioButton>
<RadioButton Content="Account" IsChecked="{Binding IsActive, Mode=TwoWay}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding }">
<RadioButton.DataContext>
<viewModels:AccountViewModel/>
</RadioButton.DataContext>
</RadioButton>
</StackPanel>
you should connect RadioButtons to the ViewModels which you have in the list. don't create new view models in xaml.
and CurrentViewModel can be displayed via ContentControl with one of templates decalred in Resources
<UserControl.DataContext>
<viewModels:ApplicationViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<DataTemplate DataType="{x:Type viewModels:HomeViewModel}">
<views:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:AccountViewModel}">
<views:AccountView/>
</DataTemplate>
</UserControl.Resources>
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding ViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Name}"
IsChecked="{Binding IsActive}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
CommandParameter="{Binding }">
</RadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ContentControl Content="{Binding CurrentViewModel}"/>
</StackPanel>
after that you should probably change StackPanel to Grid to allow ContentControl to take all available screen space
Looks like if I assign the AccountViewModel to the button DataContext and set the RelativeSource AncestorType to x:Type Window the button on the HomeView will switch the view to the AccountView.
<Button Height="40" Content="Account" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding }">
<Button.DataContext>
<viewModels:AccountViewModel/>
</Button.DataContext>
</Button>

WPF Binding ContextMenu MenuItem's ItemsSource

I'm trying to bind a single MenuItem's ItemsSource to a ReadOnlyCollection<string>located in the ViewModel. I've read that the ContextMenu is not under the main Visual tree so i can't bind it directly, but any method i try doesn't work. I have the code snippet please let me know what am i doing wrong.
<Window>
…
<DockPanel>
<!-- Task bar Icon -->
<tb:TaskbarIcon x:Name="AppNotifyIcon"
DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
ToolTipText="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.MainTitle}">
<tb:TaskbarIcon.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.NotifyIconOpen}" Click="MenuItem_Open_Click"/>
<MenuItem Header="Technologies" ItemsSource="{Binding to the ReadOnlyCollection of string in ViewModel}">
<MenuItem.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Command" Value="{Binding <!--Command in ViewModel-->, RelativeSource={RelativeSource AncestorType=Window}}"/>
<Setter Property="MenuItem.CommandParameter" Value="{Binding}"/> <!—Binding to the menuItem Header item -->
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
<MenuItem Header="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.NotifyIconExit}" Click="MenuItem_Exit_Click"/>
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
</tb:TaskbarIcon>
…
</DockPanel>
I am trying to bind the second MenuItem's ItemsSource and inside it's ItemContainerStyle i want to bind the command and the commandParameter.
**Update: ** i'm using hardcodet's TaskbarIcon for wpf, if it matters.
Thanks
Try check this out:
1. XAML Code:
<DataGrid x:Name="SelectDataGrid"
ItemsSource="{Binding Persons}" HorizontalAlignment="Left" CellEditEnding="SelectDataGrid_OnCellEditEnding"
VerticalAlignment="Top" AutoGenerateColumns="False" Loaded="SelectDataGrid_OnLoaded">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Technologies" ItemsSource="{Binding MenuItems}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="Header" Value="{Binding Content}"/>
<Setter Property="CommandParameter" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ContextMenu}}, Path=DataContext}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding HelloCommand}"></Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn></DataGrid>
2. DataContext of the context menu is the same as datagrid and window.
3. Inside the DataContext put the next code:
private void Init()
{
MenuItems = new ObservableCollection<MenuItemObject>(new List<MenuItemObject>
{
new MenuItemObject {Command = new RelayCommand<object>(Execute), Content = "A"},
new MenuItemObject {Command = new RelayCommand<object>(Execute), Content = "B"},
new MenuItemObject {Command = new RelayCommand<object>(Execute), Content = "C"},
new MenuItemObject {Command = new RelayCommand<object>(Execute), Content = "D"},
});
}
public ObservableCollection<MenuItemObject> MenuItems { get; set; }
private void Execute(object o)
{
}
4. MenuItemsObject model code:
public class MenuItemObject:BaseObservableObject
{
private ICommand _command;
private string _content;
public ICommand Command
{
get { return _command; }
set
{
_command = value;
OnPropertyChanged();
}
}
public string Content
{
get { return _content; }
set
{
_content = value;
OnPropertyChanged();
}
}
}
5. MVVM parts implementation:
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
public class RelayCommand<T> : ICommand
{
readonly Action<T> _execute;
readonly Func<T, bool> _canExecute;
public event EventHandler CanExecuteChanged;
public RelayCommand(Action<T> execute, Func<T, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public void RefreshCommand()
{
var cec = CanExecuteChanged;
if (cec != null)
cec(this, EventArgs.Empty);
}
public bool CanExecute(object parameter)
{
if (_canExecute == null) return true;
return _canExecute((T)parameter);
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
}
public class RelayCommand : RelayCommand<object>
{
public RelayCommand(Action execute, Func<bool> canExecute = null)
: base(_ => execute(),
_ => canExecute == null || canExecute())
{
}
}
Call to Init method to generate toy menu item's collection DataContext.
An Execute is the method called when some menu item is pressed.
That is all. I'will be glad to help if there will be problems with the code.
Regards,
Ok, I have found the problem thanks to Ilan's suggestion in the comments of using snoop utility.
I saw that in the visual tree, the ContextMenu didn't have its PlacementTarget to point to its parent, the TaskbarIcon (Weird..), but it had an Attached Property called TaskbarIcon.ParentTaskbarIcon from the TaskbarIcon, so i binded the ContextMenu's DataContext to the TaskbarIcon.ParentTaskbarIcon.Tag and that fixed it all.
<Window>
...
<DockPanel>
<!-- Task bar Icon -->
<tb:TaskbarIcon x:Name="AppNotifyIcon"
IconSource="pack://application:,,,/Icons/HwServerIcon.ico"
Tag="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
ToolTipText="{Binding Tag, RelativeSource={RelativeSource Self}}"><!--{Binding Source={StaticResource LocalizedStrings}, Path=Strings.MainTitle}-->
<tb:TaskbarIcon.ContextMenu>
<ContextMenu DataContext="{Binding Path=(tb:TaskbarIcon.ParentTaskbarIcon).Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.NotifyIconOpen}" Click="MenuItem_Open_Click"/>
<MenuItem Header="Technologies" ItemsSource="{Binding TechnologiesNames}">
<MenuItem.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Command" Value="{Binding DataContext.OpenTechnology, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
<Setter Property="MenuItem.CommandParameter" Value="{Binding}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
<MenuItem Header="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.NotifyIconExit}" Click="MenuItem_Exit_Click"/>
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
</tb:TaskbarIcon>
So, the TaskbarIcon's Tag is pointing the Window's DataContext and the ContextMenu's DataContext is pointing the Taskbar's attached property ParentTaskbarIcon.Tag and from now every binding is performed like it was under the window in the visual tree.
For a context menu in a ListBox I add my DataContext to the parent control's tag, and find it in a relative source binding to the placement target. There are many questions on SO regarding this though, and some of those may address more specific instances.
<ListBox ItemsSource="{Binding ItemList}"
SelectedItem="{Binding SelectedItem}"
Tag="{Binding}">
<ListBox.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Delete"
Command="{Binding Path=DeleteCommand}"/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
So for your example specifically:
<tb:TaskbarIcon x:Name="AppNotifyIcon"
DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
ToolTipText="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.MainTitle}"
Tag="{Binding}">
<tb:TaskbarIcon.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.NotifyIconOpen}" Click="MenuItem_Open_Click"/>
<MenuItem Header="Technologies" ItemsSource="{Binding TechnologyList}">
<MenuItem.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Command" Value="{Binding VmCommand}"/>
<Setter Property="MenuItem.CommandParameter" Value="{Binding}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
<MenuItem Header="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.NotifyIconExit}" Click="MenuItem_Exit_Click"/>
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
Of course your bindings will probably vary, but from here you should at least have the DataContext set, and go from there.

ContextMenu on TreeView-Element in MVVM-style

I have the "simple" task to have a ContextMenu on a TreeView(Element) that is done in MVVM-way.
When searching the web I found some solutions that I could bring to work with buttons etc. but not with the TreeView. I think the problem is with setting the ItemsSource-Property of TreeView that gives every single item an own DataContext.
Here's my little Test-App where you can see the principle working for button but not for the TreeView-Elements:
MainWindow.xaml:
<Window x:Class="ContextMenu.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid >
<StackPanel>
<TextBlock Text="{Binding MyText}" />
<Button Tag="{Binding DataContext,RelativeSource={RelativeSource Mode=Self}}" Content="Click me">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding PlacementTarget.Tag.MyText,
RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContextMenu}}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
<TreeView ItemsSource="{Binding MyList}" Tag="{Binding DataContext, RelativeSource={RelativeSource Mode=Self}}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<TextBlock Text="{Binding Name}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding Path=PlacementTarget.Tag.MyText,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
</Grid>
</Window>
Codebehind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowVM();
}
}
MainWindowVM.cs:
public class MainWindowVM
{
public string MyText { get; set; }
public ObservableCollection<TreeElement> MyList { get; set; }
public MainWindowVM()
{
MyText = "This is my Text!";
MyList = new ObservableCollection<TreeElement>();
MyList.Add(new TreeElement("String 1"));
MyList.Add(new TreeElement("String 2"));
}
}
public class TreeElement
{
public string Name { get; set; }
public TreeElement(string Name)
{
this.Name = Name;
}
}
Thanks for your help!!
Joerg
You are close.
What you are doing:
you set the Tag of TreeView to its own DataContext. This is
unnecessary.
you try to get Tag.MyText from your ContextMenu.PlacementTarget - which is TextBlock. The TextBlock has no Tag set.
What you should do:
set the Tag of the TextBlock to DataContext of the Window
(Window is TextBlock ancestor and you should look it up via
RelativeSource Mode=FindAncestor).
the second part is OK - you have TextBlock.Tag set in the first step.

Silverlight: binding a button command within a DataGrid header within an ItemsControl

I have a bit of a problem binding a button command to a property of an "outer" data context.
The image below shows the layout I have. I'm trying to bind the CommandParameter of the "Clear" button (outlined in red) to the LocationId. The ItemsControl repeats an ObservableCollection of Locations (see below for Location definition).
The Clear button is basically trying to REMOVE the Addresses attached to the Location, to clear down the DataGrid. To do that I need to pass the LocationId to the ViewModel.
The actual command is triggered perfectly but the binding on the CommandParameter isn't quite right.
Here are the underlying classes of Location & Address:
class Location
{
int Id;
ObservableCollection<Address> Addresses;
}
class Address
{
string AddressText;
}
And here is the XAML commented with three alternative attempts & error messages:
<ItemsControl ItemsSource="{Binding Locations, Mode=TwoWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel VerticalAlignment="Stretch" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Id}"/>
<sdk:DataGrid x:Name="ResponseDataGrid" ItemsSource="{Binding Addresses}">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Header="Response" Width="*" Binding="{Binding AddressText}"/>
<sdk:DataGridTemplateColumn Width="100">
<sdk:DataGridTemplateColumn.HeaderStyle>
<Style TargetType="sdk:DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Button Content="Clear"
Command="{Binding DataContext.ClearLocationCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding Id, RelativeSource={RelativeSource AncestorType=ItemsControl}}"/>
<!--System.Windows.Data Error: BindingExpression path error: 'Id' property not found on 'System.Windows.Controls.ItemsControl' -->
<!--CommandParameter="{Binding ItemsSource.Id, RelativeSource={RelativeSource AncestorType=ItemsControl}}"/>-->
<!--System.Windows.Data Error: BindingExpression path error: 'Id' property not found on 'System.Collections.ObjectModel.ObservableCollection`1[UI.Location]' -->
<!--This gives me the ID but uses a specific index so only works for the first repeated Location-->
<!--CommandParameter="{Binding ItemsSource[0].Id, RelativeSource={RelativeSource AncestorType=ItemsControl}}"/>-->
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</sdk:DataGridTemplateColumn.HeaderStyle>
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Accept">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction
Command="{Binding DataContext.SelectedAddressCommand , RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType=sdk:DataGrid}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
From the errors, it seems that the button can't quite see the repeated Location object, it can either see the collection of Locations or a specific indexed Location but not the repeated, generated Location that I NEED!
Thanks!
So you have a ViewModel with an ObservableCollection<Location> And clicking on Clear clears the ObservableCollection<Address> of the specified Location, right?
Why don't you just place the Clear command into the Location class? I dont know about the logic triggered by this command, but doing so you will be able to acces to the correct location Id property.
Edit: here it is my example working:
Classes
public class Location
{
public Location(int id, IEnumerable<Address> addresses)
{
this.Id = id;
this.Addresses =new ObservableCollection<Address>(addresses);
this.ClearLocationCommand = new RelayCommand<int>(e => MessageBox.Show(string.Format("Clear command called on Location {0}", this.Id)));
}
public int Id { get; set; }
public ObservableCollection<Address> Addresses { get; set; }
public ICommand ClearLocationCommand { get; set; }
}
public class Address
{
public Address(string text)
{
this.AddressText = text;
}
public string AddressText { get; set; }
}
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MainWindowViewModel()
{
Locations = new ObservableCollection<Location>(new []
{
new Location(1, new [] { new Address("A1") }),
new Location(2, new [] { new Address("A2"), new Address("A3"), }),
});
}
public ObservableCollection<Location> Locations { get; set; }
private void OnPropertyChanged(string porpertyName)
{
var e = this.PropertyChanged;
if (e != null)
{
e(this, new PropertyChangedEventArgs(porpertyName));
}
}
}
XAML
<Window x:Class="TestBindingButtons.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:TestBindingButtons="clr-namespace:TestBindingButtons" Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<TestBindingButtons:MainWindowViewModel />
</Window.DataContext>
<Grid>
<ItemsControl ItemsSource="{Binding Locations, Mode=TwoWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel VerticalAlignment="Stretch" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Id}"/>
<DataGrid x:Name="ResponseDataGrid" ItemsSource="{Binding Addresses}">
<DataGrid.Columns>
<DataGridTextColumn Header="Response" Width="*" Binding="{Binding AddressText}"/>
<DataGridTemplateColumn Width="100">
<DataGridTemplateColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Button Content="Clear"
Command="{Binding DataContext.ClearLocationCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridTemplateColumn.HeaderStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Accept">
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
PD:
I didn't posted the implementation of RelayCommand, use your own.
I didn't used your "sdk" framework, just plain microsoft stuff.
If you move the ClearLocationCommand property to the Location class, remember to change the following bindings:
<Button Content="Clear"
Command="{Binding DataContext.ClearLocationCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding Id, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
/>
to:
<Button Content="Clear"
Command="{Binding ClearLocationCommand}"
CommandParameter="{Binding Id}"
/>
Because each outer Location object now has its own clear command, and you are already on the correct DataContext in that item row.

How can I set my data binding in a WPF ItemsControl child to use a property on the parent data context?

I have a WPF ListView with a collection of RadioButtons. I want to set the GroupName of the child controls to be bound to a property on the parent data context. At the moment I am doing this by duplicating the property in each of the children's data context but that can't be right.
My XAML:
<ListView ItemsSource="{Binding OptionItems}" >
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type Logging:FilterOptionsRadioListViewModel}">
<RadioButton GroupName="{Binding GroupName}" Content="{Binding Option.Value}" Tag="{Binding Option.Key}" IsChecked="{Binding IsChecked}" Command="Logging:FilterOptionsRadioListViewModel.CheckedChangedCommand" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
GroupName is a property on the parent View Model. I currently pass this onto the child View Model (where it is also a property) in the child constructor:
var item = new FilterOptionsRadioListItemViewModel(option, this.GroupName);
What is the correct way of doing this?
<ListView ItemsSource="{Binding OptionItems}" >
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type Logging:FilterOptionsRadioListViewModel}">
<RadioButton GroupName="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}, Path=DataContext.GroupName}" Content="{Binding Option.Value}" Tag="{Binding Option.Key}" IsChecked="{Binding IsChecked}" Command="Logging:FilterOptionsRadioListViewModel.CheckedChangedCommand" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
You certainly can do that, but if you don't want to have to copy the GroupName to every child Option instance, you could instead do something like the following
// I'm imagining your viewmodels look something like this
public class ParentViewModel
{
private ObservableCollection<Option> m_OptionItems;
public ObservableCollection<Option> OptionItems
{
get { return m_OptionItems; }
set { m_OptionItems = value; }
}
private string m_ParentGroupName;
public string ParentGroupName
{
get { return m_ParentGroupName; }
set
{
m_ParentGroupName = value;
}
}
}
public class Option
{
private string m_Value;
public string Value
{
get { return m_Value; }
set
{
m_Value = value;
}
}
}
Then you can use a relative binding so you can look up the control tree to find the right datacontext, so you don't have to copy the ParentGroupName to each child Option instance.
If you name your window, you can traverse the root DataContext.
<Window x:Class="MyNamespace.MainWindow"
Name="Window"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="500" Width="725">
<ListView ItemsSource="{Binding OptionItems}" >
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type Logging:FilterOptionsRadioListViewModel}">
<RadioButton GroupName="{Binding ElementName=Window, Path=DataContext.ParentGroupName}" Content="{Binding Option.Value}" Tag="{Binding Option.Key}" IsChecked="{Binding IsChecked}" Command="Logging:FilterOptionsRadioListViewModel.CheckedChangedCommand" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Resources