Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected behavior of DataGrid

Tags:

c#

wpf

xaml

I have a very complicated problem. I have tried to check everything and I have worked for near-about 6 hours on this problem. But I am not successful to solve this problem. So, here is the question.

Problem:

Initially when program loads up:

enter image description here

When I click on edit button on any row:

enter image description here

When I save that row:

enter image description here

When I click on edit button on another row having different Parent Group:

enter image description here

XAML looks like below :

<CollectionViewSource x:Key="GroupsViewSource" Source="{Binding Groups, UpdateSourceTrigger=PropertyChanged}">
    <CollectionViewSource.SortDescriptions>
        <scm:SortDescription PropertyName="GroupName"/>
    </CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<CollectionViewSource x:Key="ParentGroupsViewSource" Source="{Binding ParentGroups}">
    <CollectionViewSource.SortDescriptions>
        <scm:SortDescription PropertyName="GroupName"/>
    </CollectionViewSource.SortDescriptions>
</CollectionViewSource>

<DataGrid Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Source={StaticResource GroupsViewSource}}" 
      SelectedItem="{Binding SelectedGroup}" x:Name="dataGrid"
      AutoGenerateColumns="False" CanUserAddRows="False"
      SelectionMode="Single" SelectionUnit="FullRow" IsSynchronizedWithCurrentItem="True"
      EnableRowVirtualization="False" VirtualizingPanel.IsContainerVirtualizable="False" RowEditEnding="DataGrid_RowEditEnding">

    <DataGrid.Resources>
        <Style TargetType="{x:Type DataGridCell}" BasedOn="{StaticResource {x:Type DataGridCell}}">
            <EventSetter Event="PreviewMouseLeftButtonDown" Handler="DataGridCell_PreviewMouseLeftButtonDown" />
        </Style>
    </DataGrid.Resources>

    <i:Interaction.Triggers>
        <i:EventTrigger EventName="RowEditEnding">
            <i:InvokeCommandAction Command="{Binding DataGridRowEditEndingCommand}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>

    <DataGrid.Columns>

        <DataGridTemplateColumn Header="Group Name" Width="*" SortMemberPath="GroupName">
            <DataGridTemplateColumn.HeaderTemplate>
                <DataTemplate>
                    <Grid IsHitTestVisible="True">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>
                        <TextBlock Grid.Column="0" Text="{TemplateBinding Content}"/>
                        <!--FILTER EXPANDER-->
                        <Expander Grid.Column="1" IsHitTestVisible="True" VerticalAlignment="Top" Margin="30 0 0 0" ToolTip="Filter">
                            <Border IsHitTestVisible="True" BorderThickness="1" Margin="-160 5 0 0" MinWidth="200" Height="31" >
                                <TextBox Text="{Binding DataContext.SearchGroupName, ElementName=uc, UpdateSourceTrigger=PropertyChanged}" 
                                     TextChanged="SearchTextBox_TextChanged" ToolTip="Enter Group Name to search" FontSize="16" BorderThickness="1" />
                            </Border>
                        </Expander>
                    </Grid>
                </DataTemplate>
            </DataGridTemplateColumn.HeaderTemplate>

            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding GroupName}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
            <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <TextBox Text="{Binding GroupName}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellEditingTemplate>
        </DataGridTemplateColumn>

        <DataGridTemplateColumn Header="Parent Group" Width="*" SortMemberPath="ParentID">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding ParentID, Converter={StaticResource parentIDToGroupNameConverter}}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
            <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <ComboBox ItemsSource="{Binding Source={StaticResource ParentGroupsViewSource}}" 
                          DisplayMemberPath="GroupName"
                          SelectedValue="{Binding ParentID, Converter={StaticResource parentIDToGroupNameConverter}}" SelectedValuePath="GroupName"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellEditingTemplate>
        </DataGridTemplateColumn>

        <DataGridTemplateColumn Header="Edit" Width="50" IsReadOnly="True">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <Button x:Name="btnEdit" Style="{StaticResource ResourceKey=EditButton}" Height="35" Width="35" 
                            Visibility="{Binding DataContext.IsInEdit, 
                                                 RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, 
                                                 Converter={StaticResource boolToVisibilityInverseConverter}}" 
                            Click="EditButton_Click" 
                            Command="{Binding DataContext.EditCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"/>
                        <Button x:Name="btnSave" Grid.Row="1" Style="{StaticResource ResourceKey=SaveButton}" Height="35" Width="35" 
                            Visibility="{Binding DataContext.IsInEdit, 
                                                 RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, 
                                                 Converter={StaticResource boolToVisibilityConverter}}" 
                            Click="SaveButton_Click"
                            Command="{Binding DataContext.SaveCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"/>
                    </Grid>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>

        <DataGridTemplateColumn Header="Delete" Width="70" IsReadOnly="True">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <Button x:Name="btnDelete" Style="{StaticResource ResourceKey=DeleteButton}" Height="35" Width="35"
                            Visibility="{Binding DataContext.IsInEdit, 
                                                 RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, 
                                                 Converter={StaticResource boolToVisibilityInverseConverter}}" 
                            Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"/>
                        <Button x:Name="btnCancel" Grid.Row="1" Style="{StaticResource ResourceKey=CancelButton}" Height="35" Width="35" 
                            Visibility="{Binding DataContext.IsInEdit, 
                                                 RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, 
                                                 Converter={StaticResource boolToVisibilityConverter}}" 
                            Command="{Binding DataContext.CancelCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
                            Click="CancelButton_Click"/>
                    </Grid>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>

    </DataGrid.Columns>

