Set Property of UserControl in DataGrid? - c#

I'm working on a program for playing audio files and have ran into some issues with one of my DataGrids. Basically it's meant to allow the user change settings for it (Listen, repeat and volumes for example).
For this I have made my own UserControl, which is being dynamically added via DataTemplates in XAML. So first of all here's some context;
SoundFile.cs (public class)
Class that has file-related properties such as Name, Path, Category and Volumes.
SoundLibrary.cs (public static)
This is a static class which keeps track of all SoundFiles inside an ObservableCollection, this is the collection used to populate my DataGrids.
SoundControl.cs (UserControl)
This is my UserControl which consists of a few buttons, it has a property for SoundFile which is what I'm confused as to how to set and/or properly access.
This is how my DataGrid is defined;
<DataGrid
x:Name="MyDataGrid"
Margin="0,0,0,36"
AlternationCount="2"
AutoGenerateColumns="False"
IsReadOnly="True"
ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Header="Name" />
<DataGridTemplateColumn Header="Listen Volume">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Slider
Margin="5"
Maximum="100"
Minimum="0"
Value="{Binding DataContext.ListenVolume, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGridRow}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Settings">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:SoundControl x:Name="SoundController" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Progress">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ProgressBar
Maximum="100"
Minimum="0"
Visibility="Visible"
Value="50" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
And here's how I bind it in code behind;
MyDataGrid.ItemsSource = SoundLibrary.Library;
for (int i = 0; i < MyDataGrid.Items.Count; i++)
{
SoundFile sf = (SoundFile)MyDataGrid.Items.GetItemAt(i);
Debug.WriteLine(sf.FilePath);
}
Doing that I can access the SoundFile as expected, however I'm not sure how to access the SoundFile through the SoundControl.
The SoundControl class is very simple;
public partial class SoundControl : UserControl
{
public SoundFile Sound { get; set; } = new SoundFile();
public SoundControl()
{
InitializeComponent();
ListenButton.IsToggled = Sound.Listen;
RepeatButton.IsToggled = Sound.Repeat;
}
private void GridButton_MouseUp(object sender, MouseButtonEventArgs e)
{
if (sender == ListenButton)
{
Sound.Listen = ListenButton.IsToggled;
MainWindow mainWindow = (MainWindow)Window.GetWindow(this);
mainWindow.UnsavedChanges = true;
}
if (sender == RepeatButton)
{
Sound.Repeat = RepeatButton.IsToggled;
MainWindow mainWindow = (MainWindow)Window.GetWindow(this);
mainWindow.UnsavedChanges = true;
}
if (sender == FolderButton)
{
Debug.WriteLine(Sound.FilePath);
//Sound.OpenDirectory();
}
}
}
Any pointers in the right direction would be much appreciated, still relatively new to data binding and using DataGrids... Thanks in advance!

The Sound property of the SoundControl should be a bindable property, i.e. dependency property:
public static readonly DependencyProperty SoundProperty =
DependencyProperty.Register(
nameof(Sound), typeof(SoundFile), typeof(SoundControl));
public SoundFile Sound
{
get { return (SoundFile)GetValue(SoundProperty); }
set { SetValue(SoundProperty, value); }
}
You would bind it to the current SoundFile item of the DataGrid row like this:
<DataGridTemplateColumn Header="Settings">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:SoundControl Sound="{Binding}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Related

Cannot bind slider value in DataGridTemplateColumn

