WPF Datagrid MouseBinding MVVM - wpf

I managed to handle a double click on one of my datagrid rows to execute a command on my viewmodel by this xaml:
This works perfect when I click somewhere in the area of the first column (which is bound to the readonly ID), but fails when the double click is done in the area of the textbox in column two (CustomerNumber, which of course catches the doubleclick).
Which would be a MVVM-like way to handle doubleclicks for both scenarios?

You could replace the DataGridTextColumn with a DataGridTemplateColumn and add a MouseBinding to the TextBox in the CellEditingTemplate:
<DataGridTemplateColumn Header="Customer Number">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding CustomerNumber}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding CustomerNumber}">
<TextBox.InputBindings>
<MouseBinding Gesture="LeftDoubleClick"
Command="{Binding DataContext.EditCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}" />
</TextBox.InputBindings>
</TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>

Related

How to bind a ICommand into a Button out of DataContext?

i have a Button into a DataGrid with a DataContext (KeyValuePair).
I want to implementing a Command into this Button.
But if i use the Command="{Binding CommandoTest}" it didnt work.
I tested the ICommand into a Grid without DataContext and it works well.
<DataGrid DataContext="{Binding PersonList.Keys}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<Button Command="{Binding CommandoTest}" Content="CommandoTest"></Button>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The Command Code:
Public ReadOnly Property CommandoTest As ICommand
Get
Return CommandTest_exp
End Get
End Property
Public Sub CommandTest_exp()
MessageBox.Show("Test", "Test")
End Sub
I imagine that the DataGrid is inside of some element whose DataContext is the one with the CommandoTest property on it.
If that is the case, using the example of a Grid as the parent, you could use:
<Grid x:Name="parent_grid">
<DataGrid DataContext="{Binding PersonList.Keys}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<Button Command="{Binding DataContext.CommandoTest, ElementName=parent_grid}" Content="CommandoTest"></Button>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
Another approach, in which you don't have to name any controls, would be (this wouldn't work for the Grid in the previous example, as it would first find the Grid which is the parent of your Button, so I've added another parent element of type Border):
<Border>
<Grid>
<DataGrid DataContext="{Binding PersonList.Keys}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<Button Command="{Binding DataContext.CommandoTest, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Border}}}" Content="CommandoTest"></Button>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Border>

Binding between two DataTemplate works in one direction only

I have a DataGrid where there are Date0, Date1, and Date2 columns. I can change the columns with a ComboBox.
I started to use DataTemplate. The CellTemplate uses Date0InParameterEditingTemplate where I bind Date0 which is not simple DateTime but a class. Then this class is bound to DateEditingTemplate. I would like to use DateEditingTemplate later with Date1 and Date2 as well.
The data goes well to one direction, so the ComboBox displays the proper date. When I select a new value in the ComboBox then the SelectedItem changes. But the content of ContentControl remains unchanged.
<DataGridTemplateColumn Header="{x:Static r:Resource.Date0}"
CellTemplate="{StaticResource Date0InParameterEditingTemplate}"/>
<DataTemplate x:Key="DateEditingTemplate">
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}, Path=DataContext.ValidDateObjects}"
SelectedItem="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Date" Style="{StaticResource ComboBoxError}" IsEditable="False"
ItemStringFormat="{StaticResource DateFormatHU}" />
</DataTemplate>
<!--<DataTemplate x:Key="DateEditingTemplate">
<TextBlock Text="{Binding Date}"/>
</DataTemplate>-->
<DataTemplate x:Key="Date0InParameterEditingTemplate">
<ContentControl Content="{Binding Date0, ValidatesOnDataErrors=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ContentTemplate="{StaticResource DateEditingTemplate}"/>
</DataTemplate>

WPF Datagrid TemplateColumn Control Enable and Disable

