Search code examples
c#wpfcheckboxmvvm

checkbox checked event trigger multiple time wpf mvvm


I got checkbox column in DataGrid that populating realtime download percentage in particular row, filtered by caseRefNo. i need to add checkbox changed event to perform some action. i used InvokeCommandAction to add the action to checkbox. I realize that when i click the checkbox for the first time, It is ok and trigger only one time. but there are two times triggered when i click second time on the same checkbox.For third time clicking the same checkbox, it triggered four time. quite scary and difficult to figure it out.

here is my viewmodel code

public class DataGridDownloadViewModel:BindableBase
    {
        public ObservableCollection<tblTransaction> TransList { get; private set; }
        public DispatcherTimer dispatchTimer = new DispatcherTimer();
        public CollectionView TransView { get; private set; }

        public DelegateCommand<object> CheckCommand { get; set; }

        private String _UpdatePer;
        public String UpdatePercentage
        {
            get { return _UpdatePer; }
            set { SetProperty(ref _UpdatePer, value); }
        }

        private string _caseId;
        public string CaseID
        {
            get { return _caseId; }
            set { SetProperty(ref _caseId, value); }
        }

        private string _isChecked;
        public string isChecked
        {
            get { return _isChecked; }
            set { SetProperty(ref _isChecked, value); }
        }

        private bool CanExecute(object args)
        {
            return true;
        }

        private void CheckBoxChecker(object args)
        {
            //Should Work Here
            // Totally not coming to this function
            CheckBox chk = (CheckBox)args;
            string thichintae = chk.Name.ToString();

            Console.WriteLine(thichintae);
        }      

        public DataGridDownloadViewModel(List<tblTransaction> model)
        {
            CheckCommand = new DelegateCommand<object>(CheckBoxChecker, CanExecute);

            dispatchTimer.Interval = TimeSpan.FromMilliseconds(3000); 
            dispatchTimer.Tick += dispatchTimer_Tick;
            BackGroundThread bgT = Application.Current.Resources["BackGroundThread"] as BackGroundThread;

            bgT.GetPercentChanged += (ss, ee) =>
            {
                UpdatePercentage = bgT.local_percentage.ToString();               
            };

            bgT.GetCaseID += (ss, ee) =>
            {
                CaseID = bgT.local_caseRef;
            };

            TransList =new ObservableCollection<tblTransaction>(model);
            TransView = GetTransCollectionView(TransList);
            TransView.Filter = OnFilterTrans;

            var tokenSource = new CancellationTokenSource();
            var token = tokenSource.Token;

            var cancellationTokenSource = new CancellationTokenSource();

            dispatchTimer.Start();

        }

        private void dispatchTimer_Tick(object sender, EventArgs e)
        {
            UpdateDataGrid();
        }       

        public void UpdateDataGrid()
        {           
                foreach (tblTransaction tran in TransList)
                {
                    if (tran.caseRefNo == CaseID)
                    {
                        tran.incValue = int.Parse(UpdatePercentage);

                    }
                    else
                    {
                        tran.incValue = tran.incValue;                      
                    }
                }

                TransView.Refresh();           
        }

        bool OnFilterTrans(object item)
        {
            var trans = (tblTransaction)item;
            return true;           
        }

        public CollectionView GetTransCollectionView(ObservableCollection<tblTransaction> tranList)
        {
            return (CollectionView)CollectionViewSource.GetDefaultView(tranList);
        }
    }

this is XAML for above view model.

<Window x:Class="EmployeeManager.View.DataGridDownload"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        Title="DataGridDownload" Height="600" Width="790">
    <Grid>
        <DataGrid HorizontalAlignment="Left" ItemsSource="{Binding TransView}" AutoGenerateColumns="False" Margin="10,62,0,0" VerticalAlignment="Top" Height="497" Width="762">
            <DataGrid.Columns>
                <DataGridTextColumn Header="caseRefNo" Binding="{Binding caseRefNo}" />
                <DataGridTextColumn Header="subjMatr" Binding="{Binding subjMatr}" />
                <DataGridTextColumn Header="Download %" Binding="{Binding incValue}" />
                <DataGridTemplateColumn>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <CheckBox  Name="abcdef"
                                Content="Please Select" IsChecked="{Binding Path=IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
                                <i:Interaction.Triggers>
                                    <i:EventTrigger EventName="Checked">
                                        <i:InvokeCommandAction CommandParameter="{Binding ElementName=abcdef}" Command="{Binding DataContext.CheckCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}" />
                                    </i:EventTrigger>
                                </i:Interaction.Triggers>
                            </CheckBox>
                        </DataTemplate>                        
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTemplateColumn>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Label Content="{Binding incValue,UpdateSourceTrigger=PropertyChanged}" Background="Red" Foreground="White" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
        <Label Content="{Binding UpdatePercentage}" HorizontalAlignment="Left" Background="Blue" Foreground="White" Margin="10,10,0,0" VerticalAlignment="Top" Width="338" Height="30">

        </Label>
        <Button Content="Button" HorizontalAlignment="Left" Margin="672,20,0,0" VerticalAlignment="Top" Width="75"/>

    </Grid>
</Window>

