none
MVVMにおけるTextBoxの入力制限について RRS feed

  • 質問

  • WPF アプリケーションを開発しております。

    ViewにあるTextBoxへの入力を制限したいのですが、つまずいております。

    TextBoxに特定の文字(サンプルコードでは'S')が入力された際、その文字を入力させないor削除することは可能でしょうか。

    私達は、この入力に制限を加えることはModelの責務と考え、入力不可の文字が入絵よくされた際、Modelのプロパティのsetにて値を修正するといった方法を考えました。

    しかし現状では「入力した'S'がTextBoxに表示されてしまう」といった問題点がございます。TextBoxに表示させない方法はございますでしょうか。

    よろしくお願いいたします。

    以下にサンプルコードの抜粋を記載いたします。

    ■ MainWindowViewModel ------------------------------------------------------------------------------------

    <TextBox    Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"    Height="24" HorizontalAlignment="Left" Margin="135,30,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" />

    ■ MainWindowViewModel : ModelBase  --------------------------------------------------------------------

    //ViewModelBaseはINotifyPropertyChangedを実装しています。

    public class MainWindowViewModel : ViewModelBase
       {
          private Person _person;

          /// <summary>
          /// 名前
          /// </summary>
          public string Name
          {
             get
             {
                return _person.Name;
             }
             set
             {
                if (_person.Name != value)
                {
                   _person.Name = value;
                   RaisePropertyChanged("");
                   //RaisePropertyChanged("Name");
                   //RaisePropertyChanged("UrNameIs");
                }
             }
          }

          public string UrNameIs
          {
             get
             {
                return "Ur Name Is " + _person.Name;
             }
          }

          /// <summary>
          /// コンストラクタ
          /// </summary>
          public MainWindowViewModel()
          {
             _person = new Person();
          }
       }

    ■ Person ------------------------------------------------------------------------------------------------------

    //ModelBaseはINotifyPropertyChangedを実装しています。

    public class Person : ModelBase
       {
          private string _name;
          /// <summary>
          /// Name
          /// </summary>
          public string Name
          {
             get
             {
                return _name;
             }
             set
             {
                if (_name != value)
                {
                   if (value.Contains("S"))
                   {
                      _name = value.Replace("S", "");
                   }
                   else
                   {
                      _name = value;
                   }
                   RaisePropertyChanged("Name");
                }
             }
          }
            
       }

    2015年4月24日 3:29

回答

  • あれから少し考えてみました。
    イベントを実装する以外どうにも挙動を解決できない、でも可能な限りコードビハインドに実装したくない、さらに .NET Framework 4.5 に上げることもできないというのであれば、ビヘイビアを使うというのはいかがでしょうか。これならコードビハインドにイベントハンドラを書かずに済みます。

    以下、.NET Framework 4 で動作確認したサンプルを掲示します。なお参照設定に、Microsoft.Expression.Interactions と System.Windows.Interactivity を追加する必要があります。

    using System.Windows.Controls;
    using System.Windows.Interactivity;
    
    namespace WpfApplication1 {
        public class TextBoxBehavior : Behavior<TextBox> {
            protected override void OnAttached() {
                base.OnAttached();
                this.AssociatedObject.TextChanged += TextBox_TextChanged;
           }
    
            protected override void OnDetaching() {
                base.OnDetaching();
                this.AssociatedObject.TextChanged -= TextBox_TextChanged;
            }
    
            private void TextBox_TextChanged(object sender, TextChangedEventArgs e) {
                TextBox textBox = sender as TextBox;
                textBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
            }
        }
    }


    <Window x:Class="WpfApplication1.MainWindow"
    		xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    		xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    		xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    		xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    		xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    		xmlns:local="clr-namespace:WpfApplication1"
    		mc:Ignorable="d"
    		Title="MainWindow" Height="180" Width="300">
    	<Window.DataContext>
    		<local:MainViewModel />
    	</Window.DataContext>
    	<Grid>
    		<Label Content="{Binding Name}" VerticalAlignment="Top" HorizontalAlignment="Left" />
    		<TextBox Text="{Binding Name, UpdateSourceTrigger=Explicit}" Height="26" Width="180" >
    			<i:Interaction.Behaviors>
    				<local:TextBoxBehavior />
    			</i:Interaction.Behaviors>
    		</TextBox>
    	</Grid>
    </Window>



    ぜひ 「フォーラムでご質問頂くにあたっての注意点」 もご覧ください https://social.msdn.microsoft.com/Forums/ja-JP/ca9ecfb7-4407-4fcb-b8bd-207d68257e68?forum=announceja

    • 回答としてマーク SeanKidd 2015年4月27日 0:10
    2015年4月24日 17:56
    モデレータ