I have Datagrid with two TemplateColumn.
First column is a Combobox and Second column with Extended IntergerUpDown control
I need to Enable/Disable the IntegerUpDown control based on the Combox box SelectedItem value.
Please help me how to accomplish this. Sample xaml below.
<Grid><DataGrid ItemsSource="{Binding List1}" Name="x1">
<DataGrid.Columns>
<DataGridTemplateColumn Header="ColorTemplate">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.List2, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}}"
DisplayMemberPath="Name" SelectedValue="{Binding ColourId}" SelectedValuePath="Id" Tag="{Binding Id}"
HorizontalAlignment="Stretch" x:Name="discussTemplate" VerticalAlignment="Stretch"
/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="UPDown" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<extToolkit:IntegerUpDown AllowSpin="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="Auto" Minimum="0"
x:Name="updown"
IsEnabled="????" >
</extToolkit:IntegerUpDown>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
This can be easily done with a DataTrigger on your ViewModel ColourId property.
As your SelectedValue property on the ComboBox is already binded, you can have a DataTrigger on your extToolkit:IntegerUpDown control that will set IsEnabled to True/False based on ColourId value on your ViewModel.

datagrid column does not update

I have a WPF form with a DataGrid. This DG contains a DataGridTemplateColumn that contains a ComboBox. When I click on the new row of the DG and selected a value from the Combobox and then tab to the next column, the selected value does not stay visible in the combobox column.
However, when I tab back, the correct value shows in the combobox.
How do I keep the selected value showing in the combobox column when I tab off the column?
Here is my comboxbox column xaml:
<DataGridTemplateColumn Header="Type" Width="160">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding LocationType.Description, Mode=TwoWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox Name="cboAddrtype"
ItemTemplate="{StaticResource dtAddrType}" IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}, AncestorLevel=1}, Path=DataContext.LocationTypesObject, Mode=OneTime}"
SelectedItem="{Binding Path=SelectedLocationType, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"
SelectedValue="{Binding Path=LocationTypeKey, Mode=TwoWay}"
SelectedValuePath="InternalKey"
Width="100" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
In CellTemplate you're binding LocationType.Description and in CellEditingTemplate you're binding SelectedLocationType and LocationTypeKey. I'm not sure that your binding from ComboBox won't somehow affect LocationType.Description.
Furthermore, SelectedLocationType and LocationTypeKey are not in the context of DataTemplate. You should add a Source binding property to get your ViewModel.

binding a command inside a listbox item to a property on the viewmodel parent