</DataGrid>

Here is the code-Behind:

public partial class ListView : UserControl
{
    ERPLiteDBContext db = new ERPLiteDBContext();

    public ListView()
    {
        InitializeComponent();
    }

    private void DataGridCell_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        DependencyObject dep = (DependencyObject)e.OriginalSource;

        if (dep == null)
            return;

        while (dep != null && !(dep is DataGridCell))
        {
            dep = VisualTreeHelper.GetParent(dep);
        }

        if (dep == null)
            return;

        if (dep is DataGridCell)
        {
            if (!((DataGridCell)dep).IsReadOnly)
            {
                if (!((DataGridCell)dep).IsEditing)
                    e.Handled = true;
            }
        }

        while (dep != null && !(dep is DataGridRow))
        {
            dep = VisualTreeHelper.GetParent(dep);
        }

        if (dep == null)
            return;

        if (dep is DataGridRow)
        {
            ((DataGridRow)dep).IsSelected = true;
        }

        while (dep != null && !(dep is DataGrid))
        {
            dep = VisualTreeHelper.GetParent(dep);
        }

        if (dep == null)
            return;

        if (dep is DataGrid)
        {
            ((DataGrid)dep).Focus();
        }
    }

    private void EditButton_Click(object sender, RoutedEventArgs e)
    {
        int rowIndex = 0;

        DependencyObject dep = (DependencyObject)e.OriginalSource;
        while (dep != null && !(dep is DataGridCell))
        {
            dep = VisualTreeHelper.GetParent(dep);
        }

        if (dep == null)
            return;
        DataGridRow row = null;
        if (dep is DataGridCell)
        {
            while (dep != null && !(dep is DataGridRow))
            {
                dep = VisualTreeHelper.GetParent(dep);
            }

            row = (DataGridRow)dep;
            rowIndex = FindRowIndex(row);
        }

        while (dep != null && !(dep is DataGrid))
        {
            dep = VisualTreeHelper.GetParent(dep);
        }

        if (dep == null)
            return;

        DataGrid dg = (DataGrid)dep;

        dg.CurrentCell = new DataGridCellInfo(dg.Items[rowIndex], dg.Columns[0]);
        dg.BeginEdit();

        for (int column = 0; column <= dg.Columns.Count - 1; column++)
        {
            if (!(GetDataGridCell(new DataGridCellInfo(dg.Items[rowIndex], dg.Columns[column])).IsReadOnly))
            {
                GetDataGridCell(new DataGridCellInfo(dg.Items[rowIndex], dg.Columns[column])).IsEditing = true;
            }
        }

        var rows = GetDataGridRows(dg);

        foreach (DataGridRow r in rows)
        {
            if (!(r.IsEditing))
            {
                r.IsEnabled = false;
            }
        }
    }

    private void SaveButton_Click(object sender, RoutedEventArgs e)
    {
        int rowIndex = 0;

        DependencyObject dep = (DependencyObject)e.OriginalSource;
        while (dep != null && !(dep is DataGridCell))
        {
            dep = VisualTreeHelper.GetParent(dep);
        }

        if (dep == null)
            return;

        DataGridRow row = null;

        if (dep is DataGridCell)
        {

            while (dep != null && !(dep is DataGridRow))
            {
                dep = VisualTreeHelper.GetParent(dep);
            }

            row = (DataGridRow)dep;
            rowIndex = FindRowIndex(row);

        }

        while (dep != null && !(dep is DataGrid))
        {
            dep = VisualTreeHelper.GetParent(dep);
        }

        if (dep == null)
            return;

        DataGrid dg = (DataGrid)dep;

        dg.CommitEdit(DataGridEditingUnit.Row, true);

        for (int column = 0; column <= dg.Columns.Count - 1; column++)
        {
            if (!(GetDataGridCell(new DataGridCellInfo(dg.Items[rowIndex], dg.Columns[column])).IsReadOnly))
            {
                GetDataGridCell(new DataGridCellInfo(dg.Items[rowIndex], dg.Columns[column])).IsEditing = false;
            }
        }

        var rows = GetDataGridRows(dg);

        foreach (DataGridRow r in rows)
        {
            r.IsEnabled = true;
        }
    }

    private void CancelButton_Click(object sender, RoutedEventArgs e)
    {
        DependencyObject dep = (DependencyObject)e.OriginalSource;

        int rowIndex = 0;

        DataGridRow row = null;

        while (dep != null && !(dep is DataGridRow))
        {
            dep = VisualTreeHelper.GetParent(dep);
        }

        row = (DataGridRow)dep;
        rowIndex = FindRowIndex(row);

        while (dep != null && !(dep is DataGrid))
        {
            dep = VisualTreeHelper.GetParent(dep);
        }

        if (dep == null)
            return;

        DataGrid dg = (DataGrid)dep;

        var rows = GetDataGridRows(dg);

        dg.CancelEdit(DataGridEditingUnit.Row);

        for (int column = 0; column <= dg.Columns.Count - 1; column++)
        {
            if (!(GetDataGridCell(new DataGridCellInfo(dg.Items[rowIndex], dg.Columns[column])).IsReadOnly))
            {
                GetDataGridCell(new DataGridCellInfo(dg.Items[rowIndex], dg.Columns[column])).IsEditing = false;
            }
        }

        foreach (DataGridRow r in rows)
        {
            r.IsEnabled = true;
        }
    }

    private void DataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
    {
        DataGrid dg = (DataGrid)sender;
        foreach (DataGridRow row in GetDataGridRows(dg))
        {
            row.IsEnabled = true;
        }
    }

    private void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        if (dataGrid.SelectedItem != null)
        {
            dataGrid.ScrollIntoView(dataGrid.SelectedItem);
        }
    }

    public DataGridCell GetDataGridCell(DataGridCellInfo cellInfo)
    {
        var cellContent = cellInfo.Column.GetCellContent(cellInfo.Item);
        if (cellContent != null)
            return (DataGridCell)cellContent.Parent;

        return null;
    }

    private int FindRowIndex(DataGridRow row)
    {
        DataGrid dataGrid = ItemsControl.ItemsControlFromItemContainer(row) as DataGrid;

        int index = dataGrid.ItemContainerGenerator.IndexFromContainer(row);

        return index;
    }

    public IEnumerable<DataGridRow> GetDataGridRows(DataGrid grid)
    {
        var itemsSource = grid.ItemsSource as IEnumerable;
        if (null == itemsSource) yield return null;
        foreach (var item in itemsSource)
        {
            var row = grid.ItemContainerGenerator.ContainerFromItem(item) as DataGridRow;
            if (null != row) yield return row;
        }
    }
}