I am trying to create a DataGrid with two text columns and a template column as follows:
<UserControl
...
Name="TcpNtcpSliders"
...>
<DataGrid Name="MinTcpDataGrid" AutoGenerateColumns="False" Margin="2" Grid.Row="0"
ItemsSource="{Binding MinTcpRows, ElementName=TcpNtcpSliders, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<DataGrid.Columns>
<DataGridTextColumn Header="Structure ID" IsReadOnly="True" Width="Auto"
Binding="{Binding StructureId, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
<DataGridTextColumn Header="Min. TCP" IsReadOnly="True" Width="Auto" x:Name="MinTcpTextColumn"
Binding="{Binding MinTcpValue, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
<DataGridTemplateColumn Header="Set Min. Tcp" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Slider Minimum="0" Maximum="1"
Value="{Binding MinTcpValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</UserControl>
In the code behind I have
internal ObservableCollection<MinTcpRow> MinTcpRows
{
get { return (ObservableCollection<MinTcpRow>)GetValue(MinTcpRowsProperty); }
set { SetValue(MinTcpRowsProperty, value); }
}
internal static readonly DependencyProperty MinTcpRowsProperty =
DependencyProperty.Register("MinTcpRows", typeof(ObservableCollection<MinTcpRow>),
typeof(RtpOptimizationTcpNtcpSliders),
new PropertyMetadata(new ObservableCollection<MinTcpRow>(), MinTcpRowsPropertyChangedCallback));
private static void MinTcpRowsPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (RtpOptimizationTcpNtcpSliders)d;
control.MinTcpRows = (ObservableCollection<MinTcpRow>)e.NewValue;
control.CreateTcpNtcpLimits();
}
and the MinTcpRow class is defined like:
public class MinTcpRow
{
public string StructureId { get; }
public double MinTcpValue { get; set; }
public MinTcpRow(string structureId, double minTcpValue)
{
if (minTcpValue < 0 || minTcpValue > 1)
throw new ArgumentOutOfRangeException($"{nameof(minTcpValue)} must " +
$"be in range [0, 1], but was {minTcpValue}.");
StructureId = structureId ?? throw new ArgumentNullException(nameof(structureId));
MinTcpValue = minTcpValue;
}
}
Everything works as expected, except for the Slider value binding. I says cannot resolve symbol 'MinTcpValue'. How can I bind the slider's value correctly?
In order to see what happens I put a simplified version together.
In my mainwindow I have a datagrid:
<DataGrid Name="MinTcpDataGrid" AutoGenerateColumns="False"
ItemsSource="{Binding Rows}">
<DataGrid.Columns>
<DataGridTextColumn Header="Structure ID" IsReadOnly="True" Width="Auto"
Binding="{Binding StructureId, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
<DataGridTextColumn Header="Min. TCP" IsReadOnly="True" Width="Auto" x:Name="MinTcpTextColumn"
Binding="{Binding MinTcpValue, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
<DataGridTemplateColumn Header="Set Min. Tcp" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Slider Minimum="0" Maximum="1"
Value="{Binding MinTcpValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
MainWindowViewModel will supply my Rows collection:
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private ObservableCollection<MinTcpRow> rows = new ObservableCollection<MinTcpRow>();
public MainWindowViewModel()
{
rows.Add(new MinTcpRow ("A",0.1));
rows.Add(new MinTcpRow("B", 0.5));
rows.Add(new MinTcpRow("C", 0.9));
}
}
And MinTcpRow ( is faulted because it won't notify change ) but looks the same.
public class MinTcpRow
{
public string StructureId { get; }
public double MinTcpValue { get; set; }
public MinTcpRow(string structureId, double minTcpValue)
{
if (minTcpValue < 0 || minTcpValue > 1)
throw new ArgumentOutOfRangeException($"{nameof(minTcpValue)} must " +
$"be in range [0, 1], but was {minTcpValue}.");
StructureId = structureId ?? throw new ArgumentNullException(nameof(structureId));
MinTcpValue = minTcpValue;
And that works, no error anyhow. There is a memory leak waiting to catch you out because of not implementing inpc but I get no error like you show us. Are you somehow using a different version of MinTcpRow ?

How to solve the error in naming the control inside custom DataGrid?

I have created the custom data grid in seperate usercontrol called CustomDatagrid. I am using that custom data grid in another usercontrol named CustomUserControl. And I added textblock in the CustomUserControl. While I am trying to run the code, it is showing following error
Cannot set Name attribute value 'txtblock' on element 'TextBlock'.
'TextBlock' is under the scope of element 'SLMDatagrid', which already had a name registered when it was defined in another scope.
My first question is why this error is coming and how to solve this error?
And my requirement is I created one column for radio button. If I checked that RadioButton in particular row, one parameter(e.g Name) in itemsource should display in another column of the same row. If i changed the RadioButton selection, that parameter should not display for previous one but should display for current selected one.
I tried to add a DataTrigger for a Textblock. But it is not working.
Updated the code as below
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter"></BooleanToVisibilityConverter>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<GridControl:CustomDatagrid x:Name="slmGridTask" Style="{StaticResource DatagridStyle}">
<DataGrid.Columns>
<DataGridTemplateColumn Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<RadioButton Checked="RadioButton_Checked" IsChecked="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="txtblock" Visibility="{Binding IsChecked, Converter={StaticResource booleanToVisibilityConverter}}" Text="Name">
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</GridControl:CustomDatagrid>
In c# code
private bool isChecked=false;
public event PropertyChangedEventHandler PropertyChanged;
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs ("IsChecked"));
}
}
private void RadioButton_Checked(object sender, RoutedEventArgs e)
{
IsChecked = true;
}
Is there is any way to achieve this?
public ObservableCollection<Item> Items { get; set; } = new ObservableCollection<Item>();
public class Item : INotifyPropertyChanged
{
private bool? isChecked = true;
public event PropertyChangedEventHandler PropertyChanged;
public bool? IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsChecked"));
}
}
public string Text { get; set; }
}
Then your xaml would bind to your view model properties
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Items}">
<DataGrid.Resources>
<BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter"></BooleanToVisibilityConverter>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<RadioButton GroupName="group" IsChecked="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" Visibility="{Binding IsChecked, Converter={StaticResource booleanToVisibilityConverter}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Which gives something like:

Binding Combobox inside DataGrid

I have a DataGrid with 3 column inside: Name -DataGridTextColumn, Value-DataGridTextColumn, ParamJson - DataGridTemplateColumn with combobox and has a ICollectionView of ParameterGrid as a source.
There is a ViewModel that provides ICollectionView of ParameterGrid. ParameterGrid contains 4 parameters: Name, Value, ParamJson, SelectedParamJson.
I need to handle all rows of DataGrid, so i use the command that get all dataGrid as a Command Parameter and than iterate over DataGrid.Items (I could use directly ICollectionView?).
The question is How to bind properly SelectedItem in Combobox in DataGrid to SelectedParamJson?
Xaml:
<DataGrid Grid.Row="0"
x:Name="parametersGridView"
AutoGenerateColumns="False"
VerticalScrollBarVisibility="Visible"
CanUserAddRows="False"
ItemsSource="{Binding PropertiesGrid}"
Margin="5"
AlternatingRowBackground="LightGoldenrodYellow">
<DataGrid.Columns>
<DataGridTextColumn Header="Name"
Binding="{Binding Path=Name}"
IsReadOnly="True" />
<DataGridTextColumn Header="Value"
Binding="{Binding Path=Value}" />
<DataGridTemplateColumn Header="Parameter" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox MinWidth="100"
MaxWidth="150"
ItemsSource="{Binding Path=ParamsJson}"
SelectedItem="{Binding Path=SelectedParamJson, RelativeSource={RelativeSource AncestorType=DataGrid}}"
StaysOpenOnEdit="True"
IsEditable="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid > <DataGrid Grid.Row="0"
x:Name="parametersGridView"
AutoGenerateColumns="False"
VerticalScrollBarVisibility="Visible"
CanUserAddRows="False"
ItemsSource="{Binding PropertiesGrid}"
Margin="5"
AlternatingRowBackground="LightGoldenrodYellow">
<DataGrid.Columns>
<DataGridTextColumn Header="Name"
Binding="{Binding Path=Name}"
IsReadOnly="True" />
<DataGridTextColumn Header="Value"
Binding="{Binding Path=Value}" />
<DataGridTemplateColumn Header="Parameter" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=ParamsJson}"
SelectedItem="{Binding Path=SelectedParamJson, RelativeSource={RelativeSource AncestorType=DataGrid}}"
StaysOpenOnEdit="True"
IsEditable="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid >`
ViewModel
class DataGridViewModel : DependencyObject
{
public DataGridViewModel()
{
var data = new DataEmulator();
PropertiesGrid = CollectionViewSource.GetDefaultView(data.GetCommonParameters());
}
public ICollectionView PropertiesGrid
{
get { return (ICollectionView)GetValue(PropertiesGridProperty); }
set { SetValue(PropertiesGridProperty, value); }
}
public static readonly DependencyProperty PropertiesGridProperty =
DependencyProperty.Register("PropertiesGrid", typeof(ICollectionView), typeof(DataGridViewModel), new PropertyMetadata(null));
public ICommand TestCommand
{
get
{
return new RelayCommand
{
ExecuteAction = a =>
{
// here SelectedParamJson should be as Selected in combobox,
// but it is always null because of the wrong binding
},
CanExecutePredicate = p =>
{
return true;
}
};
}
}
}
public class ParameterGrid
{
public string Name { get; set; }
public string Value { get; set; }
public List<SamResult> ParamsJson { get; set; }
public SamResult SelectedParamJson { get; set; }
}
If you want to bind to the SelectedParamJson property that is defined in the same class as the ParamsJson property, you should not set the RelativeSource property to anything at all:
ItemsSource="{Binding Path=ParamsJson}"
SelectedItem="{Binding Path=SelectedParamJson}"
You should also move the ComboBox to the CellEditingTemplate of the DataGridTemplateColumn.
The thing is you have to define <DataGridTemplateColumn.CellEditingTemplate>, where you should do all bindings and <DataGridTemplateColumn.CellTemplate>, that present value when the cell is not active.
That seems to be strange that you could not bind everything inside <DataGridTemplateColumn.CellTemplate>, because you have to do an additional klick to activate combobox.
Xaml that works for me:
<DataGridTemplateColumn Header="Parameter" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=SelectedParamJson}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox MinWidth="100"
MaxWidth="150"
ItemsSource="{Binding Path=ParamsJson}"
SelectedItem="{Binding Path=SelectedParamJson}"
StaysOpenOnEdit="True"
IsEditable="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
your ancestor is not the datagrid its the datarow you want to get the datacontext from.
so you made a collection view of some source collection like list, this means every row in your Datagrid repesents a IJsonObject Object and there you should have your Collection for the ComboBox Column defined. Like a List that you can bind to the Column and in Turn you can have a IJsonProperty field that you can bind to the selected Item.
So then you should get a Combobox filled with yout List Items and if you select one of them the IJsonProerty Field will be set to the selected Item.
I hope its understandable since I dont really got a code snippet right now.

