none
ViewModelクラスにバインドされたDataGridのセル値を更新したい RRS feed

  • 質問

  • VS2012のMicrosoft Visual C# 2012でWPFプログラムを始めたばかりの初心者です

    「DataGridでコードから値を設定し、その値を「Esc」キーでキャンセルできるようにしたい」で回答されたコードに対してセルの値をプログラムから変更する方法がわからず悩んでいます

    例えばウィンドウにボタンを配し、クリックしたら2行目の数量を10に変更する場合は、MainWindowからViewModelクラスのDetailsに2行目の数量10を連絡してあげると思うのですが実現方法がよくわかりません

    ViewModel.Details[1].Countみたいな方法ではうまくいきませんでした(オブジェクト参照が必要です、とエラーが出ます)

    ヒント、文献などご紹介頂きたくよろしくお願いします

    2013年6月22日 16:24

回答

  • MVVMパターンの学習をお勧めします。

    参考: http://www.atmarkit.co.jp/fdotnet/chushin/greatblogentry_02/greatblogentry_02_01.html

    ViewModel側でCommandを用意し、View側(MainWindow)でCommandを起動すると、ViewModelクラスの中で記述した処理を動かすことができます。

    • 回答の候補に設定 星 睦美 2013年6月26日 8:08
    • 回答としてマーク 星 睦美 2013年7月1日 6:03
    2013年6月23日 1:35
  • ちょんさんの仰られてるとおり、ViewModel に Commandを用意し、Button のコマンドとバインドしてやればいいと思います。以下、ヒントです。

    まず RelayCommand クラスを用意します

    public sealed class RelayCommand : ICommand {
        public event EventHandler CanExecuteChanged;
        private Func<bool> _canExecuteAction;
        private Action _executeAction;
    
        #region コンストラクタ
        /// <summary>
        /// コンストラクタ
        /// </summary>
        public RelayCommand(Action executeAction) {
            this._executeAction = executeAction;
            this._canExecuteAction = () => true;
        }
        #endregion
    
        public Action ExecuteAction {
            get { return _executeAction; }
            set { _executeAction = value; }
        }
    
        /// <summary>
        /// 現在の状態でこの RelayCommand を実行できるかどうかを判断します。
        /// </summary>
        public bool CanExecute(object parameter) {
            return _canExecuteAction();
        }
    
        /// <summary>
        /// 現在のコマンドの対象で RelayCommand を実行します。
        /// </summary>
        public void Execute(object parameter) {
            if (_executeAction != null) {
                try {
                    _executeAction();
                }
                catch (Exception) {
                    throw;
                }
            }
        }
    
        public void RaiseCanExecuteChanged() {
            if (CanExecuteChanged != null) {
                CanExecuteChanged(this, EventArgs.Empty);
            }
        }
    }

    次に、2行目の数量を10 に変更するコマンドを ViewModel に用意します。

    private void Change() {
        this.Details[1].Count = 10;
    }
    
    private RelayCommand _to10Command;
    public RelayCommand To10Command {
        get {
            return _to10Command ?? (_to10Command = new RelayCommand(Change));
        }
    }

    最後に View に追加したButton に ViewModel のコマンドをバインドします。

    <Button Height="24" Content="変更" Command="{Binding To10Command}"/>

    以上、簡略ながら参考になれば幸いです。


    ひらぽん http://d.hatena.ne.jp/hilapon/

    • 回答の候補に設定 星 睦美 2013年6月26日 8:08
    • 回答としてマーク 星 睦美 2013年7月1日 6:03
    2013年6月24日 4:59
    モデレータ

