I'm stuck on what seems to be a basic DataGridTemplateColumn question.
I have created a WPF DataGrid (sample code below) with a DataGridTemplateColumn. Inside the DataGridTemplateColumn, I have created a UserControl for the CellEditingTemplate. Within this UserControl, I have a button (and/or want to monitor a keypress, etc) that I want to use to Cancel (or Commit) the changes to the datagrid cell.
How do I go about notifying the DataGrid to CancelEdit from within the UserControl?
<DataGrid
AutoGenerateColumns="False"
ItemsSource="{Binding Items}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Text}"/>
<Button Content="Cancel!">
<!--
How to make this Button Cancel Editing?
Click="Cancel"
Command="{Binding CancelCommand}"
-->
</Button>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The easy way to do it is to give the grid a name (theGrid), add a click handler to the button like this...
<Button Content="Cancel!" Click="Button_Click">
...and then cancel editing in the handler:
private void Button_Click(object sender, RoutedEventArgs e)
{
theGrid.CancelEdit();
}
The "correct" way to do it is to create a view model with a command handler, bind the button's Command property to it and then use an attached behavior that watches an event in your view model and triggers the cancel when it fires.
Related
I have a community toolkit datagrid bound to an observable collection. I also have an "add" button bound to a command that adds a new element to the collection and thus adds a new row to the datagrid. Now I want to automatically start editing the first cell of the new row when the button is clicked.
I tried adding this to the end of my command:
MyDataGrid.Focus(FocusState.Programmatic);
MyDataGrid.SelectedIndex = MyCollection.Count;
MyDataGrid.CurrentColumn = MyDataGrid.Columns[0];
MyDataGrid.BeginEdit();
Though this only works when the focus was already on the datagrid before the button is clicked. Does anyone have any idea how to solve this ?
Thanks in advance
I did this in XAML Studio for the known namespaces in settings. It's an ObservableCollection bound to the ItemsSource of the DataGrid.
In addition to having a button for the Add action and putting code in its callback, you need to add an event handler on the DataGrid for the PreparingCellForEdit event.
So in your add method, you can create a new element in your collection and start editing the new item like so:
private void AddNamespaceButton_Click(object sender, RoutedEventArgs e)
{
// Add new Row and begin editing
KnownNamespaces.Insert(0, new XmlnsNamespace(string.Empty, string.Empty));
NamespaceDataGrid.SelectedIndex = 0;
NamespaceDataGrid.ScrollIntoView(NamespaceDataGrid.SelectedItem, null);
NamespaceDataGrid.Focus(FocusState.Keyboard);
NamespaceDataGrid.BeginEdit();
}
The next piece is the key for the DataGridTextColumn type to ensure the focus of the cell gets transferred to the focus of the TextBox inside when editing:
private void NamespaceDataGrid_PreparingCellForEdit(object sender, Microsoft.Toolkit.Uwp.UI.Controls.DataGridPreparingCellForEditEventArgs e)
{
if (e.EditingElement is TextBox t)
{
t.Focus(FocusState.Keyboard);
}
}
It's also important to note that the class that your ObservableCollection consists of should implement the IEditableObject interface to work properly with the DataGrid.
Here's my XAML for completeness:
<StackPanel Margin="{StaticResource SettingsSubheaderMargin}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPanel_KnownNamespaces"
Margin="0,8,0,12"
Style="{StaticResource BodyTextStyle}" />
<Button x:Uid="SettingsPanel_KnownNamespaces_Button_Add" Click="AddNamespaceButton_Click" Style="{StaticResource VSCodeAppBarHeaderButtonStyle}">
<SymbolIcon Symbol="Add" />
</Button>
</StackPanel>
<controls:DataGrid x:Name="NamespaceDataGrid"
MaxHeight="450"
AutoGenerateColumns="False"
Background="{ThemeResource Brush-Blue-Dark-1}"
CanUserReorderColumns="False"
CanUserSortColumns="True"
IsReadOnly="False"
ItemsSource="{x:Bind KnownNamespaces, Mode=OneWay}"
PreparingCellForEdit="NamespaceDataGrid_PreparingCellForEdit"
RowEditEnded="DataGrid_RowEditEnded">
<controls:DataGrid.Columns>
<controls:DataGridTextColumn Width="SizeToCells"
Binding="{Binding Name}"
FontSize="20"
Header="Shortcut" />
<controls:DataGridTextColumn Width="SizeToCells"
Binding="{Binding Path}"
FontSize="20"
Header="Namespace" />
<controls:DataGridTemplateColumn>
<controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button x:Uid="SettingsPanel_KnownNamespaces_Button_Remove"
Click="RemoveNamespaceButton_Click"
CommandParameter="{Binding}"
Style="{StaticResource VSCodeAppBarHeaderButtonStyle}">
<SymbolIcon Symbol="Delete" />
</Button>
</DataTemplate>
</controls:DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumn>
</controls:DataGrid.Columns>
</controls:DataGrid>
</StackPanel>
I use the RowEditEnding event to save my data model back to disk.
I want to show data in data grid. And only some of the cells in a column can be edited.So, for this purpose I defined the Column template for one column as shown below:
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox IsReadOnly="{Binding IsReadOnly}" BorderThickness="0" Text="{Binding Value, UpdateSourceTrigger= LostFocus}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
So, depending upon the read only property of the model object, cell will be editable or not.This is working great.But now I want to perform some operation when user starts editing the cell, so I created a handler for the BeginningEdit event for the DataGrid.But the event handler is not getting called.I replaced the TextBox with DataGridCell.Now, the event handler is called, but I can't edit the cell value.So, how do I solve this issue.
It is the CellEditingTemplate that is applied when the cell is put into edit mode, which is when the BeginningEdit event occurs, so you should add your TextBox to this one:
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value}"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox IsReadOnly="{Binding IsReadOnly}" BorderThickness="0"
Text="{Binding Value, UpdateSourceTrigger= LostFocus}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
I am using the following code to use a ComboBox inside my DataGridTemplateColumn:
<DataGridTemplateColumn Header="MyHeader" Width="50">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Width="Auto" Text="{Binding Path=MyVal}" ToolTip="{Binding MyDisplayName}"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox Width="50" Height="17" ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}},Path=DataContext.MyList}"
SelectedValuePath="MyValPath" SelectedValue="{Binding MySelectedVal}" SelectedItem="{Binding MyObject}" DisplayMemberPath="MyDisplayName"
FontSize="12" >
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
CellTemplate is for showing my text (custom text for selected value) and CellEditingTemplate contains my ComboBox which has the actual list.
When I select a value from my drop-down, I have to click on another part of the data grid to get DataGridDiagnosticCellEditEnding fired.
I want to get it fired as soon as I select a value or change a value from my ComboBox.
I tried adding the following code, but it didn't work:
<ComboBox Width="50" Height="17" ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}},Path=DataContext.MyList}"
SelectedValuePath="MyValPath" SelectedValue="{Binding MySelectedVal}" SelectedItem="{Binding MyObject, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="MyDisplayName" FontSize="12" >
Also I tried adding IsSynchronizedWithCurrentItem="True".
I gave a try to your code, and ve got a fix :
On the ComboBox of the DataGrid Column, subscribe to the closing event
Implement the event Handler and raise a routed Event.
CommitEditCommand belongs to the DataGrid class :
private void ComboBoxDropDownClosed(object sender, EventArgs e)
{
DataGrid.CommitEditCommand.Execute(datagrid1,datagrid1);
}
From there you fall in the DataGrid.CellEditting breakpoint
Here is the working solution : http://1drv.ms/1LFB5Gc
Regards
I have a WPF UI, with the following elements:
The checked checkbox is in a DataGridRow.
The rest is in the DataGridRowDetails. (It contains a smaller DataGrid)
What I want to get done is to bind the two (shown by red arrows) checkboxes together, so that when one is checked, the other also gets checked, and vice-versa.
I have already taken a look at these questions:
1) WPF Binding with 2 Checkboxes
But when I try this, the click handlers stop working. (The checkboxes stop working altogether)
2) wpf bindings between two checkboxes
When I use triggers, one checkbox triggers the other, and it inturn triggers the other, and goes on.. UI gets stuck.
Sample of my code:
<DataGrid x:Name="DataGrid1">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="SelectionCheckBox1" PreviewMouseLeftButtonDown="SelectionCheckBox1_PreviewMouseLeftButtonDown"
Loaded="SelectionCheckBox1_Loaded"
IsChecked="{Binding ElementName=HeaderCheckBox1, Path=IsChecked, Mode=TwoWay}">
</CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DockPanel LastChildFill="True">
<DataGrid>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<CheckBox x:Name="HeaderCheckBox1" PreviewMouseLeftButtonDown="HeaderCheckBox_PreviewMouseLeftButtonDown"
IsChecked="{Binding ElementName=SelectionCheckBox1, Path=IsChecked, Mode=TwoWay}">
</CheckBox>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
</DataGrid.Columns>
</DataGridTemplateColumn>
</DataGrid>
</DockPanel>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
Concern : Because the second Checkbox appears only after the row is selected ( which means the other checkbox is selected), I am unable to find the second checkbox through VisualTreeHelpers also.
Some idea even leading to a possible solution will be much appreciated.
i would suggest 2 way binding them to a property they both can access (somewhere inside the window for example) since with solution 1 you are creating an infinite loop
Checkbox 1: clicked => notify changed
Checkbox 2: changed => update => notify changed
Checkbox 1: changed => update => notify changed
...
You will need to create a property in ViewModel say that property is called IsSelected, then you have to bind both these checkboxes to that Property. And it should have its dataContext as your view model and not an Element.
IsChecked="{Binding IsSelected, Mode=TwoWay}"
If your VM is not data context then you will have to traverse to find VM and then bind to it
I am using ComboBox inside a DataGrid which is in CellEditingTemplate.
I insert the selected item to the textblock in same cell, which is in CellTemplate. the insertion will happen only when I move to next cell.
what I want is when I select item from the ComboBox it should insert it in the TextBlock
without moving to the next cell.
here is my xaml.
<DataGrid.Columns>
<DataGridTextColumn Header="Hours" Binding="{Binding time}" FontSize="14" FontWeight="Bold" IsReadOnly="True" Width="100"/>
<DataGridTemplateColumn Header="Monday" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel >
<TextBlock x:Name="mon" Text="{Binding Path=SelectedSubject}"></TextBlock>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!--<ComboBox x:Name="monday" Width="50" IsSynchronizedWithCurrentItem="true" Loaded="monday_Loaded" SelectionChanged="monday_SelectionChanged"></ComboBox>-->
<ComboBox x:Name="monday" Width="50" ItemsSource="{Binding Path=Subjects}" SelectedItem="{Binding Path=SelectedSubject}" IsSynchronizedWithCurrentItem="true" Loaded="monday_Loaded" SelectionChanged="monday_SelectionChanged"></ComboBox>
<ComboBox x:Name="staff" Width="50" Loaded="staff_Loaded"></ComboBox>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
Is it possible to do this?
If any one have any idea about how to do it please help me.
Sorry i have use above code but it was not giving perfect answer.
Use mouse leave event of monday combo box and commit edit in that event it will work fine. :)
private void monday_MouseLeave(object sender, MouseEventArgs e)
{
this.myGrid.CommitEdit();
}
If you add a name to your DataGrid you can access it from monday_SelectionChanged and commit the edits:
<Grid x:Name="myGrid" ....>
in the handler of the ComboBox selection changed event
private void monday_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
myGrid.CommitEdit();
// Rest of your implementation ....
}