Combobox(inside a DataGrid) Binding to Observable List not loading

I have a ComboBox column inside a DataGrid. I need it to display values based on another WPF control, not in DataGrid. So, the values of ComboBox should change according to that control. I have created an ObservableCollection and binded it to the ComboBox but it does not display any values. For displaying dynamically, I have added DropOpenOpened event. But the ComboBox does not display any value. The list that populates the ComboBox is getting updated but its not displaying anything.
Below is the xaml code.
The DataGrid is bound to another List, whose values I fetch from DB.
<DataGrid x:Name="grid1" AutoGenerateColumns="False" Grid.Row="1" Grid.RowSpan="2" Grid.Column="1" Grid.ColumnSpan="2"
AlternatingRowBackground="Azure" AlternationCount="2" CanUserReorderColumns="True" CanUserResizeRows="True" CanUserSortColumns="True"
DataContext="attr">
<DataGrid.Columns>
<DataGridTextColumn Width="*" Binding="{Binding modify_user}" Header="Modified By" IsReadOnly="True"></DataGridTextColumn>
<DataGridTextColumn Width="*" Binding="{Binding modify_date}" Header="Modified Date" IsReadOnly="True"></DataGridTextColumn>
<DataGridTemplateColumn Header="Source" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding source_value}"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox x:Name="combo_source" ItemsSource="{Binding Path=sourceComboDropdown}"
DisplayMemberPath="desc" SelectedValuePath="id"
SelectedItem="{Binding source_value}"
SelectedValue="{Binding Path=source_value,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
DropDownOpened="combo_source_DropDownOpened"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Target value" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding target_value}"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding targetComboDropdown}"
DisplayMemberPath="desc" SelectedValuePath="id"
SelectedItem="{Binding target_value}"
SelectedValue="{Binding Path=target_value,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
DropDownOpened="ComboBox_DropDownOpened"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The class of observablecollection is:
public static ObservableCollection<Source> sourceComboDropdown =
new ObservableCollection<Source>();
public static ObservableCollection<Source> targetComboDropdown =
new ObservableCollection<Source>();
public class Source
{
public string id { get; set; }
public string desc { get; set; }
}
Where desc is the DisplayMember value and id is SelectedValue.
The class for DataGrid list is:
public static ObservableCollection<Attribute_Param> attr =
new ObservableCollection<Attribute_Param>();
public class Attribute_Param
{
public string source_value { get; set; }
public string target_value { get; set; }
public string modify_user { get; set; }
public DateTime modify_date { get; set; }
}
I have tried adding static resource. But since I need to dynamically update the values, I couldn't figure out a work around to use it.
I think I am missing something really small but cant figure out what.
I think it is unable to find the list sourceComboDropdown since the dataContext of the DataGrid is attr which does not CONTAIN sourceCombo drop down.
I would set my datagrid datacontext to the whole viewmodel. i.e DataGrid.DataContext=model in the constructor of the code behind.
Then I can bind the datagrid to attr using : ItemsSource="{Binding attr}" inside the DataGrid tag . Assuming attr is immediately in the VieWModel model. That way it should be able to detect the combo box items soruce which is now in the data context i.e model contains sourceDropDown .
If that doesnt work , try this :
<DataGridTemplateColumn >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate >
<ComboBox Loaded="LoadItemsSource"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
private void LoadItemsSource(object sender, RoutedEventArgs e)
{
ComboBox comboBox = sender as ComboBox;
comboBox.ItemsSource=model.sourceDropDown;
}

