WPF ContextMenu ignores CanExecute - c#

I have a DataGrid with elements and a ViewModel behind this view.
The ViewModel has a RelayCommand, that implements CanExecuteChanged.
There's a Style for DataGridRow that has a ContextMenu, its MenuItems are bound to the RelayCommand and pass the item as a parameter.
Here's the XAML:
<ContextMenu x:Key="CommentMenu" DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}">
<MenuItem Header="Bind to Project" Command="{Binding BindToProjectCommand}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}}"/>
</ContextMenu>
<Style x:Key="DefaultRowStyle" TargetType="{x:Type DataGridRow}">
<Setter Property="ContextMenu" Value="{StaticResource CommentMenu}" />
</Style>
...
<DataGrid ItemsSource="{Binding Comments}"
RowStyle="{StaticResource DefaultRowStyle}"
IsReadOnly="True"
CanUserAddRows="False">
Here's the ViewModel:
public MyViewModel()
{
BindToProjectCommand = new RelayCommand<Comment>(BindToProject, CanBindToProject);
}
public bool CanBindToProject(Comment comment)
{
var answer = comment != null && comment.ProjectId == null;
return answer;
}
At runtime CanExecuteChanged is called, the correct comment is passed as a parameter, true is returned, but the menu item is still disabled.
The Output window has no binding errors, so the binding is definitely correct, I can see the correct instance coming.
So the questions are:
1. Why does is the result of CanExecute ignored?
2. How to make it work?
Thank you for your input.

So I found out that the binding of CommandParameter always binds to the last comment in the list. The simplest workaround is to bind SelectedItem to a property in the ViewModel and change the command and the CanBindToProject-method as follows:
<DataGrid ItemsSource="{Binding Comments}"
IsReadOnly="True"
CanUserAddRows="False"
RowStyle="{StaticResource DefaultRowStyle}"
SelectedItem="{Binding SelectedComment}"
SelectionMode="Single"/>
public Comment SelectedComment
public RelayCommand BindToProjectCommand(BindToProject, CanBindToProject)
public bool CanBindToProject()
{
return SelectedComment != null && SelectedComment.ProjectId == null;
}

Related

MultiBinding with two Comboboxes in different DataContexts

