INotifyPropertyChanged 在 Resettable 视图模型中不起作用

INotifyPropertyChanged does not work in Resettable view model

我尝试使用最初由 Nish Nishant 在 https://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=158591 中发布的可重置视图模型 但是不知何故,当我执行 DeleteBahanExecute 方法时,INotifyPropertyChanged 不适用于 LabelContent 和 OkCancelButtonVis 属性。有人知道为什么吗?这是 MainWindow.xaml

<Window x:Class="XLMPIS.MainWindow"
        xmlns:local="clr-namespace:XLMPIS"
        xmlns:vm="clr-namespace:XLMPIS.ViewModel"
        mc:Ignorable="d"
        Loaded="Window_Loaded"
        Title="MainWindow" Height="800" Width="800">
    <TabControl>
        <TabItem Header="Inventory">
            <StackPanel DataContext="{Binding Source={x:Static vm:ViewModels.BahanViewModel}}">
                <Grid Margin="0 10 0 0">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="250"/>
                        <ColumnDefinition Width="250"/>
                    </Grid.ColumnDefinitions>
                    <ComboBox Grid.Column="0" ItemsSource="{Binding TipeBahans}" Width="250" HorizontalAlignment="left" SelectedItem="{Binding SelTipeBahan, Mode=TwoWay}" DisplayMemberPath="NamaTipeBahanGsm" Padding="2"/>
                    <ComboBox Grid.Column="1" ItemsSource="{Binding Suppliers}" Width="250" HorizontalAlignment="left" SelectedItem="{Binding SelSupplier, Mode=TwoWay}" DisplayMemberPath="NamaSupplier" Padding="2"/>
                </Grid>

                <Grid Margin="0 10 0 0">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="90"/>
                        <ColumnDefinition Width="50"/>
                        <ColumnDefinition Width="50"/>
                        <ColumnDefinition Width="50"/>
                        <ColumnDefinition Width="50"/>
                        <ColumnDefinition Width="50"/>
                        <ColumnDefinition Width="40"/>
                        <ColumnDefinition Width="40"/>
                        <ColumnDefinition Width="50"/>
                        <ColumnDefinition Width="40"/>
                        <ColumnDefinition Width="40"/>
                        <ColumnDefinition Width="40"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Column="0" Text="Huruf Kode Bahan" HorizontalAlignment="Right" Padding="2"/>
                    <TextBox Text="{Binding HurufKodeBahan, Mode=TwoWay}" Grid.Column="1" Background="{Binding TBBackground}" IsReadOnly="{Binding IsReadOnly}" IsReadOnlyCaretVisible="{Binding IsReadOnly}" PreviewTextInput="LetterValidationTextBox" Padding="2"/>
                    <TextBlock Grid.Column="2" Text="No. Awal" HorizontalAlignment="Right" Padding="2"/>
                    <TextBox Text="{Binding NoAwal, Mode=TwoWay}" Grid.Column="3" Background="{Binding TBBackground}" IsReadOnly="{Binding IsReadOnly}" IsReadOnlyCaretVisible="{Binding IsReadOnly}" PreviewTextInput="IntegerValidationTextBox" Padding="2"/>
                    <TextBlock Grid.Column="4" Text="No. Akhir" HorizontalAlignment="Right" Padding="2"/>
                    <TextBox Text="{Binding NoAkhir, Mode=TwoWay}" Grid.Column="5" Background="{Binding TBBackground}" IsReadOnly="{Binding IsReadOnly}" IsReadOnlyCaretVisible="{Binding IsReadOnly}" PreviewTextInput="IntegerValidationTextBox" Padding="2"/>
                    <TextBlock Grid.Column="6" Text="Lebar" HorizontalAlignment="Right" Padding="2"/>
                    <TextBox Text="{Binding NewLebar, Mode=TwoWay}" PreviewTextInput="NumberValidationTextBox" Grid.Column="7" Background="{Binding TBBackground}" IsReadOnly="{Binding IsReadOnly}" IsReadOnlyCaretVisible="{Binding IsReadOnly}" Padding="2"/>
                    <TextBlock Grid.Column="8" Text="Panjang" HorizontalAlignment="Right" Padding="2"/>
                    <TextBox Text="{Binding NewPanjang, Mode=TwoWay}" PreviewTextInput="NumberValidationTextBox" Grid.Column="9" Background="{Binding TBBackground}" IsReadOnly="{Binding IsReadOnly}" IsReadOnlyCaretVisible="{Binding IsReadOnly}" Padding="2"/>
                    <TextBlock Grid.Column="10" Text="Sisa" HorizontalAlignment="Right" Padding="2"/>
                    <TextBox Text="{Binding NewSisa, Mode=TwoWay}" PreviewTextInput="NumberValidationTextBox" Grid.Column="11" Background="{Binding TBBackground}" IsReadOnly="{Binding IsReadOnly}" IsReadOnlyCaretVisible="{Binding IsReadOnly}" Padding="2"/>
                </Grid>
                <Grid Margin="0 10 0 0">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="60"/>
                        <ColumnDefinition Width="200"/>
                        <ColumnDefinition Width="80"/>
                        <ColumnDefinition Width="120"/>
                    </Grid.ColumnDefinitions>

                    <TextBlock Grid.Column="0" Text="Surat Jalan" HorizontalAlignment="Right" Padding="2"/>
                    <TextBox Text="{Binding NewSuratJalan, Mode=TwoWay}" Grid.Column="1" Background="{Binding TBBackground}" IsReadOnly="{Binding IsReadOnly}" IsReadOnlyCaretVisible="{Binding IsReadOnly}" Padding="2"/>
                    <TextBlock Grid.Column="2" Text="Tanggal Masuk" Padding="2"/>
                    <DatePicker SelectedDate="{Binding NewTanggalMasuk}" Grid.Column="3" Height="25" HorizontalAlignment="Left" VerticalAlignment="Top" Width="115"/>
                </Grid>
                <Grid Margin="0 10 0 0">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="80"/>
                        <ColumnDefinition Width="70"/>
                    </Grid.ColumnDefinitions>
                    <Button Grid.Column="0" Content="Add New Bahan" Width="100" Command="{Binding AddNewBahanCommand}" Padding="2"/>
                    <Button Grid.Column="1" Margin="10 0 0 0" Content="Delete Bahan" Width="90" Command="{Binding DeleteBahanCommand}" Padding="2"/>
                    <Button Grid.Column="2" Margin="10 0 0 0" Content="Refresh" Width="90" Command="{Binding ResetCommand}" Padding="2"/>
                    <Button Grid.Column="3" Margin="20 0 0 0" Content="Ok" Visibility="{Binding OkCancelButtonVis,Mode=TwoWay}" HorizontalAlignment="Left" Width="60" Command="{Binding OkCommand}" Padding="2"/>
                    <Button Grid.Column="4" Margin="10 0 0 0" Content="Cancel" Visibility="{Binding OkCancelButtonVis,Mode=TwoWay}" HorizontalAlignment="Left" Width="60" Command="{Binding CancelCommand}" Padding="2"/>
                </Grid>
                <Grid Margin="0 10 0 0">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="250"/>
                        <ColumnDefinition Width="50"/>
                        <ColumnDefinition Width="50"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Column="0" Text="Nama Tipe Bahan" Padding="2"/>
                    <TextBox Text="{Binding NewNamaTipeBahan, Mode=TwoWay}" Grid.Column="1" Background="{Binding TBBackground}" IsReadOnly="{Binding IsReadOnly}" IsReadOnlyCaretVisible="{Binding IsReadOnly}" Padding="2"/>
                    <TextBlock Grid.Column="2" Text="Gsm" HorizontalAlignment="Right" Padding="2"/>
                    <TextBox Text="{Binding NewGsm, Mode=TwoWay}" PreviewTextInput="NumberValidationTextBox" Grid.Column="3" Background="{Binding TBBackground}" IsReadOnly="{Binding IsReadOnly}" IsReadOnlyCaretVisible="{Binding IsReadOnly}" Padding="2"/>
                </Grid>
                <Button Margin="0 10 0 0" Content="Add New Tipe Bahan" HorizontalAlignment="Left" Width="120" Command="{Binding AddNewTipeBahanCommand}" Padding="2"/>
                <Label Margin="0 10 0 0" Content="{Binding LabelContent, Mode=TwoWay}"/>
            </StackPanel>
        </TabItem>