WPF DataGrid Cell does not leave editing mode

I am facing a strange problem with the WPF DataGrid. If you click the first cell, enter a value and then tab to the next cell, the first cell does not exit editing mode. I have reproduced the problem using a simplified version of the templates below:
<DataGrid Name="grid" HorizontalAlignment="Stretch" ItemsSource="{Binding Persons}" Margin="0,10,0,0" VerticalAlignment="Stretch" AutoGenerateColumns="False" CanUserAddRows="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="FirstName">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding FirstName}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding FirstName}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="LastName">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding LastName}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding LastName}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Age">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Age}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Age}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The dummy class definition is:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
The code behind is:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Persons = new ObservableCollection<Person>();
this.grid.CurrentCellChanged += grid_CurrentCellChanged;
this.grid.PreparingCellForEdit += grid_PreparingCellForEdit;
}
void grid_PreparingCellForEdit(object sender, DataGridPreparingCellForEditEventArgs e)
{
if (e.EditingElement != null)
SetFocusToTextBox(e.EditingElement);
}
void grid_CurrentCellChanged(object sender, EventArgs e)
{
((DataGrid)sender).BeginEdit();
}
public ObservableCollection<Person> Persons { get; set; }
void SetFocusToTextBox(object obj)
{
// Get all children and examine if the child is a TextBox
object obChild;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj as DependencyObject); i++)
{
obChild = VisualTreeHelper.GetChild(obj as DependencyObject, i);
if (obChild is TextBox)
{
((TextBox)obChild).Focus();
break;
}
else
SetFocusToTextBox(obChild);
}
}
Does anyone see what's wrong here? Are you able to reproduce the issue? Any help will be appreciated.
Thanks,
Bhanu
The DataGrid XAML definition is 100% correct. The problem must lie in code behind. My best guess is CurrentCellChanged. It could be raised two times. First when you leave a cell and secondly when a new cell is selected. If that is the case, you are putting the cell that lost focus back into editing mode, because you aren't checking the condition of the event.
You should be able to verify this with a simple debugging - put a break point at
((DataGrid)sender).BeginEdit() and count the number of times it gets called.
I personally use the following code for BeginEdit and it works flawlesly:
private void dataGrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
if (e.AddedCells.Count > 0 && !this.dataGrid.IsReadOnly)
{
this.dataGrid.BeginEdit();
}
}
This code works for me:
void grid_CurrentCellChanged(object sender, EventArgs e)
{
Dispatcher.BeginInvoke(new Action(() =>
{
((DataGrid)sender).BeginEdit();
}));
}

Categories