Home > database >  WPF Datagrid issue when adding rows (data not synced)
WPF Datagrid issue when adding rows (data not synced)

Time:10-31

I have a datagrid that I am populating with an object. Lets say customer and each customer holds an item. I have a button that adds a customer to the datagrid with a random item. If I keep adding customers to the datagrid to a point where the number of customers is more than the height of the datagrid (which causes the scroll to appear) the data beyond this point is not accurate until I press on it (to refresh it?). I tested this by making my datagrid height bigger, as soon as I start inserting rows that dont appear in my view (until I scrolldown) the data doesnt seemed to be synced properly. I suspect this is a sync/initialization problem.

My datagrid xaml:

    <DataGrid x:Name="DG" HorizontalAlignment="Left" Height="291" Margin="706,84,0,0" VerticalAlignment="Top" Width="300"
              ItemsSource="{Binding CustomersList}" AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Customer" Binding="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="*"/>

            <DataGridTemplateColumn Header="Item" Width="*">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <local:CustomComboBox SelectedItem="{Binding Item, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                            ItemsSource="{Binding DataContext.ItemsList, ElementName=MainWind}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>

CustomComboBox

    <Grid DataContext="{Binding ElementName=Root}">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="30"/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <!-- Image at the side of the ComboBox -->
    <Image Height="30" Width="30" Grid.Column="0"
           Source="{Binding SelectedItem.Type, Converter={StaticResource ImageConverter}}"/>

    <!-- Actual ComboBox -->
    <ComboBox x:Name="CustomCombo" Grid.Column="1" 
              IsTextSearchEnabled="False" IsReadOnly="True" KeyDown="CustomCombo_KeyDown"
              DropDownOpened="CustomCombo_DropDownOpened" DropDownClosed="CustomCombo_DropDownClosed"
              Loaded="CustomCombo_Loaded"
              IsEditable="True" TextSearch.TextPath="Name"
              Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
              SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
              ItemsSource="{Binding ItemsSource, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
        <ComboBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <!-- StackPanel consisting of an Image and TextBlock as ItemTemplate-->
                    <Image Height="30" Width="30"
                           Source="{Binding Path=Type, Converter={StaticResource ImageConverter}}"/>
                    <TextBlock Text="{Binding Path=Name}" VerticalAlignment="Center"/>
                </StackPanel>
            </DataTemplate>
        </ComboBox.ItemTemplate>
    </ComboBox>
</Grid>

I dont know the technical term of this problem to search for a solution.

CodePudding user response:

Here you go. github.com/melsawy93/WPFTestAppBug

There are a lot of things wrong with this Repository.
First, the repository itself was created incorrectly. It should contain ignore and attribute files. Due to their absence, a lot of extra files got into the repository, including the .vs, bin, obj folders. Their presence adds a lot of imaginary changes at each commit.
Try creating a new empty Repository via the Git Visul Studio Menu. And you will see the ignore and attribute files there. Copy them to your Repository. And delete the extra folders in it using the CitHub Web interface.

I will post here the changes that need to be made to the source code:

MainWindow.

<Window x:Class="WPFTestApp.MainWindow"
        ------------------
        ------------------
        DataContext="{DynamicResource vm}">
    <FrameworkElement.Resources>
        <vm:MainViewModel x:Key="vm"/>
    </FrameworkElement.Resources>
    <syncfusion:GridTemplateColumn.CellTemplate>
        <DataTemplate>
            <local:CustomComboBox SelectedItem="{Binding FruitorVegie}"
                ItemsSource="{Binding ItemsList, Source={StaticResource vm}}"/>
        </DataTemplate>
    </syncfusion:GridTemplateColumn.CellTemplate>
        private MainViewModel _vm;
        public MainWindow()
        {
            SelectedFruitsOrVegie = new ObservableCollection<IsFruitOrVegetable>();
            InitializeComponent();
            _vm = (MainViewModel)DataContext;
        }