这是视图模型代码。我删除了一些 "using" 行。

using XLMPIS.Model;
using XLMPIS.Command;

namespace XLMPIS.ViewModel
{
    public class BahanViewModel : INotifyPropertyChanged
    {
        public BahanViewModel()
        {
            SelTipeBahan = new TipeBahan();
            TipeBahans = new ObservableCollection<TipeBahan>();
            Suppliers = new ObservableCollection<Supplier>();
            SelSupplier = new Supplier();
            SqlDbConnect sdc = new SqlDbConnect();
            DataSet ds = new DataSet();
            DataTable dt = new DataTable();
            sdc.SqlQuery("select * from TTipeBahan");
            ds = sdc.QueryEx("TTipeBahan");
            dt = ds.Tables["TTipeBahan"];
            int tmpIndex = 0;
            foreach (DataRow dr in dt.Rows)
            {
                TipeBahans.Add(new TipeBahan());
                TipeBahans[tmpIndex].TipeBahanID = (int)dr["TipeBahanID"];
                TipeBahans[tmpIndex].NamaTipeBahan = dr["NamaTipeBahan"].ToString();
                TipeBahans[tmpIndex].Gsm = (int)dr["Gsm"];
                tmpIndex++;
            }
            SelTipeBahan = TipeBahans[SELECTED_INDEX];

            sdc.SqlQuery("select NamaSupplier,SupplierID from TSupplier");
            ds = sdc.QueryEx("TSupplier");
            dt = ds.Tables["TSupplier"];
            tmpIndex = 0;
            foreach (DataRow dr in dt.Rows)
            {
                Suppliers.Add(new Supplier());
                Suppliers[tmpIndex].SupplierID = (int)dr["SupplierID"];
                Suppliers[tmpIndex].NamaSupplier= dr["NamaSupplier"].ToString();
                tmpIndex++;
            }
            SelSupplier = Suppliers[SELECTED_INDEX];

            sdc.SqlDbClose();

            NewTanggalMasuk = DateTime.Today;
            IsReadOnly = false;//not read only
            TBBackground = new SolidColorBrush(Colors.White); //white textbox
            OkCancelButtonVis = "Hidden";
            LabelContent = "test";
        }