Here is my model

    public class tblTransaction
{
    public string caseRefNo { get;set;}
    public string subjMatr { get; set; }
    public int incValue { get; set; }
    public DateTime? longTime { get; set; }

    public bool IsSelected { get; set; }
}

This is picture of my form

enter image description here

Is it because of DispatcherTimer ? All suggestion are welcome.

df


Solution

  • enter image description here

    I think I left a comment in your previous question saying that wrapping your collection to CollectionView is quite smelly.

    Anyway, the TransView.Refresh(); is causing the problem in your code. TransView.Refresh will trigger the "Checked" event for each checked checkbox. The refresh basically asking the wpf engine re-populate all the data to your CollectionView and in turn, each checked checkbox will fire the checked event all over again.

    Try setting dispatchTimer.Interval to a much shorter time e.g. 300. You should be able to see the "tick" in checkbox keep flicking becoz of the TransView.Refresh.

    For real, I have no idea why you don't just bind your TransList to your DataGrid and called BeginInvoke on UpdateDataGrid method.

    To demo how ObservableCollection works I used the ObservableCollection to re-wrote some part of your code. At least make things better fall in line with MVVM model.

    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    
    public class MainViewModel : ViewModelBase
    {
        public ObservableCollection<TblTransaction> TransList { get; private set; }
        public DispatcherTimer DispatchTimer = new DispatcherTimer();        
    
        public MainViewModel()
        {
            var model = new ObservableCollection<TblTransaction>();
            for (int i = 0; i < 5; i++)
            {
                model.Add(new TblTransaction { CaseRefNo = i.ToString(), IncValue = i, LongTime = DateTime.Now, SubjMatr = i.ToString() });
                if (i == 3)
                    model[i].IsSelected = true;
            }               
    
            DispatchTimer.Interval = TimeSpan.FromMilliseconds(200); 
            DispatchTimer.Tick += dispatchTimer_Tick;
    
            TransList = model;
    
            DispatchTimer.Start();
        }
    
        private void dispatchTimer_Tick(object sender, EventArgs e)
        {
            UpdateDataGrid();
        }       
    
        public void UpdateDataGrid()
        {           
            var ran = new Random();
            foreach (var tran in TransList)
                tran.IncValue = ran.Next(0, 100);
        }        
    }
    
    public class TblTransaction : ViewModelBase
    {
        private string caseRefNo;
        private string subjMatr;
        private int incValue;
        private DateTime? longTime;
        private bool isSelected;
        public DelegateCommand<object> CheckCommand { get; set; }
    
        public TblTransaction()
        {
            CheckCommand = new DelegateCommand<object>(CheckBoxChecker, (p) => true);
        }
    
        private void CheckBoxChecker(object args)
        {
            //Should Work Here
            // Totally not coming to this function
            //CheckBox chk = (CheckBox)args;
            //string thichintae = chk.Name;
    
            Console.WriteLine(args);
        }
    
    
        public string CaseRefNo
        {
            get { return caseRefNo; }
            set
            {
                caseRefNo = value;
                OnPropertyChanged();
            }
        }
    
        public string SubjMatr
        {
            get { return subjMatr; }
            set
            {
                subjMatr = value;
                OnPropertyChanged();
            }
        }
    
        public int IncValue
        {
            get { return incValue; }
            set
            {
                incValue = value;
                OnPropertyChanged();
            }
        }
    
        public DateTime? LongTime
        {
            get { return longTime; }
            set
            {
                longTime = value;
                OnPropertyChanged();
            }
        }
    
        public bool IsSelected
        {
            get { return isSelected; }
            set
            {
                isSelected = value;
                OnPropertyChanged();
            }
        }
    }
    
    <Window x:Class="WpfTestProj.MainWindow"
        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:WpfTestProj"
        xmlns:interact="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        mc:Ignorable="d" 
        d:DataContext="{d:DesignInstance Type=local:MainViewModel, IsDesignTimeCreatable=False}"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DataGrid HorizontalAlignment="Left" ItemsSource="{Binding TransList}" AutoGenerateColumns="False" Margin="10,62,0,0" VerticalAlignment="Top" Height="497" Width="762">
            <DataGrid.Columns>
                <DataGridTextColumn Header="caseRefNo" Binding="{Binding CaseRefNo}" />
                <DataGridTextColumn Header="subjMatr" Binding="{Binding SubjMatr}" />
                <DataGridTextColumn Header="Download %" Binding="{Binding IncValue}" />
                <DataGridTemplateColumn>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <CheckBox
                                Content="Please Select" IsChecked="{Binding Path=IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
                                <interact:Interaction.Triggers>
                                    <interact:EventTrigger EventName="Checked">
                                        <interact:InvokeCommandAction CommandParameter="{Binding Path=CaseRefNo}" Command="{Binding Path=CheckCommand}" />
                                    </interact:EventTrigger>
                                </interact:Interaction.Triggers>
                            </CheckBox>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTemplateColumn>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Label Content="{Binding IncValue, UpdateSourceTrigger=PropertyChanged}" Background="Red" Foreground="White" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    
        <Button Content="Button" HorizontalAlignment="Left" Margin="672,20,0,0" VerticalAlignment="Top" Width="75"/>
    </Grid>