すべての返信

  • ViewModelのNameで受け入れたら_person.NameをViewに読み直させるのは?

    public class MainWindowViewModel : ViewModelBase
    {
        private Person _person;
    
        /// <summary> 名前 </summary>
        public string Name
        {
            get
            {
                return _person.Name;
            }
            set
            {
                if (_person.Name != value)
                {
                    _person.Name = value;              
                 }
                //受け付けなくても再度読ませるために
                RaisePropertyChanged("UrNameIs");
                RaisePropertyChanged("Name");
            }
        }
    
        public string UrNameIs
        {
            get
            {
                return "Ur Name Is " + _person.Name;
            }
        }
    
        /// <summary> コンストラクタ </summary>
        public MainWindowViewModel()
        {
            _person = new Person();
        }
    }


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)


    • 編集済み gekkaMVP 2015年4月24日 3:51 やり方をちょっと変更
    • 回答の候補に設定 Tak1waMVP, Moderator 2015年4月24日 5:39
    2015年4月24日 3:47
  • gekkaさま、ご返信ありがとうございます。

    このやり方ですと、現象は変わらず、


    step 入力 TextBoxの表示
    step1 a a
    step2 S aS
    step3 a aa

    となります。step2ではTextBoxにaと表示することは可能でしょうか。

    2015年4月24日 4:32
  • 試してみました。私は MVVMインフラに Livet を使用してますが、狙いどおり動作しています。Livetの使用有無は別として、基本的に Person.Name プロパティのセッターで RaisePropertyChanged メソッドをコールするよう実装すれば反映される筈ですが、 どこかコーディングミスされてませんか?

    using Livet;
    
    namespace LivetWPFApplication9.Models {
        public class Person : NotificationObject {
            private string _name;
            public string Name {
                get { return _name; }
                set {
                    if (_name != value) {
                        if (value.Contains("S")) {
                            _name = value.Replace("S", "");
                        } else {
                            _name = value;
                        }
                        RaisePropertyChanged("Name");
                    }
                }
            }
        }
    }

    using Livet;
    using LivetWPFApplication9.Models;
    
    namespace LivetWPFApplication9.ViewModels {
        public class MainWindowViewModel : ViewModel {
            private Person _person;
    
            /// <summary>名前</summary>
            public string Name {
                get { return _person.Name; }
                set {
                    if (_person.Name != value) {
                        _person.Name = value;
                    }
                    // 受け付けなくても再度読ませるために
                    RaisePropertyChanged("Name");
                }
            }
    
            /// <summary>コンストラクタ</summary>
            public MainWindowViewModel() {
                _person = new Person();
            }
        }
    }

    <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Height="26" Width="180" />


    ぜひ 「フォーラムでご質問頂くにあたっての注意点」 もご覧ください https://social.msdn.microsoft.com/Forums/ja-JP/ca9ecfb7-4407-4fcb-b8bd-207d68257e68?forum=announceja


    2015年4月24日 5:19
    モデレータ
  • ひらぽんさま、ご返信ありがとうございます。

    Livetを使用して確認してみましたが、残念ながら現象は変わりません。

    Livetを使ったことがなかったのですが、ひらぽんさまの環境では上手くいっているとのことですので、こちらにて引き続き確認いたします。

    Viewに

    <Label Content="{Binding Name, UpdateSourceTrigger=PropertyChanged}"・・・>

    を追加しての確認もしており、Labelには’S'が削除されたPerson.Nameが表示されますが、TextBoxに'S'が入力されると'S'が表示され、さらに入力が更新されると'S'が削除されます。

    よろしくお願いいたします。

    2015年4月24日 5:51
  • まったくもって謎です。ちなみにこちらは簡単なサンプルで試してますが、SeanKidd さんの方でも問題を絞って試されてるのですよね?あと動作環境に問題はないでしょうか?

    ぜひ 「フォーラムでご質問頂くにあたっての注意点」 もご覧ください https://social.msdn.microsoft.com/Forums/ja-JP/ca9ecfb7-4407-4fcb-b8bd-207d68257e68?forum=announceja

    2015年4月24日 6:04
    モデレータ
  • ひらぽんさま、

    VisualStudio2010 .NetFramework4 を使用し、記載いたしましたサンプルにて確認しております。


    2015年4月24日 6:36
  • 私が言いました 「環境」 とは、Visual Studio や .NET Framework のみでなく、OS・ビデオカードおよびドライバをも含めた動作環境全般を意味します。

    実際、ビデオカードのドライバーが旧くて WPFアプリケーションの描画に不具合が発生した事例を見たことがあります。また、7 では正常に動いているアプリが、XP では描画遅延が発生したこともありました。この辺りはいかがでしょうか?


    ぜひ 「フォーラムでご質問頂くにあたっての注意点」 もご覧ください https://social.msdn.microsoft.com/Forums/ja-JP/ca9ecfb7-4407-4fcb-b8bd-207d68257e68?forum=announceja

    2015年4月24日 6:44
    モデレータ
  • 試してみました。当初 .NET Framework 4.5 で試してましたが、.NET Framework 4 だと現象が再現するのを確認しました(汗) こちらの環境は

    Windows 7 SP1 64bit
    Visual Studio 2013 Update4
    NVIDIA GeForce GT530 最新ドライバ更新済

    です。もうちょっと調べてみますね。


    ぜひ 「フォーラムでご質問頂くにあたっての注意点」 もご覧ください https://social.msdn.microsoft.com/Forums/ja-JP/ca9ecfb7-4407-4fcb-b8bd-207d68257e68?forum=announceja

    2015年4月24日 7:04
    モデレータ
  • こんにちは。

    この現象知りませんでした…。
    .NET4.0までのTextBox同期バグのようですね。(ほんとか?)

    下記リンク先では自前で更新してやることで解決しているようです。
    もう少し調べてみます。

    .NET 3.5 | WPF Textbox refuses to update itself while binded to a view model property
    WPF - MVVM - Textbox getting out of sync with viewmodel property



    2015年4月24日 7:27
    モデレータ
  • ひらぽんさま、

    「環境」の件、勘違いしておりました。

    Thinkpad E520

    Windows 7 Professional SP1 32bit

    VisualStudio 2010 Professional SP1Rel

    Intel HD Graphics 3000 

    になります。

    >試してみました。当初 .NET Framework 4.5 で試してましたが、.NET Framework 4 だと現象が再現するのを確認しました(汗)

    ご確認ありがとうございます!

    2015年4月24日 7:55
  • Tak1waさま、ご返信ありがとうございます。

    お教え頂きましたページのように、コードビハインドでTextChengedイベント処理すれば問題は解決できました。この手しかないのでしょうかね・・・

    2015年4月24日 8:05
  • > お教え頂きましたページのように、コードビハインドでTextChengedイベント処理すれば問題は解決できました。この手しかないのでしょうかね・・・

    ですかねぇ・・・。とりあえずこちらも、二つ目のページどおり以下のコードで動作確認できましたが・・・

    <TextBox Text="{Binding Name, UpdateSourceTrigger=Explicit}" Height="26" Width="180" TextChanged="TextBox_TextChanged" />
    private void TextBox_TextChanged(object sender, TextChangedEventArgs e) {
        TextBox textBox = sender as TextBox;
        textBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
    }



    ぜひ 「フォーラムでご質問頂くにあたっての注意点」 もご覧ください https://social.msdn.microsoft.com/Forums/ja-JP/ca9ecfb7-4407-4fcb-b8bd-207d68257e68?forum=announceja

    2015年4月24日 9:04
    モデレータ
  • 調べてみたところ、Connectにバグとして挙がっていました。

    https://connect.microsoft.com/VisualStudio/feedback/details/612486/coercing-a-wpf-textbox-is-broken-in-wpf4

    回避策としては、やはり自前でUpdateSourceを実行することになってしまいますね。
    コメントの「次期リリースでの対応」というのが.NET4.5を指していると思いますので、.NET4.0では上記回避策になると思います。

    2015年4月24日 9:22
    モデレータ
  • あれから少し考えてみました。
    イベントを実装する以外どうにも挙動を解決できない、でも可能な限りコードビハインドに実装したくない、さらに .NET Framework 4.5 に上げることもできないというのであれば、ビヘイビアを使うというのはいかがでしょうか。これならコードビハインドにイベントハンドラを書かずに済みます。

    以下、.NET Framework 4 で動作確認したサンプルを掲示します。なお参照設定に、Microsoft.Expression.Interactions と System.Windows.Interactivity を追加する必要があります。

    using System.Windows.Controls;
    using System.Windows.Interactivity;
    
    namespace WpfApplication1 {
        public class TextBoxBehavior : Behavior<TextBox> {
            protected override void OnAttached() {
                base.OnAttached();
                this.AssociatedObject.TextChanged += TextBox_TextChanged;
           }
    
            protected override void OnDetaching() {
                base.OnDetaching();
                this.AssociatedObject.TextChanged -= TextBox_TextChanged;
            }
    
            private void TextBox_TextChanged(object sender, TextChangedEventArgs e) {
                TextBox textBox = sender as TextBox;
                textBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
            }
        }
    }


    <Window x:Class="WpfApplication1.MainWindow"
    		xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    		xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    		xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    		xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    		xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    		xmlns:local="clr-namespace:WpfApplication1"
    		mc:Ignorable="d"
    		Title="MainWindow" Height="180" Width="300">
    	<Window.DataContext>
    		<local:MainViewModel />
    	</Window.DataContext>
    	<Grid>
    		<Label Content="{Binding Name}" VerticalAlignment="Top" HorizontalAlignment="Left" />
    		<TextBox Text="{Binding Name, UpdateSourceTrigger=Explicit}" Height="26" Width="180" >
    			<i:Interaction.Behaviors>
    				<local:TextBoxBehavior />
    			</i:Interaction.Behaviors>
    		</TextBox>
    	</Grid>
    </Window>



    ぜひ 「フォーラムでご質問頂くにあたっての注意点」 もご覧ください https://social.msdn.microsoft.com/Forums/ja-JP/ca9ecfb7-4407-4fcb-b8bd-207d68257e68?forum=announceja

    • 回答としてマーク SeanKidd 2015年4月27日 0:10
    2015年4月24日 17:56
    モデレータ
  • >以下、.NET Framework 4 で動作確認したサンプルを掲示します。なお参照設定に、Microsoft.Expression.Interactions と System.Windows.Interactivity を追加する必要があります。

    補足すると、Blendが入ってないとそれらのdllがありませんので、以下を参考にして下さい。

    Visual Studioから使うExpression BlendのBehavior達
    http://okazuki.hatenablog.com/entry/20100817/1282044737

    また、私はTextChangedイベントでコマンドを実行することがありますが、以下のようにEventTriggerを使っています。

    Handling events in an MVVM WPF application
    http://blog.magnusmontin.net/2013/06/30/handling-events-in-an-mvvm-wpf-application/


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    2015年4月25日 1:55
    モデレータ
  • みなさま、お世話になっております。


    >調べてみたところ、Connectにバグとして挙がっていました。

    今回はバグということでUpdateSourceを実行する方向でいこうと思います。

    本件に関しまして、色々とお調べ頂きまことにありがとうございました。

    SeanKidd



    2015年4月27日 0:15