        private const int SELECTED_INDEX = 0;

        private ObservableCollection<Supplier> _suppliers;

        public ObservableCollection<Supplier> Suppliers
        {
            get { return _suppliers; }
            set { _suppliers = value; }
        }

        private string _noawal;

        public string NoAwal
        {
            get { return _noawal; }
            set { _noawal = value; }
        }

        private string _noakhir;

        public string NoAkhir
        {
            get { return _noakhir; }
            set { _noakhir = value; }
        }

        private string _okcancelbuttonvis;

        public string OkCancelButtonVis
        {
            get { return _okcancelbuttonvis; }
            set { _okcancelbuttonvis = value; FirePropertyChanged("OkCancelButtonVis"); }
        }

        private string _labelcontent;

        public string LabelContent
        {
            get { return _labelcontent; }
            set { _labelcontent = value; FirePropertyChanged("LabelContent"); }
        }

        private string _hurufkodebahan;

        public string HurufKodeBahan
        {
            get { return _hurufkodebahan; }
            set { _hurufkodebahan = value; }
        }

        private string _newsuratjalan;

        public string NewSuratJalan
        {
            get { return _newsuratjalan; }
            set { _newsuratjalan = value; }
        }

        private string _newgsm;

        public string NewGsm
        {
            get { return _newgsm; }
            set { _newgsm = value; }
        }

        private string _newnamatipebahan;

        public string NewNamaTipeBahan
        {
            get { return _newnamatipebahan; }
            set { _newnamatipebahan = value; }
        }

        private string _newlebar;

        public string NewLebar
        {
            get { return _newlebar; }
            set { _newlebar = value; }
        }

        private string _newpanjang;

        public string NewPanjang
        {
            get { return _newpanjang; }
            set { _newpanjang = value; }
        }

        private string _newsisa;

        public string NewSisa
        {
            get { return _newsisa; }
            set { _newsisa = value; }
        }

        private DateTime _newtanggalmasuk;

        public DateTime NewTanggalMasuk
        {
            get { return _newtanggalmasuk; }
            set { _newtanggalmasuk = value; }
        }

        private Supplier _selsupplier;

        public Supplier SelSupplier
        {
            get { return _selsupplier; }
            set { _selsupplier = value; }
        }

        private TipeBahan _seltipebahan;

        public TipeBahan SelTipeBahan
        {
            get { return _seltipebahan; }
            set
            {
                _seltipebahan = value;
                FirePropertyChanged("SelTipeBahan");


        }
        }
        private ObservableCollection<TipeBahan> _tipebahans;

        public ObservableCollection<TipeBahan> TipeBahans
        {
            get { return _tipebahans; }
            set { _tipebahans = value; }
        }

        private SolidColorBrush _tbbackground;