I have the following datagrid that contains two comboboxes each contained in a data template.
<DataGrid x:Name="FilterSelection" HorizontalAlignment="Stretch">
<DataGrid.Resources>
<DataTemplate x:Key="EntityComboboxTemp">
<ComboBox x:Name="EntityCombobox"
ItemsSource="{Binding DataContext.FilterVehicleEntities, RelativeSource={RelativeSource AncestorType=local:ExportView}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction
Command="{Binding DataContext.SelectedEntityCommand, RelativeSource={RelativeSource AncestorType=local:ExportView}}"
CommandParameter="{Binding ElementName=EntityCombobox, Path=SelectedItem}">
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</DataTemplate>
<DataTemplate x:Key="AttributeComboboxTemp">
<ComboBox x:Name="AttributeCombobox"
ItemsSource="{Binding DataContext.FilterAttributes, RelativeSource={RelativeSource AncestorType=local:ExportView}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction
Command="{Binding DataContext.SelectedAttributeCommand, RelativeSource={RelativeSource AncestorType=local:ExportView}}">
<i:InvokeCommandAction.CommandParameter>
<MultiBinding Converter="{conv:FilterMultipleConverter}">
<Binding ElementName="EntityCombobox" Path="SelectedItem"/>
<Binding ElementName="AttributeCombobox" Path="SelectedItem"/>
</MultiBinding>
</i:InvokeCommandAction.CommandParameter>
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Entity" CellTemplate="{StaticResource EntityComboboxTemp}" />
<DataGridTemplateColumn Header="Entity Attribute" CellTemplate="{StaticResource AttributeComboboxTemp}"/>
</DataGrid.Columns>
</DataGrid>
The problem lies in the multiple binding of the second combobox, namely the line:
Binding ElementName="EntityCombobox" Path="SelectedItem"/>
which should bind the selecteditem of the first combobox to the second combobox as commandparameter. But I always get the Data binding error that EntityCombobox is unknown. How can I set the DataContext for this binding, is this even possible?
My suggestion is to not do so much in your view. I would recommend to do this logic in the ViewModel for the DataGrid.
To start then I would create a view model for holding your filter selections.
Example: (please see comments for where to put logic for selection changes)
public class FilterViewModel
{
private string _vehicleEntity;
public string VehicleEntity
{
get { return _vehicleEntity; }
set
{
_vehicleEntity = value;
//OnPropertyChanged() if you want
}
}
private string _attribute;
public string Attribute
{
get { return _attribute; }
set
{
_attribute = value;
//Add logic here to determine what to do with both Attribute and VehicleEntity
//OnPropertyChanged() if you want
}
}
}
Then setup your overall View's ViewModel to hold a collection of the FilterModel along with the list of Vehicle options and Attribute options.
Example:
public class MainWindowViewModel
{
public ObservableCollection<FilterViewModel> Rows { get; }
public ObservableCollection<string> FilterVehicleEntities { get; }
public ObservableCollection<string> FilterAttributes { get; set; }
public MainWindowViewModel()
{
Rows = new ObservableCollection<FilterViewModel>();
FilterVehicleEntities = new ObservableCollection<string>()
{
"Vehicle 1",
"Vehicle 2",
"Vehicle 3",
};
FilterAttributes = new ObservableCollection<string>()
{
"Attribute 1",
"Attribute 2",
"Attribute 3",
};
}
}
Then make your view less complicated by just directly binding the selection to the properties. You will then get notified of selection changes as soon as the property updates (as marked in the comments for the FilterViewModel).
Example (full xaml):
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow"
Width="600"
Height="500">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<DataGrid
AutoGenerateColumns="False"
ItemsSource="{Binding Rows}">
<DataGrid.Columns>
<DataGridComboBoxColumn
Header="Entity"
SelectedItemBinding="{Binding VehicleEntity, UpdateSourceTrigger=PropertyChanged}">
<!-- property changed so we get the change right after we select-->
<DataGridComboBoxColumn.ElementStyle>
<Style
TargetType="ComboBox">
<Setter
Property="ItemsSource"
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.FilterVehicleEntities}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style
TargetType="ComboBox">
<Setter
Property="ItemsSource"
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.FilterVehicleEntities}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
<DataGridComboBoxColumn
Header="Entity Attribute"
SelectedItemBinding="{Binding Attribute, UpdateSourceTrigger=PropertyChanged}">
<!-- property changed so we get the change right after we select-->
<DataGridComboBoxColumn.ElementStyle>
<Style
TargetType="ComboBox">
<Setter
Property="ItemsSource"
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.FilterAttributes}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style
TargetType="ComboBox">
<Setter
Property="ItemsSource"
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.FilterAttributes}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
A ComboBox in a DataTemplate cannot refer to a ComboBox that is defined somewhere else using an ElementName binding because they are not in the same namescope.
What you should do is to bind the SelectedItem property of the EntityCombobox to a source property, and then bind the CommandParameter property of the other ComboBox to the same source property. For this to work, the class where the source property is defined must implement the INotifyPropertyChanged interface and raise change notifications and both ComboBoxes must also share the same DataContext.

ListBox checkbox command binding

I can't figure this out. I thought I had the binding set properly but it's not firing. So I have a View:
<ListBox x:Name="EquipmentViewsListBox"
ItemsSource="{Binding EquipmentViews, UpdateSourceTrigger=PropertyChanged}"
SelectionMode="Extended"
BorderThickness="0"
Height="150"
Margin="5,5,10,10">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}, Path=DataContext.ViewSelected}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Content="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I want to fire a command every time the checkbox in that ListBox is selected. I created a command on my view model like so:
public class SdddViewModel : ViewModelBase
{
public SdddModel Model { get; set; }
public RelayCommand<ViewWrapper> ViewSelected { get; set; }
public SdddViewModel(SdddModel model)
{
Model = model;
ViewSelected = new RelayCommand<ViewWrapper>(OnViewSelected);
}
private void OnViewSelected(ViewWrapper obj)
{
var asd = obj;
}
}
So I understand that when I do a ListBox.ItemTemplate the context for that item becomes the ListBoxItem so in my case a class object ViewWrapper. That works fine with the Name binding for content as well with the IsSelected property. It's the command that is not firing when item is checked. I set the relative ancestor to ListBox and the Path=DataContext but still nothing happens. Ideas?
The problem is that the CommandParameter doesn't match. You Declared it so the CommandParameter is ViewWrapper but you sent a parameter of type CheckBox by using RelativeSource Self. Change the CommandParameter to simply {Binding} which means it sends the DataContext of the ListBoxItem, which is ViewWrapper.
You could have detected this binding error using Snoop.