すべての返信

  • MVVMパターンの学習をお勧めします。

    参考: http://www.atmarkit.co.jp/fdotnet/chushin/greatblogentry_02/greatblogentry_02_01.html

    ViewModel側でCommandを用意し、View側(MainWindow)でCommandを起動すると、ViewModelクラスの中で記述した処理を動かすことができます。

    • 回答の候補に設定 星 睦美 2013年6月26日 8:08
    • 回答としてマーク 星 睦美 2013年7月1日 6:03
    2013年6月23日 1:35
  • ちょんさんの仰られてるとおり、ViewModel に Commandを用意し、Button のコマンドとバインドしてやればいいと思います。以下、ヒントです。

    まず RelayCommand クラスを用意します

    public sealed class RelayCommand : ICommand {
        public event EventHandler CanExecuteChanged;
        private Func<bool> _canExecuteAction;
        private Action _executeAction;
    
        #region コンストラクタ
        /// <summary>
        /// コンストラクタ
        /// </summary>
        public RelayCommand(Action executeAction) {
            this._executeAction = executeAction;
            this._canExecuteAction = () => true;
        }
        #endregion
    
        public Action ExecuteAction {
            get { return _executeAction; }
            set { _executeAction = value; }
        }
    
        /// <summary>
        /// 現在の状態でこの RelayCommand を実行できるかどうかを判断します。
        /// </summary>
        public bool CanExecute(object parameter) {
            return _canExecuteAction();
        }
    
        /// <summary>
        /// 現在のコマンドの対象で RelayCommand を実行します。
        /// </summary>
        public void Execute(object parameter) {
            if (_executeAction != null) {
                try {
                    _executeAction();
                }
                catch (Exception) {
                    throw;
                }
            }
        }
    
        public void RaiseCanExecuteChanged() {
            if (CanExecuteChanged != null) {
                CanExecuteChanged(this, EventArgs.Empty);
            }
        }
    }

    次に、2行目の数量を10 に変更するコマンドを ViewModel に用意します。

    private void Change() {
        this.Details[1].Count = 10;
    }
    
    private RelayCommand _to10Command;
    public RelayCommand To10Command {
        get {
            return _to10Command ?? (_to10Command = new RelayCommand(Change));
        }
    }

    最後に View に追加したButton に ViewModel のコマンドをバインドします。

    <Button Height="24" Content="変更" Command="{Binding To10Command}"/>

    以上、簡略ながら参考になれば幸いです。


    ひらぽん http://d.hatena.ne.jp/hilapon/

    • 回答の候補に設定 星 睦美 2013年6月26日 8:08
    • 回答としてマーク 星 睦美 2013年7月1日 6:03
    2013年6月24日 4:59
    モデレータ
  • フォーラム オペレーターの星 睦美です。
    newon1 さん、投稿ありがとうございます。

    今回はちょん さんとひらぽん さんからの返信が参考になりそうだと思いますので、私から[回答としてマーク] させていただきました。

    今後ともMSDN フォーラムをお役立てください。


    日本マイクロソフト株式会社 フォーラム オペレーター 星 睦美

    2013年7月1日 6:06
  • 頑張ってみましたが、私の能力では対処することはできませんでした

    丁寧な回答を折角頂戴したのですが、役立てることができず残念です

    今回は伝統的なスパゲッティで仕上げました

    オペレータ様も投稿に対して適切な判断をしていただき、ありがとうございました

    2013年7月30日 14:22
  • DataGrid上で単価と数量を入力し、ボタンを押すと金額を計算する例を書いてみました。基本的な構造がわかるように、あえてシンプルなコードにしています。
    基本的な流れとしては、DataGridにソースを提供しているViewModelで計算し、ソースを変更して、OnPropertyChangedによってDataGridにソースの金額に変化があったことを知らせ、DataGridが金額の部分のみを再表示するという流れです。これはMVVMの基本的なパターンの一つです。
    今回は伝統的なスパゲティだとしても、次回はそうならないようにいくらかでも参考になれば幸いです。なお、理解できなところがあれば、忌憚なくお尋ねください。

    <Window x:Class="test2010wpf.DataGridMVVM2"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:test2010wpf"
            Title="DataGridMVVM" Height="365" Width="344">
    
        <Window.DataContext>
            <local:DataGridMVVM2ViewModel />
        </Window.DataContext>
        
        <Grid Height="249">
            <DataGrid Name="dg" ItemsSource="{Binding UIObjects}" AutoGenerateColumns="False" CanUserAddRows="False" Margin="0,0,0,41">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="単価" Binding="{Binding Path=単価}" />
                    <DataGridTextColumn  Header="数量" Binding="{Binding Path=数量}" />
                    <DataGridTextColumn  Header="金額" Binding="{Binding Path=金額}" />
                </DataGrid.Columns>
            </DataGrid>
            <Button Content="計算" Command="{Binding 計算Command}" Height="23" Width="75" Margin="123,214,0,12" HorizontalAlignment="Left" />
        </Grid>
        
    </Window>
    /// <summary>
    /// ViewModel
    /// </summary>
    public class DataGridMVVM2ViewModel: INotifyPropertyChanged  {
    
        //コンストラクタ
        public DataGridMVVM2ViewModel()
        {
            //プロパティの設定-------------------
    
            DataTable dt = new DataTable();
    
            dt.Columns.Add("単価", typeof(int));
            dt.Columns.Add("数量", typeof(int));
            dt.Columns.Add("金額", typeof(int));
    
            DataRow dr = dt.NewRow();
            dr["単価"] = 10;
            dr["数量"] = 2;
            dr["金額"] = 0;
            dt.Rows.Add(dr);
    
            dr = dt.NewRow();
            dr["単価"] = 30;
            dr["数量"] = 5;
            dr["金額"] = 0;
            dt.Rows.Add(dr);
    
            UIObjects = new 仕入UIObjectCollection(dt);
    
            //コマンドの設定------------------
    
            計算Command = new RelayCommand(計算);
        }
    
        //プロパティ-------------------------
    
        /// <summary>
        /// UIに表示するデータを取得する。
        /// </summary>
        仕入UIObjectCollection _UIObjects;
        public 仕入UIObjectCollection UIObjects { get { return _UIObjects; } set { _UIObjects = value; OnPropertyChanged("UIObjects"); } }
    
        //コマンド--------------------------
    
        /// <summary>
        /// 計算コマンド
        /// </summary>
        public ICommand 計算Command { get; private set; }
    
        //コマンドハンドラ---------------------
    
        private void 計算(object obj)
        {
            foreach (var item in UIObjects)
                item.金額 = item.単価 * item.数量;
        }
    
    
        //INotifyPropertyChanged メンバー--------
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected void OnPropertyChanged(string propertyName)
        {
            var d = PropertyChanged;
    
            if (d != null)
                d(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    
    /// <summary>
    /// 仕入UIObject
    /// </summary>
    public class 仕入UIObject : INotifyPropertyChanged
    {
        public 仕入UIObject(int 単価, int 数量)
        {
            this.単価 = 単価;
            this.数量 = 数量;
            this.金額 = 0;
        }
    
        int _単価;
        public int 単価 { get { return _単価; } set { _単価 = value; OnPropertyChanged("単価"); } }
    
        int _数量;
        public int 数量 { get { return _数量; } set { _数量 = value; OnPropertyChanged("数量"); } }
    
        int _金額;
        public int 金額 { get { return _金額; } set { _金額 = value; OnPropertyChanged("金額"); } }
    
        //INotifyPropertyChanged メンバー ----------------------------
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected void OnPropertyChanged(string propertyName)
        {
            var d = PropertyChanged;
    
            if (d != null)
                d(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    
    /// <summary>
    /// 仕入のコレクションを表すクラス
    /// </summary>
    public class 仕入UIObjectCollection : ObservableCollection<仕入UIObject>
    {
            //コンストラクタ
        public 仕入UIObjectCollection(DataTable dt)
        {
                foreach (DataRow dr in dt.Rows)
                    this.Add(new 仕入UIObject((int)dr["単価"], (int)dr["数量"]));
            }
        }



    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2013年7月31日 4:04
    モデレータ
  • 追加の解説ありがとうございます

    ソースコードを眺めて少しずつ理解していきたいと思います

    理解できていない時点で「回答としてマーク」を打つことはできませんのでオペレータ様に一任したいと思います

    2013年8月3日 16:25