        public SolidColorBrush TBBackground
        {
            get { return _tbbackground; }
            set { _tbbackground = value; FirePropertyChanged("TBBackground"); }//NotifyPropertyChanged("TBBackground"); }
        }

        private bool _isreadonly;

        public bool IsReadOnly
        {
            get { return _isreadonly; }
            set { _isreadonly = value; FirePropertyChanged("IsReadOnly"); }
            //NotifyPropertyChanged("IsReadOnly"); }
        }

        private ICommand _addnewbahancommand;
        public ICommand AddNewBahanCommand
        {
            get
            {
                if (_addnewbahancommand == null)
                {
                    _addnewbahancommand = new RelayCommand(AddNewBahanExecute, CanAddNewBahanExecute, false);
                }
                return _addnewbahancommand;
            }
        }

        private void AddNewBahanExecute(object parameter)
        {

        }

        private bool CanAddNewBahanExecute(object parameter)
        {
            if (string.IsNullOrEmpty(NewLebar)|| string.IsNullOrEmpty(NewPanjang)|| string.IsNullOrEmpty(HurufKodeBahan)|| string.IsNullOrEmpty(NoAwal) || string.IsNullOrEmpty(NoAkhir) || string.IsNullOrEmpty(NewSuratJalan))
                return false;
            else
                return true;
        }

        private ICommand _deletebahancommand;
        public ICommand DeleteBahanCommand
        {
            get
            {
                if (_deletebahancommand == null)
                {
                    _deletebahancommand = new RelayCommand(DeleteBahanExecute, CanDeleteBahanExecute, false);
                }
                return _deletebahancommand;
            }
        }

        private void DeleteBahanExecute(object parameter)
        {
            OkCancelButtonVis = "Visible"; //this is where the user interface failed to show the Ok and Cancel Button
            LabelContent = "Delete " + HurufKodeBahan + "-" + NoAwal + " to " + NoAkhir +" ?";//This is where the Label Content failed to show up

        }

        private bool CanDeleteBahanExecute(object parameter)
        {
            if (string.IsNullOrEmpty(HurufKodeBahan) || string.IsNullOrEmpty(NoAwal) || string.IsNullOrEmpty(NoAkhir))
                return false;
            else
                return true;
        }

        private ICommand _addnewtipebahancommand;
        public ICommand AddNewTipeBahanCommand
        {
            get
            {
                if (_addnewtipebahancommand == null)
                {
                    _addnewtipebahancommand = new RelayCommand(AddNewTipeBahanExecute, CanAddNewTipeBahanExecute, false);
                }
                return _addnewtipebahancommand;
            }
        }

        private void AddNewTipeBahanExecute(object parameter)
        {

        }

        private bool CanAddNewTipeBahanExecute(object parameter)
        {
            if (string.IsNullOrEmpty(NewNamaTipeBahan) || string.IsNullOrEmpty(NewGsm) )
                return false;
            else
                return true;
        }

        private ICommand _okcommand;
        public ICommand OkCommand
        {
            get
            {
                if (_okcommand == null)
                {
                    _okcommand = new RelayCommand(OkExecute, CanOkExecute, false);
                }
                return _okcommand;
            }
        }

        private void OkExecute(object parameter)
        {
            SqlDbConnect sdc = new SqlDbConnect();

            DataSet ds = new DataSet();
            DataTable dt = new DataTable();
            DataRow dr;
            string tmpKodeBahan;
            int tmpNo, tmpCount;
            tmpNo = int.Parse(NoAwal);
            while (tmpNo <= int.Parse(NoAkhir))
            {
                tmpKodeBahan = HurufKodeBahan + "-" + tmpNo.ToString();
                sdc.SqlQuery("select TmpCount=count(*) from TBahan where Replace(Replace(KodeBahan,'-',''),' ','')= Replace(Replace('" + tmpKodeBahan + "','-',''),' ','')");
                ds = sdc.QueryEx("TBahan");
                dt = ds.Tables["TBahan"];
                dr = dt.Rows[0];
                tmpCount = (int)dr["TmpCount"];
                if (tmpCount > 0)
                {
                    sdc.SqlQuery("delete from TBahan where Replace(Replace(KodeBahan,'-',''),' ','')=Replace(Replace('" + tmpKodeBahan + "','-',''),' ','')");
                    sdc.NonQueryEx();
                }
                tmpNo++;
            }

            LabelContent = "Delete is done";
            sdc.SqlDbClose();
            OkCancelButtonVis = "Hidden";
        }

        private bool CanOkExecute(object parameter)
        {
            return true;

        }