How do I get double click edit to work on one row in my list view?

I have a simple list view with gridview to display each row.
I added a key binding for delete which is working fine.
<ListView.InputBindings>
<KeyBinding Key="Delete" Command="{Binding Path=DeleteKeyCommand}" CommandParameter="{Binding ElementName=DatabasesLstVw, Path=SelectedItem}"/>
</ListView.InputBindings>
But when I add a Mousebinding for LeftDoubleClick to edit its not firing the command.
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding Path=LeftDoubleClickCommand}" CommandParameter="{Binding ElementName=DatabasesLstVw, Path=SelectedItem}" />
After spending the last two hours trying to figure it out the only thing I have come up with is that its firing the double click on the entire list view and not the listview item???
How do I get double click edit to work on one row in my list view? I am using MVVM I don't want to break that so I cant use code behind to hack it. There must be a way to map the command back to my view model.
Update more code:
<ListView x:Name="DatabasesLstVw" ItemsSource="{Binding Path=ClientDetails.Databases}" ItemContainerStyle="{StaticResource alternatingStyle}" AlternationCount="2" Grid.Row="2" Grid.ColumnSpan="4" VerticalAlignment="Top" >
<ListView.InputBindings>
<KeyBinding Key="Delete" Command="{Binding Path=DeleteKeyCommand}" CommandParameter="{Binding ElementName=DatabasesLstVw, Path=SelectedItem}"/>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding Path=LeftDoubleClickCommand}" CommandParameter="{Binding ElementName=DatabasesLstVw, Path=SelectedItem}" />
</ListView.InputBindings>
As the referenced answer is missing some code, this is how it should be:
public class AddToInputBinding
{
public static System.Windows.Input.InputBinding GetBinding(DependencyObject obj)
{
return (System.Windows.Input.InputBinding)obj.GetValue(BindingProp);
}
public static void SetBinding(DependencyObject obj, System.Windows.Input.InputBinding value)
{
obj.SetValue(BindingProp, value);
}
public static readonly DependencyProperty BindingProp = DependencyProperty.RegisterAttached(
"Binding", typeof(System.Windows.Input.InputBinding), typeof(AddToInputBinding), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
((UIElement)obj).InputBindings.Add((System.Windows.Input.InputBinding)e.NewValue);
}
});
}
Then, in your XAML, you would do something like this:
<Window x:Class="WpfApplication.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<ResourceDictionary>
<Style TargetType="ListViewItem">
<Setter Property="local:AddToInputBinding.Binding">
<Setter.Value>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding DataContext.ItemDoubleClick,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}"
CommandParameter="{Binding}"/>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid>
<ListView ItemsSource="{Binding Patients}">
<ListView.View>
<GridView>
<GridViewColumn Header="Test" />
</GridView>
</ListView.View>
</ListView>
</Grid>
In your viewModel, the command definition would be like this:
RelayCommand<string> _ItemDoubleClick;
public ICommand ItemDoubleClick
{
get
{
if (_ItemDoubleClick == null)
{
_ItemDoubleClick = new RelayCommand<string>(this.ItemDoubleClickExecuted,
param => this.ItemDoubleClickCanExecute());
}
return _ItemDoubleClick;
}
}
private bool ItemDoubleClickCanExecute()
{
return true;
}
private void ItemDoubleClickExecuted(string item)
{
//In item you've got the text of double clicked ListViewItem
}
Note that in this sample, the ListView binded ObservableCollection is of type string. If this was other type, you should change the types in the ICommand definitions. Don't forget also to set the Window DataContext to your ViewModel.
Hope this is clearer now.

Binding ContextMenu to DataGridCell Content To Copy Content