CustomComboBox

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;

namespace WPFTestApp
{
    /// <summary>
    /// Interaction logic for CustomComboBox.xaml
    /// </summary>
    ///
    public partial class CustomComboBox : CustomComboBoxBase
    {
        public CustomComboBox()
        {
            InitializeComponent();
        }

    }
    public partial class CustomComboBoxBase : UserControl
    {
        #region States
        public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
                "ItemsSource",
                typeof(IEnumerable<IsFruitOrVegetable>),
                typeof(CustomComboBoxBase),
                new PropertyMetadata(null)
            );
        //private IsFruitOrVegetable[] defaultList = null;
        public IEnumerable<IsFruitOrVegetable> ItemsSource
        {
            get
            {
                return (IEnumerable<IsFruitOrVegetable>)GetValue(ItemsSourceProperty);
            }
            set => SetValue(ItemsSourceProperty, value);
        }

        public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register(
                "SelectedItem",
                typeof(IsFruitOrVegetable),
                typeof(CustomComboBoxBase),
                new FrameworkPropertyMetadata(null, (d, e) => ((CustomComboBoxBase)d).OnSelectedItemChanged(e)) { BindsTwoWayByDefault = true }
            );

        private void OnSelectedItemChanged(object e)
        {
            Text = SelectedItem?.Name;
        }

        public IsFruitOrVegetable SelectedItem
        {
            get
            {
                return (IsFruitOrVegetable)GetValue(SelectedItemProperty);
            }
            set
            {
                SetValue(SelectedItemProperty, value);
            }
        }


        /// <summary>
        /// Element name or filter text for the <see cref="ItemsSource"/> collection.
        /// </summary>
        public string Text
        {
            get => (string)GetValue(TextProperty);
            set => SetValue(TextProperty, value);
        }

        /// <summary><see cref="DependencyProperty"/> for property <see cref="Text"/>.</summary>
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register(
                nameof(Text),
                typeof(string),
                typeof(CustomComboBoxBase),
                new FrameworkPropertyMetadata(null, (d, e) => ((CustomComboBoxBase)d).OnTextChanged((string)e.NewValue)) { BindsTwoWayByDefault = true });

        private void OnTextChanged(string filterText)
        {
            if (itemsSourceViewSource.View is null)
                return;

            filterText = filterText?.Trim();

            if (string.IsNullOrEmpty(filterText))
            {
                itemsSourceViewSource.View.Filter = null;
            }
            else
            {
                itemsSourceViewSource.View.Filter = item => ((IsFruitOrVegetable)(item)).Name.IndexOf(filterText, StringComparison.OrdinalIgnoreCase) > -1;
            }
        }

        public CustomComboBoxBase()
        {
            DependencyPropertyDescriptor.FromProperty(CollectionViewSource.SourceProperty, typeof(CollectionViewSource))
                .AddValueChanged(itemsSourceViewSource, OnItemsSourceViewChanged);
            BindingOperations.SetBinding(
                itemsSourceViewSource,
                CollectionViewSource.SourceProperty,
                new Binding() { Path = new PropertyPath(ItemsSourceProperty), Source = this });
        }

        private void OnItemsSourceViewChanged(object sender, EventArgs e)
        {
            FilteredItemsSource = itemsSourceViewSource.View;
            OnTextChanged(Text);
        }

        /// <summary>
        /// The source for the filtered view of the ItemsSource collection.
        /// </summary>
        private CollectionViewSource itemsSourceViewSource = new CollectionViewSource();

        /// <summary>
        /// A filtered view of the ItemsSource collection.
        /// </summary>
        public ICollectionView FilteredItemsSource
        {
            get => (ICollectionView)GetValue(FilteredItemsSourceProperty);
            private set => SetValue(FilteredItemsSourcePropertyKey, value);
        }

