Tuesday, March 13, 2012

WPF Datagrid Edit Row Toggle Button



Problem:

I have a WPF datagrid with some data, some of the fields are configurable.
I don`t want user to change data in datagrid unless he enters into Edit Mode for specific row.
User can edit only one row at the same time.


Solution:

DataGrid, edit mode locked
Edit Mode locked for all rows


DataGrid, edit mode unlocked
Edit Mode unlocked for second row



Window1.xaml:
<DataGridTemplateColumn Header="Mode" 
                    MinWidth="50" 
                    MaxWidth="50"
                    CellStyle="{StaticResource CenterCellStyle}"
                    SortMemberPath="EditMode">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ToggleButton x:Name="Edit_ToggleButton"
                            IsThreeState="False"
                            Width="28" Height="28"
                            IsChecked="{Binding EditMode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                            Visibility="{Binding EditButtonVisibility}"
                            Unchecked="Edit_ToggleButton_Unchecked">
                <Image>
                    <Image.Style>
                        <Style TargetType="{x:Type Image}">
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding EditMode}" Value="false">
                                    <Setter Property="Source" Value="Icons/Locked.ico"/>
                                </DataTrigger>
                                <DataTrigger Binding="{Binding EditMode}" Value="true">
                                    <Setter Property="Source" Value="Icons/Unlocked.ico"/>
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </Image.Style>
                </Image>
            </ToggleButton>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
As you can see IsChecked property of the toggle button is binded to EditMode property of the Model class. Toggle button contains an image. We change the image accordingly to the EditMode state. 
By the way the icons I took from Icon FinderDelete Range is the only configurable field, so its IsEnabled property is also binded to EditMode. Thus when EditMode is false then the control is disabled. 
I bind Visibility property of the toggle button to EditButtonVisibility property of the Model class. 

Model.cs:
public bool EditMode
{
 get
 {
  return this._editMode;
 }
 set
 {
  this._editMode = value;               
  this.NotifyPropertChanged("EditMode");
 }
}       

public string EditButtonVisibility
{
 get
 {
  return _editButtonVisibility;
 }
 set
 {
  if (_editButtonVisibility != value)
   _editButtonVisibility = value;
  NotifyPropertChanged("EditButtonVisibility");
 }
}
Now to the tricky part.
I want constrain user to edit only one row at the same time.

How to ensure that when user clicks on toggle button other toggle buttons disappear?
So that`s how we do it:
The Model class implements INotifyPropertyChanged interface, it means we can track any changes made to Model`s members. 
I have ViewModel class that contains ObservableCollection of Model objects.
This collection is actually binded to the ItemSource property of the datagrid.
In other words each row of the datagrid is one object of Model class.
We need to subscribe to PropertyChanged event for each object of the collection in order to catch one when user clicks on specific toggle button.


ViewModel.cs:
public ViewModel
{
 LoadData();
 foreach (var model in ModelCollection)
  model.PropertyChanged += new PropertyChangedEventHandler(Model_PropertyChanged);
}       

void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
 Model model = (Model)sender;
 string property = e.PropertyName;
 if (property == "EditMode")
 {
  if (model.EditMode)
  {
   foreach (var m in ModelCollection)
   {
    if (m != model)
     m.EditButtonVisibility = VisibilityModes.Hidden.ToString();
   }
  }
  else
  {
   foreach (var m in ModelCollection)
   {
    if (m != model)
     m.EditButtonVisibility = VisibilityModes.Visible.ToString();
   }
  }
 }
}
In PropertyChanged handler we catching event of "EditMode" type.
If user unlocked some row then we setting VisibiltyMode to Hidden for all other rows.
If user locked back the row then all the rows become visible.


That`s how I solved the problem. 
I`m pretty sure there is exist more elegant solution. 
If you have one, please share with me in comments section.

3 comments:

  1. Nice topic.
    Thanks!

    ReplyDelete
  2. Thanks
    Could you post a link to the project source for the above?
    I am particularly interested in the colours/styles you set for the datagrid
    and its column headers and cells.

    ReplyDelete
    Replies
    1. Hi Mike.
      Unfortunately I can't share the whole project code because of copyright subject, but I will share the Styles that were used in this project. Hope it will help you.
      Look for the updates in WPF Styles category: http://www.codearsenal.net/search/label/WPF%20Styles

      Delete