I've been working on this for about an hour and looked at all related SO questions.
My problem is very simple:
I have HomePageVieModel:
HomePageVieModel
+IList<NewsItem> AllNewsItems
+ICommand OpenNews
My markup:
<Window DataContext="{Binding HomePageViewModel../>
<ListBox ItemsSource="{Binding Path=AllNewsItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<Hyperlink Command="{Binding Path=OpenNews}">
<TextBlock Text="{Binding Path=NewsContent}" />
</Hyperlink>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
The list shows fine with all the items, but for the life of me whatever I try for the Command won't work:
<Hyperlink Command="{Binding Path=OpenNewsItem, RelativeSource={RelativeSource AncestorType=vm:HomePageViewModel, AncestorLevel=1}}">
<Hyperlink Command="{Binding Path=OpenNewsItem, RelativeSource={RelativeSource AncestorType=vm:HomePageViewModel,**Mode=FindAncestor}**}">
<Hyperlink Command="{Binding Path=OpenNewsItem, RelativeSource={RelativeSource AncestorType=vm:HomePageViewModel,**Mode=TemplatedParent}**}">
I just always get :
System.Windows.Data Error: 4 : Cannot find source for binding with reference .....
Update
I am setting my ViewModel like this? Didn't think this would matter:
<Window.DataContext>
<Binding Path="HomePage" Source="{StaticResource Locator}"/>
</Window.DataContext>
I use the ViewModelLocator class from the MVVMLight toolkit which does the magic.
Slightly different example but,
I found that by referencing the parent container (using ElementName) in the binding you can get to it's DataContext and its subsequent properties using the Path syntax. As shown below:
<ItemsControl x:Name="lstDevices" ItemsSource="{Binding DeviceMappings}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<ComboBox Text="{Binding Device}" ItemsSource="{Binding ElementName=lstDevices, Path=DataContext.AvailableDevices}" />
...
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
There's two issue working against you here.
The DataContext for the DataTemplate is set to the item the template is displaying. This means you can't just use binding without setting a source.
The other issue is that the template means the item is not technically part of the logical tree, so you can't search for ancestors beyond the DataTemplate node.
To solve this you need to have the binding reach outside the logical tree. You can use a DataContextSpy defined here.
<ListBox ItemsSource="{Binding Path=AllNewsItems}">
<ListBox.Resources>
<l:DataContextSpy x:Key="dataContextSpy" />
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<Hyperlink Command="{Binding Source={StaticResource dataContextSpy}, Path=DataContext.OpenNews}" CommandParameter="{Binding}">
<TextBlock Text="{Binding Path=NewsContent}" />
</Hyperlink>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
try something like this
<Button Command="{Binding DataContext.YourCommand,RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}"
he can't find your command binding inside the listbox because you set a diffrent datacontext than the viewmodel for that listbox
So looks like you are trying to give the proper DataContext to the HyperLink so as to trigger ICommand.
I think a simple element name binding can solve this.
<Window x:Name="window" DataContext="{Binding HomePageViewModel../>
<ListBox ItemsSource="{Binding Path=AllNewsItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<Hyperlink DataContext="{Binding DataContext ,ElementName=window}" Command="{Binding Path=OpenNews}">
<TextBlock Text="{Binding Path=NewsContent}" />
</Hyperlink>
</TextBlock>
</StackPanel>
</DataTemplate>
The AncestorType checks only for Visual-Types not for ViewModel types.
Well, it's a little bit late, I know. But I have only recently faced the same problem. Due to architectural reasons I decided to use a static viewmodel locator instead of the dataContextSpy.
<UserControl x:Class="MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:locator="clr-namespace: MyNamespace"
DataContext="{Binding Source={x:Static locator:ViewModelLocator.MyViewModel}}" >
<ListBox ItemsSource="{Binding Path=AllNewsItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<Hyperlink Command="{Binding Source={x:Static locator:ViewModelLocator.MyViewModel},
Path=OpenNews}"
CommandParameter="{Binding}">
<TextBlock Text="{Binding Path=NewsContent}" />
</Hyperlink>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
The static viewmodel locator instantiates the view model:
namespace MyNamespace
{
public static class ViewModelLocator
{
private static MyViewModelType myViewModel = new MyViewModelType();
public static MyViewModelType MyViewModel
{
get
{
return myViewModel ;
}
}
}
}
Using this workaround is another way to bind from a data template to a command that is in the viewmodel.
The answer from #Darren works well in most cases, and should be the preferred method if possible. However, it is not a working solution where the following (niche) conditions all occur:
DataGrid with DataGridTemplateColumn
.NET 4
Windows XP
...and possibly in other circumstances too. In theory it should fail on all versions of Windows; but in my experience the ElementName approach works in a DataGrid on Windows 7 upwards but not XP.
In the following fictional example, we are trying to bind to an ICommand called ShowThingCommand on the UserControl.DataContext (which is the ViewModel):
<UserControl x:Name="ThisUserControl" DataContext="whatever...">
<DataGrid ItemsSource="{Binding Path=ListOfThings}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Thing">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button
Command="{Binding ElementName=ThisUserControl, Path=ShowThingCommand}"
CommandParameter="{Binding Path=ThingId}"
Content="{Binding Path=ThingId}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</UserControl>
Due to the DataTemplate not being in the same VisualTree as the main control, it's not possible to reference back up to the control by ElementName.
To solve this, the little known .NET 4 and above {x:Reference} can be used. Modifying the above example:
<UserControl x:Name="ThisUserControl" DataContext="whatever...">
<DataGrid ItemsSource="{Binding Path=ListOfThings}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Thing">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button
Command="{Binding Source={x:Reference ThisUserControl}, Path=ShowThingCommand}"
CommandParameter="{Binding Path=ThingId}"
Content="{Binding Path=ThingId}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</UserControl>
For reference, see the following stackoverflow posts:
Question 19244111
Question 5834336
...and for an explanation of why ElementName doesn't work in this circumstance, see this blog post which contains a pre-.NET 4 workaround.
<ListBox xmlns:model="clr-namespace:My.Space;assembly=My.Assembly"
ItemsSource="{Binding Path=AllNewsItems, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<Hyperlink Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}, Path=DataContext.(model:MyNewsModel.OpenNews), Mode=OneWay}"
CommandParameter="{Binding Path=., Mode=OneWay}">
<TextBlock Text="{Binding Path=NewsContent, Mode=OneWay}" />
</Hyperlink>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Resources