トップ回答者
MVVMにおけるTextBoxの入力制限について

質問
-
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");
}
}
}
}
回答
-
あれから少し考えてみました。
イベントを実装する以外どうにも挙動を解決できない、でも可能な限りコードビハインドに実装したくない、さらに .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
すべての返信
-
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
-
試してみました。私は 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
- 編集済み ひらぽんModerator 2015年4月24日 5:20 誤解ないよう語句を追加した
-
ひらぽんさま、ご返信ありがとうございます。
Livetを使用して確認してみましたが、残念ながら現象は変わりません。
Livetを使ったことがなかったのですが、ひらぽんさまの環境では上手くいっているとのことですので、こちらにて引き続き確認いたします。
Viewに
<Label Content="{Binding Name, UpdateSourceTrigger=PropertyChanged}"・・・>
を追加しての確認もしており、Labelには’S'が削除されたPerson.Nameが表示されますが、TextBoxに'S'が入力されると'S'が表示され、さらに入力が更新されると'S'が削除されます。
よろしくお願いいたします。
-
私が言いました 「環境」 とは、Visual Studio や .NET Framework のみでなく、OS・ビデオカードおよびドライバをも含めた動作環境全般を意味します。
実際、ビデオカードのドライバーが旧くて WPFアプリケーションの描画に不具合が発生した事例を見たことがあります。また、7 では正常に動いているアプリが、XP では描画遅延が発生したこともありました。この辺りはいかがでしょうか?
ぜひ 「フォーラムでご質問頂くにあたっての注意点」 もご覧ください https://social.msdn.microsoft.com/Forums/ja-JP/ca9ecfb7-4407-4fcb-b8bd-207d68257e68?forum=announceja
-
試してみました。当初 .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
-
こんにちは。
この現象知りませんでした…。
.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
- 編集済み Tak1waMVP, Moderator 2015年4月24日 7:28
- 回答の候補に設定 ひらぽんModerator 2015年4月24日 9:00
-
> お教え頂きましたページのように、コードビハインドで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
-
調べてみたところ、Connectにバグとして挙がっていました。
https://connect.microsoft.com/VisualStudio/feedback/details/612486/coercing-a-wpf-textbox-is-broken-in-wpf4
回避策としては、やはり自前でUpdateSourceを実行することになってしまいますね。
コメントの「次期リリースでの対応」というのが.NET4.5を指していると思いますので、.NET4.0では上記回避策になると思います。- 回答の候補に設定 ひらぽんModerator 2015年4月24日 9:34
-
あれから少し考えてみました。
イベントを実装する以外どうにも挙動を解決できない、でも可能な限りコードビハインドに実装したくない、さらに .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
-
>以下、.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/