        private static readonly DependencyPropertyKey FilteredItemsSourcePropertyKey =
            DependencyProperty.RegisterReadOnly(nameof(FilteredItemsSource), typeof(ICollectionView), typeof(CustomComboBoxBase), new PropertyMetadata(null));
        /// <summary><see cref="DependencyProperty"/> for property <see cref="FilteredItemsSource"/>.</summary>
        public static readonly DependencyProperty FilteredItemsSourceProperty = FilteredItemsSourcePropertyKey.DependencyProperty;

        #endregion

        #region Event Handlers
        private IsFruitOrVegetable lastSelectedItem;

        // Saves Last Selected Item when the drop down is opened
        protected void CustomCombo_DropDownOpened(object sender, EventArgs e)
        {
            ComboBox comboBox = sender as ComboBox;
            comboBox.IsReadOnly = false;


            if (SelectedItem != null && SelectedItem.Type != FoodType.None)
            {
                lastSelectedItem = SelectedItem;
            }
            Text = "";
        }

        // Validates if Selected Item is of Type None, then it will revert back to 
        // the last selected Item which is saved
        protected void CustomCombo_DropDownClosed(object sender, EventArgs e)
        {
            ComboBox comboBox = sender as ComboBox;
            comboBox.IsReadOnly = true;

            if (SelectedItem == null || SelectedItem.Type == FoodType.None)
            {
                if (lastSelectedItem != null)
                {
                    SelectedItem = lastSelectedItem;
                    Text = SelectedItem.Name;
                }
                else
                {
                    SelectedItem = null;
                    Text = "";
                }
            }
            else if (SelectedItem != null && SelectedItem.Type != FoodType.None)
            {
                Text = SelectedItem.Name;
            }
        }
        #endregion

        protected void CustomCombo_KeyDown(object sender, KeyEventArgs e)
        {
            ComboBox comboBox = sender as ComboBox;
            if (!comboBox.IsDropDownOpen && !(e.Key == Key.Enter || e.Key == Key.Escape))
            {
                comboBox.IsDropDownOpen = true;
            }
        }
    }
}
<local:CustomComboBoxBase x:Class="WPFTestApp.CustomComboBox"
             x:Name="Root"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WPFTestApp"
             xmlns:cnvrt="clr-namespace:WPFTestApp.Converter"
             xmlns:vm="clr-namespace:WPFTestApp.ViewModel"
             mc:Ignorable="d" 
             d:DesignHeight="30" d:DesignWidth="150">
    <FrameworkElement.Resources>
        <cnvrt:ImageConverter x:Key="ImageConverter"/>
    </FrameworkElement.Resources>
    <Grid DataContext="{Binding ElementName=Root}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="30"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <!-- Image at the side of the ComboBox -->
        <Image Height="30" Width="30" Grid.Column="0"
               Source="{Binding SelectedItem.Type, Converter={StaticResource ImageConverter}}"/>

        <!-- Actual ComboBox -->
        <ComboBox x:Name="CustomCombo" Grid.Column="1" 
                  IsTextSearchEnabled="False" IsReadOnly="True" KeyDown="CustomCombo_KeyDown"
                  DropDownOpened="CustomCombo_DropDownOpened" DropDownClosed="CustomCombo_DropDownClosed"
                  IsEditable="True" TextSearch.TextPath="Name"
                  Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
                  SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                  ItemsSource="{Binding FilteredItemsSource}">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <!-- StackPanel consisting of an Image and TextBlock as ItemTemplate-->
                        <Image Height="30" Width="30"
                               Source="{Binding Path=Type, Converter={StaticResource ImageConverter}}"/>
                        <TextBlock Text="{Binding Path=Name}" VerticalAlignment="Center"/>
                    </StackPanel>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
    </Grid>
</local:CustomComboBoxBase>

P.S. Keep in mind that I didn't fix all the bugs. Changed only what matters for the solution of the question in the top of this topic.

  • Related