[解決済み】WPFコンボボックスのカスタムリストへのバインド
質問
コンボボックスがあるのですが、SelectedItem/SelectedValueが更新されないようです。
コンボボックスのItemsSourceは、CollectionViewとしてRAS電話帳のエントリーの束をリストしているViewModelクラスのプロパティにバインドされています。そして、私は(別々の時に)両方のコンボボックスのItemsourceをバインドしました。
SelectedItem
または
SelectedValue
をViewModelの別のプロパティに追加しました。データバインディングによって設定された値をデバッグするために、保存コマンドに MessageBox を追加しましたが、その際に
SelectedItem
/
SelectedValue
バインディングが設定されていません。
ViewModelクラスはこのような感じです。
public ConnectionViewModel
{
private readonly CollectionView _phonebookEntries;
private string _phonebookeEntry;
public CollectionView PhonebookEntries
{
get { return _phonebookEntries; }
}
public string PhonebookEntry
{
get { return _phonebookEntry; }
set
{
if (_phonebookEntry == value) return;
_phonebookEntry = value;
OnPropertyChanged("PhonebookEntry");
}
}
}
ビジネス・オブジェクトから _phonebookEntries コレクションをコンストラクターで初期化しています。コンボボックスのXAMLは以下のような感じです。
<ComboBox ItemsSource="{Binding Path=PhonebookEntries}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding Path=PhonebookEntry}" />
私は、ComboBox に表示される実際の文字列値にのみ興味があり、オブジェクトの他のプロパティには興味がありません。これは、VPN 接続を行うときに RAS に渡す必要がある値だからです。
DisplayMemberPath
と
SelectedValuePath
はいずれもConnectionViewModelのNameプロパティです。コンボボックスは
DataTemplate
に適用されます。
ItemsControl
DataContext が ViewModel インスタンスに設定された Window 上で使用されます。
コンボボックスは項目のリストを正しく表示し、UIで問題なく選択することができます。しかし、コマンドからメッセージボックスを表示すると、PhonebookEntryプロパティには、ComboBoxから選択された値ではなく、初期値が入ったままになっています。他のTextBoxインスタンスは正常に更新され、MessageBoxに表示されています。
コンボボックスのデータバインディングで何が足りないのでしょうか?いろいろと検索してみたのですが、間違っているところが見つからないようです。
これは私が見ている動作ですが、私の特定のコンテキストでは何らかの理由で動作していません。
MainWindowViewModelがあり、その中に
CollectionView
のConnectionViewModelsを使用します。MainWindowView.xamlファイルのコードビハインドで、DataContextをMainWindowViewModelに設定しました。MainWindowView.xamlには、DataContextを設定するために
ItemsControl
をConnectionViewModelsのコレクションにバインドしています。コンボボックスといくつかのテキストボックスを保持するDataTemplateを持っています。テキストボックスは、ConnectionViewModelのプロパティに直接バインドされている。
Text="{Binding Path=ConnectionName}"
.
public class ConnectionViewModel : ViewModelBase
{
public string Name { get; set; }
public string Password { get; set; }
}
public class MainWindowViewModel : ViewModelBase
{
// List<ConnectionViewModel>...
public CollectionView Connections { get; set; }
}
XAML のコードビハインドです。
public partial class Window1
{
public Window1()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
次にXAML。
<DataTemplate x:Key="listTemplate">
<Grid>
<ComboBox ItemsSource="{Binding Path=PhonebookEntries}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding Path=PhonebookEntry}" />
<TextBox Text="{Binding Path=Password}" />
</Grid>
</DataTemplate>
<ItemsControl ItemsSource="{Binding Path=Connections}"
ItemTemplate="{StaticResource listTemplate}" />
テキストボックスはすべて正しくバインドされ、データはテキストボックスと ViewModel 間で問題なく移動します。機能していないのはコンボボックスだけです。
PhonebookEntryクラスに関するあなたの仮定は正しいです。
私が想定しているのは、DataTemplate が使用する DataContext はバインディング階層を通して自動的に設定されるので
ItemsControl
. それは少し馬鹿げているように思えます。
上記の例をもとに、この問題を実証するテスト実装を紹介します。
XAMLです。
<Window x:Class="WpfApplication7.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<DataTemplate x:Key="itemTemplate">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Path=Name}" Width="50" />
<ComboBox ItemsSource="{Binding Path=PhonebookEntries}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding Path=PhonebookEntry}"
Width="200"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Path=Connections}"
ItemTemplate="{StaticResource itemTemplate}" />
</Grid>
</Window>
は コードビハインド :
namespace WpfApplication7
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
public class PhoneBookEntry
{
public string Name { get; set; }
public PhoneBookEntry(string name)
{
Name = name;
}
}
public class ConnectionViewModel : INotifyPropertyChanged
{
private string _name;
public ConnectionViewModel(string name)
{
_name = name;
IList<PhoneBookEntry> list = new List<PhoneBookEntry>
{
new PhoneBookEntry("test"),
new PhoneBookEntry("test2")
};
_phonebookEntries = new CollectionView(list);
}
private readonly CollectionView _phonebookEntries;
private string _phonebookEntry;
public CollectionView PhonebookEntries
{
get { return _phonebookEntries; }
}
public string PhonebookEntry
{
get { return _phonebookEntry; }
set
{
if (_phonebookEntry == value) return;
_phonebookEntry = value;
OnPropertyChanged("PhonebookEntry");
}
}
public string Name
{
get { return _name; }
set
{
if (_name == value) return;
_name = value;
OnPropertyChanged("Name");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class MainWindowViewModel
{
private readonly CollectionView _connections;
public MainWindowViewModel()
{
IList<ConnectionViewModel> connections = new List<ConnectionViewModel>
{
new ConnectionViewModel("First"),
new ConnectionViewModel("Second"),
new ConnectionViewModel("Third")
};
_connections = new CollectionView(connections);
}
public CollectionView Connections
{
get { return _connections; }
}
}
}
この例を実行すると、私が言っているような動作になるはずです。テキストボックスは編集するとバインディングが正常に更新されますが、コンボボックスは更新されません。親ViewModelを導入しただけなので、非常にわかりにくいです。
DataContextの子にバインドされたアイテムは、その子をDataContextとして持っているという印象で、現在、苦労しています。この点を明確にした文書が見当たりません。
すなわち
ウィンドウ -> DataContext = MainWindowViewModel
..Items -> DataContext.PhonebookEntriesにバインドされます。
...Item -> DataContext = PhonebookEntry (暗黙のうちに関連づけられる)
私の思い込みを少しでも説明できているかは分かりませんが(?)
私の推測を確認するために、TextBoxのバインディングを次のように変更します。
<TextBox Text="{Binding Mode=OneWay}" Width="50" />
そして、これはTextBoxのバインディングルート(DataContextと比較している)がConnectionViewModelのインスタンスであることを示すでしょう。
解決するには?
DisplayMemberPathとSelectedValuePathを"Name"に設定しているので、PhoneBookEntryクラスでパブリックプロパティNameがあると推測されます。
DataContextにConnectionViewModelオブジェクトを設定しましたか?
あなたのコードをコピーして少し修正したところ、問題なく動作しているようです。 ビューモデルのPhoneBookEntyプロパティを設定するとコンボボックスの選択項目が変わり、コンボボックスの選択項目を変更するとビューモデルのPhoneBookEntryプロパティが正しく設定されますね。
以下は、私のXAMLコンテンツです。
<Window x:Class="WpfApplication6.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<StackPanel>
<Button Click="Button_Click">asdf</Button>
<ComboBox ItemsSource="{Binding Path=PhonebookEntries}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding Path=PhonebookEntry}" />
</StackPanel>
</Grid>
</Window>
そして、これが私のコードビハインドです。
namespace WpfApplication6
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
ConnectionViewModel vm = new ConnectionViewModel();
DataContext = vm;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
((ConnectionViewModel)DataContext).PhonebookEntry = "test";
}
}
public class PhoneBookEntry
{
public string Name { get; set; }
public PhoneBookEntry(string name)
{
Name = name;
}
public override string ToString()
{
return Name;
}
}
public class ConnectionViewModel : INotifyPropertyChanged
{
public ConnectionViewModel()
{
IList<PhoneBookEntry> list = new List<PhoneBookEntry>();
list.Add(new PhoneBookEntry("test"));
list.Add(new PhoneBookEntry("test2"));
_phonebookEntries = new CollectionView(list);
}
private readonly CollectionView _phonebookEntries;
private string _phonebookEntry;
public CollectionView PhonebookEntries
{
get { return _phonebookEntries; }
}
public string PhonebookEntry
{
get { return _phonebookEntry; }
set
{
if (_phonebookEntry == value) return;
_phonebookEntry = value;
OnPropertyChanged("PhonebookEntry");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
編集する Geoffの2番目の例はうまくいかないようで、私には少し奇妙に思えます。もし私が ConnectionViewModel の PhonebookEntries プロパティを ReadOnlyCollection 型に変更します。 コンボボックスのSelectedValueプロパティのTwoWayバインディングは問題なく動作します。
もしかして、CollectionViewに問題があるのでは?出力コンソールに警告が表示されていることに気づきました。
System.Windows.Data Warning: 50 : CollectionView を直接使用することは完全にサポートされていません。基本的な機能は非効率ながら動作しますが、高度な機能は既知のバグに遭遇する可能性があります。これらの問題を回避するために、派生クラスの使用を検討してください。
Edit2 (.NET 4.5)です。 DisplayMemberPathは選択され表示された項目のメンバーのみを指定しますが、DropDownListの内容はDisplayMemberPathではなくToString()に基づいて指定することが可能です。
関連
-
[解決済み] [Solved] 1つ以上のエンティティで検証に失敗しました。詳細は'EntityValidationErrors'プロパティを参照してください [重複]。
-
[解決済み】スクリプトクラスが見つからないので、スクリプトコンポーネントを追加できない?
-
[解決済み】ここで「要求URIに一致するHTTPリソースが見つかりませんでした」となるのはなぜですか?
-
[解決済み】トランスポート接続からデータを読み取れない:既存の接続は、リモートホストによって強制的に閉じられました。
-
[解決済み】なぜこのコードはInvalidOperationExceptionを投げるのですか?
-
[解決済み】"指定されたパスのフォーマットはサポートされていません。"
-
[解決済み】HRESULTからの例外:0x800A03ECエラー
-
[解決済み】ファイルへの読み書きの際に共有違反のIOExceptionが発生する C#
-
[解決済み】名前 'ViewBag' が現在のコンテキストに存在しない - Visual Studio 2015
-
[解決済み] AngularJSでデータバインディングはどのように機能するのですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】GDI+、JPEG画像をMemoryStreamに変換する際にジェネリックエラーが発生しました。
-
[解決済み] メンバー '<メンバー名>' にインスタンス参照でアクセスできない
-
[解決済み】Excel "外部テーブルが期待された形式ではありません。"
-
[解決済み] 'SubSonic.Schema .DatabaseColumn' 型のオブジェクトをシリアライズする際に、循環参照が検出されました。
-
[解決済み】プロジェクトビルド時のエラー。エディタでスクリプトにコンパイルエラーがあるため、Playerのビルドにエラーが発生する
-
[解決済み】非静的メソッドはターゲットを必要とする
-
[解決済み】C# - パスに不正な文字がある場合
-
[解決済み】HRESULTからの例外:0x800A03ECエラー
-
[解決済み] 2つのリストを結合する
-
[解決済み】プロセスが実行されているかどうかを知るには?