I've done ContextMenus before but for a DataGrid they seem to be far too complex. I just want to copy the content of the cell into the clipboard. How can I get this working? Seems like there are many alternatives, but no set guaranteed method.
<DataGrid ItemsSource="{Binding MyStuff}">
<DataGridTextColumn Binding="{Binding MyContentIWantToCopy}">
<DataGridTextColumn.CellStyle>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Command="ApplicationCommands.Copy"/>
</ContextMenu>
</Setter.Value>
</Setter>
<DataGridTextColumn.CellStyle>
</DataGridTextColumn>
</DataGrid>
Ideally this would be the simplest way. But of course, it does not work.
However, I've also tried Commands -
Paste from Excel to WPF DataGrid
MVVM binding command to contextmenu item
Create contextmenus for datagrid rows
None of these work. Typically the result is a context menu that appears and either a greyed out button or one that doesn't do anything, never triggers the command. I just want to copy the text inside the Datagrid cell into the clipboard. I've done this on all kinds of objects before in similar manner - even Listviews - but I can't find any solution for DataGrid and DataGridTextColumns.
Your xaml code at start of message - contains errors. But even after fix it seems to takes and copy entire row instead one cell.
One of the straightforward variants - just write own command, for exapmle:
public class MyCopyCommand : ICommand
{
public string Name { get { return "Copy"; } }
public void Execute(object parameter)
{
Clipboard.SetText(parameter != null ? parameter.ToString() : "<null>");
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
}
This command thinks that parameter already contain required string to copy. But you can customize it's logic as you need, for example - take entire DataGridCell and decide - what to copy from it.
Second step - using our command in XAML, for example through Resources:
<Window
x:Class="WpfApplication65.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"
xmlns:app="clr-namespace:WpfApplication65"
>
<Window.Resources>
<app:MyCopyCommand x:Key="CopyCommand"/>
</Window.Resources>
<Grid>
<DataGrid x:Name="MyDataGrid">
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem
Command="{StaticResource CopyCommand}"
CommandParameter="{Binding Path=Parent.PlacementTarget.Content.Text, RelativeSource={RelativeSource Self}}"
Header="{Binding Name, Source={StaticResource CopyCommand}}"
/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</DataGrid.CellStyle>
</DataGrid>
</Grid>
Where "WpfApplication65" is just my project name, you must insert yours of course.
Here i wrote CommandParameter binding path as it is, becouse i know, that DataGrid will automatically genereate DataGridCell with TextBlock inside it's Content property. If you want generic solution - bind CommandParameter to Parent.PlacementTarget and you will get DataGridCell instance to your MyCopyCommand.Execute method, and then, using it and it's Column property - take from it what you want.
And to make our DataGrid working - add to MainWindow class this code:
public class MyData
{
public string Name { get; set; }
public int Count { get; set; }
public MyData(string name, int count)
{
Name = name;
Count = count;
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var data = new List<MyData>
{
new MyData("one", 10),
new MyData("two", 100500),
new MyData("three", 777)
};
MyDataGrid.ItemsSource = data;
}
}
Hope this helps, if something will not working - ask me.
XAML:
<DataGrid x:Name="MyDataGrid">
<DataGridTextColumn Binding="{Binding MyContentIWantToCopy}">
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Copy" Click="MenuItem_OnClick"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</DataGrid.CelStyle>
</DataGridTextColumn>
</DataGrid>
Code-behind:
void MenuItem_OnClick(object sender, RoutedEventArgs e)
{
ApplicationCommands.Copy.Execute(null, MyDataGrid);
}
I think you can also use the attribute CommandTarget in your XAML, instead of using code-behind.

Bind combobox in DataGrid template using DataTable [duplicate]

I have two simple Model classes and a ViewModel...
public class GridItem
{
public string Name { get; set; }
public int CompanyID { get; set; }
}
public class CompanyItem
{
public int ID { get; set; }
public string Name { get; set; }
}
public class ViewModel
{
public ViewModel()
{
GridItems = new ObservableCollection<GridItem>() {
new GridItem() { Name = "Jim", CompanyID = 1 } };
CompanyItems = new ObservableCollection<CompanyItem>() {
new CompanyItem() { ID = 1, Name = "Company 1" },
new CompanyItem() { ID = 2, Name = "Company 2" } };
}
public ObservableCollection<GridItem> GridItems { get; set; }
public ObservableCollection<CompanyItem> CompanyItems { get; set; }
}
...and a simple Window:
<Window x:Class="DataGridComboBoxColumnApp.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>
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding GridItems}" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" />
<DataGridComboBoxColumn ItemsSource="{Binding CompanyItems}"
DisplayMemberPath="Name"
SelectedValuePath="ID"
SelectedValueBinding="{Binding CompanyID}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
The ViewModel is set to the MainWindow's DataContext in App.xaml.cs:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow window = new MainWindow();
ViewModel viewModel = new ViewModel();
window.DataContext = viewModel;
window.Show();
}
}
As you can see I set the ItemsSource of the DataGrid to the GridItems collection of the ViewModel. This part works, the single Grid line with Name "Jim" is displayed.
I also want to set the ItemsSource of the ComboBox in every row to the CompanyItems collection of the ViewModel. This part does not work: The ComboBox remains empty and in the Debugger Output Window I see an error message:
System.Windows.Data Error: 2 : Cannot
find governing FrameworkElement or
FrameworkContentElement for target
element.
BindingExpression:Path=CompanyItems;
DataItem=null; target element is
'DataGridComboBoxColumn'
(HashCode=28633162); target property
is 'ItemsSource' (type 'IEnumerable')
I believe that WPF expects CompanyItems to be a property of GridItem which is not the case, and that's the reason why the binding fails.
I've already tried to work with a RelativeSource and AncestorType like so:
<DataGridComboBoxColumn ItemsSource="{Binding CompanyItems,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}}}"
DisplayMemberPath="Name"
SelectedValuePath="ID"
SelectedValueBinding="{Binding CompanyID}" />
But that gives me another error in the debugger output:
System.Windows.Data Error: 4 : Cannot
find source for binding with reference
'RelativeSource FindAncestor,
AncestorType='System.Windows.Window',
AncestorLevel='1''.
BindingExpression:Path=CompanyItems;
DataItem=null; target element is
'DataGridComboBoxColumn'
(HashCode=1150788); target property is
'ItemsSource' (type 'IEnumerable')
Question: How can I bind the ItemsSource of the DataGridComboBoxColumn to the CompanyItems collection of the ViewModel? Is it possible at all?
Thank you for help in advance!
Pls, check if DataGridComboBoxColumn xaml below would work for you:
<DataGridComboBoxColumn
SelectedValueBinding="{Binding CompanyID}"
DisplayMemberPath="Name"
SelectedValuePath="ID">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.CompanyItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.CompanyItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
Here you can find another solution for the problem you're facing: Using combo boxes with the WPF DataGrid
The documentation on MSDN about the ItemsSource of the DataGridComboBoxColumn says that only static resources, static code or inline collections of combobox items can be bound to the ItemsSource:
To populate the drop-down list, first
set the ItemsSource property for the
ComboBox by using one of the following
options:
A static resource. For more information, see StaticResource Markup
Extension.
An x:Static code entity. For more information, see x:Static Markup
Extension.
An inline collection of ComboBoxItem types.
Binding to a DataContext's property is not possible if I understand that correctly.
And indeed: When I make CompanyItems a static property in ViewModel ...
public static ObservableCollection<CompanyItem> CompanyItems { get; set; }
... add the namespace where the ViewModel is located to the window ...
xmlns:vm="clr-namespace:DataGridComboBoxColumnApp"
... and change the binding to ...
<DataGridComboBoxColumn
ItemsSource="{Binding Source={x:Static vm:ViewModel.CompanyItems}}"
DisplayMemberPath="Name"
SelectedValuePath="ID"
SelectedValueBinding="{Binding CompanyID}" />
... then it works. But having the ItemsSource as a static property might be sometimes OK, but it is not always what I want.
The correct solution seems to be:
<Window.Resources>
<CollectionViewSource x:Key="ItemsCVS" Source="{Binding MyItems}" />
</Window.Resources>
<!-- ... -->
<DataGrid ItemsSource="{Binding MyRecords}">
<DataGridComboBoxColumn Header="Column With Predefined Values"
ItemsSource="{Binding Source={StaticResource ItemsCVS}}"
SelectedValueBinding="{Binding MyItemId}"
SelectedValuePath="Id"
DisplayMemberPath="StatusCode" />
</DataGrid>
The layout above works perfectly fine for me, and should work for others. This design choice also makes sense, though it isn't very well explained anywhere. But if you have a data column with predefined values, those values typically don't change during run-time. So creating a CollectionViewSource and initializing the data once makes sense. It also gets rid of the longer bindings to find an ancestor and bind on it's data context (which always felt wrong to me).
I am leaving this here for anyone else who struggled with this binding, and wondered if there was a better way (As this page is obviously still coming up in search results, that's how I got here).
I realize this question is over a year old, but I just stumbled across it in dealing with a similar problem and thought I would share another potential solution in case it might help a future traveler (or myself, when I forget this later and find myself flopping around on StackOverflow between screams and throwings of the nearest object on my desk).
In my case I was able to get the effect I wanted by using a DataGridTemplateColumn instead of a DataGridComboBoxColumn, a la the following snippet. [caveat: I'm using .NET 4.0, and what I've been reading leads me to believe the DataGrid has done a lot of evolving, so YMMV if using earlier version]
<DataGridTemplateColumn Header="Identifier_TEMPLATED">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox IsEditable="False"
Text="{Binding ComponentIdentifier,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Path=ApplicableIdentifiers, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding ComponentIdentifier}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
RookieRick is right, using DataGridTemplateColumn instead of DataGridComboBoxColumn gives a much simpler XAML.
Moreover, putting the CompanyItem list directly accessible from the GridItem allows you to get rid of the RelativeSource.
IMHO, this give you a very clean solution.
XAML:
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding GridItems}" >
<DataGrid.Resources>
<DataTemplate x:Key="CompanyDisplayTemplate" DataType="vm:GridItem">
<TextBlock Text="{Binding Company}" />
</DataTemplate>
<DataTemplate x:Key="CompanyEditingTemplate" DataType="vm:GridItem">
<ComboBox SelectedItem="{Binding Company}" ItemsSource="{Binding CompanyList}" />
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" />
<DataGridTemplateColumn CellTemplate="{StaticResource CompanyDisplayTemplate}"
CellEditingTemplate="{StaticResource CompanyEditingTemplate}" />
</DataGrid.Columns>
</DataGrid>
View model:
public class GridItem
{
public string Name { get; set; }
public CompanyItem Company { get; set; }
public IEnumerable<CompanyItem> CompanyList { get; set; }
}
public class CompanyItem
{
public int ID { get; set; }
public string Name { get; set; }
public override string ToString() { return Name; }
}
public class ViewModel
{
readonly ObservableCollection<CompanyItem> companies;
public ViewModel()
{
companies = new ObservableCollection<CompanyItem>{
new CompanyItem { ID = 1, Name = "Company 1" },
new CompanyItem { ID = 2, Name = "Company 2" }
};
GridItems = new ObservableCollection<GridItem> {
new GridItem { Name = "Jim", Company = companies[0], CompanyList = companies}
};
}
public ObservableCollection<GridItem> GridItems { get; set; }
}
Your ComboBox is trying to bind to bind to GridItem[x].CompanyItems, which doesn't exist.
Your RelativeBinding is close, however it needs to bind to DataContext.CompanyItems because Window.CompanyItems does not exist
the bast way i use i bind the textblock and combobox to same property and this property should support notifyPropertyChanged.
i used relativeresource to bind to parent view datacontext which is usercontrol to go up datagrid level in binding because in this case the datagrid will search in object that you used in datagrid.itemsource
<DataGridTemplateColumn Header="your_columnName">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.SelectedUnit.Name, Mode=TwoWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox DisplayMemberPath="Name"
IsEditable="True"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.UnitLookupCollection}"
SelectedItem="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.SelectedUnit, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValue="{Binding UnitId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Id" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
This is working for me:
<DataGridTemplateColumn Width="*" Header="Block Names">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox
VerticalContentAlignment="Center"
ItemsSource="{Binding DataContext.LayerNames,
RelativeSource={RelativeSource Findancestor,
AncestorType={x:Type Window}}}"
SelectedItem="{Binding LayerName, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Categories

Resources