At Last here is the code for ViewModel:

public class ListViewModel : ViewModelBase
{
    ERPLiteDBContext db = new ERPLiteDBContext();

    public ListViewModel()
    {
        Groups = new ObservableCollection<Group>(db.Groups);
        ParentGroups = new ObservableCollection<Group>(db.Groups);

        EditCommand = new RelayCommand(Edit);
        SaveCommand = new RelayCommand(Save);
        DeleteCommand = new RelayCommand(Delete);
        CancelCommand = new RelayCommand(Cancel);
        DataGridRowEditEndingCommand = new RelayCommand(DataGridRowEditEnding);

        SearchGroupName = "";
        IsInEdit = false;
    }

    public RelayCommand EditCommand { get; set; }
    public RelayCommand SaveCommand { get; set; }
    public RelayCommand DeleteCommand { get; set; }
    public RelayCommand CancelCommand { get; set; }
    public RelayCommand DataGridRowEditEndingCommand { get; set; }

    private string _searchGroupName;
    public string SearchGroupName
    {
        get
        {
            return _searchGroupName;
        }
        set
        {
            if (value == null)
            {
                SearchGroupName = "";
            }
            else
            {
                _searchGroupName = value;
            }

            OnPropertyChanged("SearchGroupName");

            SelectedGroup = db.Groups.AsEnumerable().OrderBy(x => x.GroupName).Where(x => x.GroupName.StartsWith(SearchGroupName, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();

            if (SelectedGroup == null)
            {
                SelectedGroup = db.Groups.AsEnumerable().OrderBy(x => x.GroupName).Where(x => x.GroupName.Contains(SearchGroupName, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
            }

        }
    }

    private ObservableCollection<Group> _groups;
    public ObservableCollection<Group> Groups
    {
        get
        {
            return _groups;
        }
        set
        {
            _groups = value;
            OnPropertyChanged("Groups");
        }
    }

    private Group _selectedGroup;
    public Group SelectedGroup
    {
        get
        {
            return _selectedGroup;
        }
        set
        {
            _selectedGroup = value;
            OnPropertyChanged("SelectedGroup");

            if (value != null)
            {
                ParentGroups = new ObservableCollection<Group>(db.Groups.Where(x => x.GroupID != value.GroupID));
                ParentGroups.Add(new Group { GroupID = -1, GroupName = "Primary" });
            }
        }
    }

    private ObservableCollection<Group> _parentGroups;
    public ObservableCollection<Group> ParentGroups
    {
        get
        {
            return _parentGroups;
        }
        set
        {
            _parentGroups = value;
            OnPropertyChanged("ParentGroups");
        }
    }

    private Group _selectedParentGroup;
    public Group SelectedParentGroup
    {
        get
        {
            return _selectedParentGroup;
        }
        set
        {
            _selectedParentGroup = value;
            OnPropertyChanged("SelectedParentGroup");
        }
    }

    private bool _isInEdit;
    public bool IsInEdit
    {
        get
        {
            return _isInEdit;
        }
        set
        {
            _isInEdit = value;
            OnPropertyChanged("IsInEdit");
        }
    }

    private void Edit(object obj)
    {
        IsInEdit = true;
    }

    private void Save(object obj)
    {
        IsInEdit = false;
        SaveToDataBase();
    }

    private void Delete(object obj)
    {

    }

    private void Cancel(object obj)
    {
        IsInEdit = false;
    }

    private void DataGridRowEditEnding(object obj)
    {
        IsInEdit = false;
    }

    public void SaveToDataBase()
    {
        Group currentGroup = db.Groups.Where(x => x.GroupID == SelectedGroup.GroupID).FirstOrDefault();
        if (currentGroup != null)
        {
            currentGroup.GroupName = SelectedGroup.GroupName;
            if (SelectedGroup.ParentID == -1)
            {
                currentGroup.ParentID = null;
            }
            else
            {
                currentGroup.ParentID = SelectedGroup.ParentID;
            }

            db.SaveChanges();
        }
    }
}

I am not sure where the problem lies, so, I posted almost all the code here.

I have created a sample project that can be downloaded from the link below:

Project:

https://drive.google.com/file/d/0B5WyqSALui0bTTNsMm5ISHV3VEk/view?usp=sharing

DataBase:

https://drive.google.com/file/d/0B5WyqSALui0bTXVJanp4TE9iSGs/view?usp=sharing

Edit 1:

The original problem(as shown in the images) is solved

To solve the problem I removed CollectionViewSource and bound the ComboBox directly to the ViewModel Property. Now I get another error:

enter image description here

When I see InnerException:

enter image description here

I have searched the net for this error but then everywhere I can see that if I insert NULL value into the foreign Key column then I get this error. But my column is Nullable and I need nulls to be inserted.

edit 2:

I have solved the error mentioned in edit 1.

Added a new record as first record in the table called Primary. And removed all the coding related to Primary Group. Replaced NULLs in database with the primary group.

Now the only problem left is using a CollectionViewSource as the source of combobox to sort the data instead of Binding the combobox directly to the Property inside ViewModel. Can anybody answer this question????

like image 602
Vishal Avatar asked Nov 28 '14 13:11

Vishal


2 Answers

The Problem is solved. Please see Edit 1 and Edit 2 in question for half of the solution.

After Edit 2 I was not able to Sort the data in ComboBox using CollectionViewSource. So, I used OrderBy in my query. Now the data in ComboBox is sorted as expected. Now, my query looks like:

ParentGroups = new ObservableCollection<Group>(db.Groups.OrderBy(x => x.GroupName));

Now the DataGrid works as expected.

I have posted this solution because I think it might help someone in future.

Thanks to everybody to took interest in solving my problem.

Here you can find the working sample:

Project : https://drive.google.com/file/d/0B5WyqSALui0beC05VnpMY1hzV3c/view?usp=sharing

Database : https://drive.google.com/file/d/0B5WyqSALui0bTXVJanp4TE9iSGs/view?usp=sharing

like image 65
Vishal Avatar answered Oct 11 '22 17:10

Vishal


You are mixing Commands and event handlers - never a good idea. I believe I understand what you are trying to do. This will be the pure MVVM solution with no event handlers route.

Before we begin, this assumes that your DataGrid is bound to an ObservableCollection of ViewModel objects which represent the individual rows. This solves a lot of the hassle and complexity with RelativeSource bindings to parent ViewModel objects as you currently have in your XAML and simplifies the implementation of per row Commands (because the data and the Command are encapsulated within the same class if you use DelegateCommand - as you are using PRISM I am assuming you are using it everywhere).

Firstly you should add a style for DataGridCell.IsEditing property to bind it to an IsEditing boolean property on your ViewModel. Change your Edit Button to bind to a Command that sets IsEditing on your row ViewModel to true - the grid should react by going into Edit mode.

You might want to consider binding your edit templates to Edit specific values so that you don't overwrite your original data in the event of Cancelling Edit. Also remember to set the Binding to Mode=TwoWay with UpdateSourceTrigger=PropertyChanged

You can then add Interaction Event To Command mappers to the various Edit template controls such that if you hit Enter that it invokes the SaveCommand and ends edit mode by setting IsEditing on your ViewModel back to false.

like image 42
toadflakz Avatar answered Oct 11 '22 18:10

toadflakz