        private ICommand _cancelcommand;
        public ICommand CancelCommand
        {
            get
            {
                if (_cancelcommand == null)
                {
                    _cancelcommand = new RelayCommand(CancelExecute, CanCancelExecute, false);
                }
                return _cancelcommand;
            }
        }

        private void CancelExecute(object parameter)
        {
            //hide ok cancel button
            OkCancelButtonVis = "Hidden";
            LabelContent = "";

        }

        private bool CanCancelExecute(object parameter)
        {
            return true;
        }
        public event PropertyChangedEventHandler PropertyChanged;

        private void FirePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;

            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

这是 ViewModels Class

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;

namespace XLMPIS.ViewModel
{
    public class ViewModels 
    {
        public ViewModels()
        {

        }
        private static object inventoryViewModel = new NSViewModelExtensions.ResettableViewModel(new InventoryViewModel());

        public static object InventoryViewModel
        {
            get
            { 
                return ViewModels.inventoryViewModel;
            }

        }

        private static object bahanViewModel = new NSViewModelExtensions.ResettableViewModel(new BahanViewModel());

        public static object BahanViewModel
        {
            get
            {
                return ViewModels.bahanViewModel;
            }

        }

        private static object spkdetailViewModel = new NSViewModelExtensions.ResettableViewModel(new SPKDetailViewModel());

        public static object SPKDetailViewModel
        {
            get
            {
                return ViewModels.spkdetailViewModel;
            }

        }
    }
}

这是 ResettableViewModel Class

   using System;
   using System.ComponentModel;
   using System.Windows.Input;

   namespace NSViewModelExtensions
   {
       [TypeDescriptionProvider(typeof(ResettableViewModelTypeDescriptionProvider))]
       sealed class ResettableViewModel : INotifyPropertyChanged
       {
           public event PropertyChangedEventHandler PropertyChanged;

           private void FirePropertyChanged(string propertyName)
           {
               PropertyChangedEventHandler handler = PropertyChanged;

               if (handler != null)
               {
                   handler(this, new PropertyChangedEventArgs(propertyName));
               }
           }

           private static string ErrorViewModelTypeHasToMatch = "The type of the new View Model has to match that of the old View Model.";

           private Func<object> creatorMethod;

           public ResettableViewModel(object innerViewModel, Func<object> creatorMethod = null)
           {
               this.InnerViewModel = innerViewModel;
               this.creatorMethod = creatorMethod;
           }

           public ResettableViewModel(Func<object> creatorMethod)
           {
               this.InnerViewModel = (this.creatorMethod = creatorMethod)();            
           }

           public ResettableViewModel(Type innerViewModelType)
           {
               this.InnerViewModel = Activator.CreateInstance(innerViewModelType);
           }

           internal object InnerViewModel { get; private set; }

           private ICommand resetCommand;

           public ICommand ResetCommand
           {
               get
               {
                   return resetCommand ?? (resetCommand = new InternalDelegateCommand(() =>
                       {
                              if (creatorMethod == null)
                               {
                                   this.InnerViewModel = Activator.CreateInstance(this.InnerViewModel.GetType());
                               }
                              else
                           {
                                    var newViewModel = creatorMethod();

                             if (this.InnerViewModel.GetType() != newViewModel.GetType())
                                     {
                                         throw new InvalidOperationException(ResettableViewModel.ErrorViewModelTypeHasToMatch);
                                     }

                             this.InnerViewModel = newViewModel;
                                }

                         FirePropertyChanged(String.Empty);
                        }));
               }
           }

           class InternalDelegateCommand : ICommand
           {
               private readonly Action executeMethod;

               public InternalDelegateCommand(Action executeMethod)
               {
                   this.executeMethod = executeMethod;
               }

               public void Execute(object parameter)
               {
                   if (this.executeMethod != null)
                 {
                        this.executeMethod();
                    }
               }

               public bool CanExecute(object parameter)
               {
                   return true;
               }

              public event EventHandler CanExecuteChanged;
          }
      }
  }

还有 Nish 使用的其他 4 个 类,可以从上面的 link 中找到:

  1. ResettableViewModelCustomField.cs
  2. ResettableViewModelCustomFieldPropertyDescriptor.cs
  3. ResettableViewModelCustomTypeDescriptor.cs
  4. ResettableViewModelTypeDescriptionProvider.cs

我最终放弃了 ResettableViewModel 并在我的构造函数中使用了 Initialize 方法。每当我需要重置视图模型时,我就调用这个 Initialize 方法。