メインウィンドウに割り当てられたデータコンテキスト(UserPreferences
)と、コンテキスト内のデータコンテキストのプロパティ(CollectionDevice
)内のプロパティに双方向でバインドするテキストボックスがあります。
ウィンドウがロードされると、テキストボックスはモデルのプロパティにバインドしません。データコンテキストがモデルオブジェクトに設定され、モデルのプロパティが適切に割り当てられていることをデバッガー内で検証します。ただし、取得できるのは、一連のテキストボックスに0が含まれているだけです。
テキストボックスにデータを入力すると、モデル内のデータが更新されます。この問題は、データをロードしてデータコンテキストに適用したときに発生し、テキストボックスは更新されません。
モデルをデータベースに保存すると、テキストボックスから適切なデータが保存されます。データベースからモデルを復元すると、適切なデータが適用されます。モデルがコンストラクター内のデータコンテキストに適用されると、テキストボックスのdatacontextに正しいデータが含まれ、そのプロパティが必要に応じて割り当てられます。問題は、UIがこれを反映していないことです。
[〜#〜] xaml [〜#〜]
<Window.DataContext>
<models:UserPreferences />
</Window.DataContext>
<!-- Wrap pannel used to store the manual settings for a collection device. -->
<StackPanel Name="OtherCollectionDevicePanel">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Margin="10, 10, 0, 0" Text="Baud Rate" />
<TextBox Name="BaudRateTextBox" Text="{Binding Path=SelectedCollectionDevice.BaudRate, Mode=TwoWay}" Margin="10, 10, 0, 0" MinWidth="80" ></TextBox>
</StackPanel>
<WrapPanel>
<TextBlock VerticalAlignment="Center" Margin="10, 10, 0, 0" Text="Com Port" />
<TextBox Text="{Binding Path=SelectedCollectionDevice.ComPort, Mode=TwoWay}" Margin="10, 10, 0, 0" MinWidth="80" ></TextBox>
</WrapPanel>
<WrapPanel>
<TextBlock VerticalAlignment="Center" Margin="10, 10, 0, 0" Text="Data Points" />
<TextBox Text="{Binding Path=SelectedCollectionDevice.DataPoints, Mode=TwoWay}" Margin="10, 10, 0, 0" MinWidth="80" ></TextBox>
</WrapPanel>
<WrapPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Margin="10, 10, 0, 0" Text="WAAS" />
<CheckBox IsChecked="{Binding Path=SelectedCollectionDevice.WAAS, Mode=TwoWay}" Content="Enabled" Margin="20, 0, 0, 0" VerticalAlignment="Bottom"></CheckBox>
</WrapPanel>
</StackPanel>
Model<-Datacontext。
/// <summary>
/// Provides a series of user preferences.
/// </summary>
[Serializable]
public class UserPreferences : INotifyPropertyChanged
{
private CollectionDevice selectedCollectionDevice;
public UserPreferences()
{
this.AvailableCollectionDevices = new List<CollectionDevice>();
var yuma1 = new CollectionDevice
{
BaudRate = 4800,
ComPort = 31,
DataPoints = 1,
Name = "Trimble Yuma 1",
WAAS = true
};
var yuma2 = new CollectionDevice
{
BaudRate = 4800,
ComPort = 3,
DataPoints = 1,
Name = "Trimble Yuma 2",
WAAS = true
};
var toughbook = new CollectionDevice
{
BaudRate = 4800,
ComPort = 3,
DataPoints = 1,
Name = "Panasonic Toughbook",
WAAS = true
};
var other = new CollectionDevice
{
BaudRate = 0,
ComPort = 0,
DataPoints = 0,
Name = "Other",
WAAS = false
};
this.AvailableCollectionDevices.Add(yuma1);
this.AvailableCollectionDevices.Add(yuma2);
this.AvailableCollectionDevices.Add(toughbook);
this.AvailableCollectionDevices.Add(other);
this.SelectedCollectionDevice = this.AvailableCollectionDevices.First();
}
/// <summary>
/// Gets or sets the GPS collection device.
/// </summary>
public CollectionDevice SelectedCollectionDevice
{
get
{
return selectedCollectionDevice;
}
set
{
selectedCollectionDevice = value;
this.OnPropertyChanged("SelectedCollectionDevice");
}
}
/// <summary>
/// Gets or sets a collection of devices that can be used for collecting GPS data.
/// </summary>
[Ignore]
[XmlIgnore]
public List<CollectionDevice> AvailableCollectionDevices { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notifies objects registered to receive this event that a property value has changed.
/// </summary>
/// <param name="propertyName">The name of the property that was changed.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
CollectionDevice<-テキストボックスのバインド先。
/// <summary>
/// CollectionDevice model
/// </summary>
[Serializable]
public class CollectionDevice : INotifyPropertyChanged
{
/// <summary>
/// Gets or sets the COM port.
/// </summary>
private int comPort;
/// <summary>
/// Gets or sets a value indicating whether [waas].
/// </summary>
private bool waas;
/// <summary>
/// Gets or sets the data points.
/// </summary>
private int dataPoints;
/// <summary>
/// Gets or sets the baud rate.
/// </summary>
private int baudRate;
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the COM port.
/// </summary>
public int ComPort
{
get
{
return this.comPort;
}
set
{
this.comPort= value;
this.OnPropertyChanged("ComPort");
}
}
/// <summary>
/// Gets or sets the COM port.
/// </summary>
public bool WAAS
{
get
{
return this.waas;
}
set
{
this.waas = value;
this.OnPropertyChanged("WAAS");
}
}
/// <summary>
/// Gets or sets the COM port.
/// </summary>
public int DataPoints
{
get
{
return this.dataPoints;
}
set
{
this.dataPoints = value;
this.OnPropertyChanged("DataPoints");
}
}
/// <summary>
/// Gets or sets the COM port.
/// </summary>
public int BaudRate
{
get
{
return this.baudRate;
}
set
{
this.baudRate = value;
this.OnPropertyChanged("BaudRate");
}
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notifies objects registered to receive this event that a property value has changed.
/// </summary>
/// <param name="propertyName">The name of the property that was changed.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public override string ToString()
{
return this.Name;
}
}
誰かが私を正しい方向に向けることができますか?この問題はXAMLでのバインドに起因すると考えられます。私はそれを見つけることができません。データはモデル内のアプリの有効期間中いつでも変更でき(データベースは同期によって更新されます)、UIはそれらの変更を反映する必要がありますが、ユーザーは次の方法でモデルに変更を適用できます。 UI。
アップデート1
テキストボックスのデータバインドを強制的に更新しようとしましたが、うまくいきませんでした。
BindingExpression be = this.BaudRateTextBox.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
また、UpdateSourceTrigger
をPropertyChanged
に設定しようとしましたが、それでも問題は解決しないようです。
<TextBox Name="BaudRateTextBox" Text="{Binding Path=SelectedCollectionDevice.BaudRate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="10, 10, 0, 0" MinWidth="80" ></TextBox>
更新2
私はいくつかの Microsoftからのドキュメント を追おうとしましたが、問題は解決しないようです。ウィンドウがロードされても、値は0のままです。データベースからオブジェクトの状態を復元した後、バインディングが更新されていません。ただし、データを入力すると、データコンテキストが更新されるため、バインドが完了します。何らかの理由で、私が双方向に設定した場合、それは一方向のように動作します。
アップデート3
私はコードをウィンドウのロードされたイベントに移動し、コンストラクターから出そうとしましたが、それは役に立たなかったようです。興味深いのは、逆シリアル化プロセス中にPropertyChangedイベントが発生しないことです。この場合、オブジェクトが完全に適切に復元され、とにかくそれをデータコンテキストに割り当てるだけなので、これは重要ではないと思います。 XAMLに問題があるかどうかをテストするために、データコンテキストをXAMLからWindowLoadedに移動しました。結果は同じでした。
private void WindowLoaded(object sender, RoutedEventArgs e)
{
// Restore our preferences state.
var preferences = new UserPreferenceCommands();
Models.UserPreferences viewModel = new Models.UserPreferences();
// Set up the event handler before we deserialize.
viewModel.PropertyChanged += viewModel_PropertyChanged;
preferences.LoadPreferencesCommand.Execute(viewModel);
// At this point, viewModel is a valid object. All properties are set correctly.
viewModel = preferences.Results;
// After this step, the UI still shows 0's in all of the text boxs. Even though the values are not zero.
this.DataContext = viewModel;
}
// NEVER gets fired from within the WindowLoaded event.
void viewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
MessageBox.Show("Property changed!");
}
// This changes the model properties and is immediately reflected in the UI. Why does this not happen within the WindowLoaded event?
private void TestButtonClickEvent(object sender, RoutedEventArgs e)
{
var context = this.DataContext as Models.UserPreferences;
context.SelectedCollectionDevice.ComPort = 1536;
}
アップデート4-特定された問題
問題を特定しましたが、まだ解決が必要です。データバインディングのポイントは、この手動割り当てを実行する必要がないようにすることです。 INotifyの実装に何か問題がありますか?
private void WindowLoaded(object sender, RoutedEventArgs e)
{
// Restore our preferences state.
var preferences = new UserPreferenceCommands();
Models.UserPreferences viewModel = new Models.UserPreferences();
// Set up the event handler before we deserialize.
viewModel.PropertyChanged += viewModel_PropertyChanged;
preferences.LoadPreferencesCommand.Execute(viewModel);
// At this point, viewModel is a valid object. All properties are set correctly.
viewModel = preferences.Results;
// After this step, the UI still shows 0's in all of the text boxs. Even though the values are not zero.
this.DataContext = viewModel;
// SOLUTION: - Setting the actual property causes the UI to be reflected when the window is initialized; setting the actual data context does not. Why? Also note that I set this property and my PropertyChanged event handler still does not fire.
((Models.UserPreferences) DataContext).SelectedCollectionDevice = viewModel.SelectedCollectionDevice;
}
さて、問題を特定して解決することができました。これは、これを引き起こすものの編集であることが判明しました。
まず、私のモデル。
serPreferences <-MainWindowはこれにバインドされたデータです。
[Serializable]
public class UserPreferences : INotifyPropertyChanged
{
private CollectionDevice selectedCollectionDevice;
public UserPreferences()
{
this.AvailableCollectionDevices = new List<CollectionDevice>();
var yuma1 = new CollectionDevice
{
BaudRate = 4800,
ComPort = 31,
DataPoints = 1,
Name = "Trimble Yuma 1",
WAAS = true
};
var yuma2 = new CollectionDevice
{
BaudRate = 4800,
ComPort = 3,
DataPoints = 1,
Name = "Trimble Yuma 2",
WAAS = true
};
var toughbook = new CollectionDevice
{
BaudRate = 4800,
ComPort = 3,
DataPoints = 1,
Name = "Panasonic Toughbook",
WAAS = true
};
var other = new CollectionDevice
{
BaudRate = 0,
ComPort = 0,
DataPoints = 0,
Name = "Other",
WAAS = false
};
this.AvailableCollectionDevices.Add(yuma1);
this.AvailableCollectionDevices.Add(yuma2);
this.AvailableCollectionDevices.Add(toughbook);
this.AvailableCollectionDevices.Add(other);
this.SelectedCollectionDevice = this.AvailableCollectionDevices.First();
}
/// <summary>
/// Gets or sets the GPS collection device.
/// </summary>
public CollectionDevice SelectedCollectionDevice
{
get
{
return selectedCollectionDevice;
}
set
{
selectedCollectionDevice = value;
if (selectedCollectionDevice.Name == "Other")
{
this.AvailableCollectionDevices[3] = value;
}
this.OnPropertyChanged("SelectedCollectionDevice");
}
}
/// <summary>
/// Gets or sets a collection of devices that can be used for collecting GPS data.
/// </summary>
[Ignore]
[XmlIgnore]
public List<CollectionDevice> AvailableCollectionDevices { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notifies objects registered to receive this event that a property value has changed.
/// </summary>
/// <param name="propertyName">The name of the property that was changed.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
SelectedCollectionDevice
のセッターでは、選択したデバイスがotherであるかどうかを確認していませんでした。他のすべてのデバイス(yuma1、panasonicなど)には、変更されないプロパティ値が事前に設定されています。ユーザーが「その他」を選択すると、テキストボックスが表示され、データを手動で入力できます。問題は、ウィンドウのロード中に手動で入力したデータがデータベースから復元されたときに、SelectedCollectionDevice
のカスタムデータをコレクション内の対応するオブジェクトに割り当てていなかったことです。
ウィンドウのロード中、Combobox.SelectedItem
がSelectedCollectionDevice
のインデックスに設定されました。 Combobox.ItemsSource
はAvailableCollectionDevices
コレクションに設定されました。
this.CollectionDevice.SelectedIndex =
viewModel.AvailableCollectionDevices.IndexOf(
viewModel.AvailableCollectionDevices.FirstOrDefault(
acd => acd.Name == viewModel.SelectedCollectionDevice.Name));
上記のコードが実行されると、コンボボックスはそのデータソースからデフォルトオブジェクトを取得します。このデータソースはすべての値がゼロに設定されています。コンボボックスのSelectionChanged
イベント内で、データコンテキストSelectedCollectionDevice
をコンボボックスに関連付けられたゼロアウトアイテムに割り当てました。
private void CollectionDeviceSelected(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0 && e.AddedItems[0] is CollectionDevice)
{
// Assign the view models SelectedCollectionDevice to the device selected in the combo box.
var device = e.AddedItems[0] as CollectionDevice;
((Models.UserPreferences)this.DataContext).SelectedCollectionDevice = device;
// Check if Other is selected. If so, we have to present additional options.
if (device.Name == "Other")
{
OtherCollectionDevicePanel.Visibility = Visibility.Visible;
}
else if (OtherCollectionDevicePanel.Visibility == Visibility.Visible)
{
OtherCollectionDevicePanel.Visibility = Visibility.Collapsed;
}
}
}
要するに、SelectedCollectionDevice
のセッターに上記のコードを追加して、値をAvailableCollectionDevices
List <>に適用しました。このように、コンボボックスで「その他」の値が選択されている場合、正しいデータを使用してコレクションから値を取得します。逆シリアル化中、List <>ではなくSelectedCollectionDevice
を逆シリアル化しているため、ウィンドウが最初に読み込まれたときにデータが常に上書きされていました。
これは、ローカルviewModel.SelectedCollectionDevice
を使用してデータコンテキストSelectedCollectionDevice
プロパティを再割り当てした理由も説明しています。 SelectionChanged
イベント中にデータコンテキストを設定したコンボボックスに関連付けられたゼロアウトオブジェクトを置き換えました。 XAMLでDataContextを設定し、手動割り当てを削除することはできません。
すべての助けをありがとう、それは私が最終的に問題を解決するまで私のデバッグを絞り込むのに役立ちました。大変感謝いたします!
デフォルトでは、TextBoxのText
プロパティは、フォーカスが失われた場合にのみ更新されます。 DataContextで確認しましたか?
この振る舞いをオーバーライドしたい場合は、次のようにプロパティ UpdateSourceTrigger
を含める必要があります。
Text="{Binding Path=SelectedCollectionDevice.BaudRate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
UpdateSourceTrigger
の値をPropertyChanged
に設定すると、バインドされたプロパティの値を変更すると、テキストが変更されるとすぐに変更がTextBoxに反映されます。
UpdateSourceTrigger
プロパティの使用法に関する便利なチュートリアルは、 here です。
答えではありませんが、OPを支援するために私のマシンで動作するコードを投稿したかった...
完全なxamlページ...
<Window x:Class="WpfApplication1.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 Name="OtherCollectionDevicePanel">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Margin="10, 10, 0, 0"
Text="Baud Rate" />
<TextBox Name="BaudRateTextBox"
Text="{Binding Path=SelectedCollectionDevice.BaudRate, Mode=TwoWay}"
Margin="10, 10, 0, 0"
MinWidth="80"></TextBox>
</StackPanel>
<WrapPanel>
<TextBlock VerticalAlignment="Center"
Margin="10, 10, 0, 0"
Text="Com Port" />
<TextBox Text="{Binding Path=SelectedCollectionDevice.ComPort, Mode=TwoWay}"
Margin="10, 10, 0, 0"
MinWidth="80"></TextBox>
</WrapPanel>
<WrapPanel>
<TextBlock VerticalAlignment="Center"
Margin="10, 10, 0, 0"
Text="Data Points" />
<TextBox Text="{Binding Path=SelectedCollectionDevice.DataPoints, Mode=TwoWay}"
Margin="10, 10, 0, 0"
MinWidth="80"></TextBox>
</WrapPanel>
<WrapPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Margin="10, 10, 0, 0"
Text="WAAS" />
<CheckBox IsChecked="{Binding Path=SelectedCollectionDevice.WAAS, Mode=TwoWay}"
Content="Enabled"
Margin="20, 0, 0, 0"
VerticalAlignment="Bottom"></CheckBox>
</WrapPanel>
<Button Click="ButtonBase_OnClick" Content="Set ComPort to 11"></Button>
</StackPanel>
</Grid>
</Window>
完全なコードビハインド...
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Xml.Serialization;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new UserPreferences();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
((UserPreferences) DataContext).SelectedCollectionDevice.ComPort = 11;
}
}
/// <summary>
/// Provides a series of user preferences.
/// </summary>
[Serializable]
public class UserPreferences : INotifyPropertyChanged
{
private CollectionDevice selectedCollectionDevice;
public UserPreferences()
{
this.AvailableCollectionDevices = new List<CollectionDevice>();
var yuma1 = new CollectionDevice
{
BaudRate = 4800,
ComPort = 31,
DataPoints = 1,
Name = "Trimble Yuma 1",
WAAS = true
};
var yuma2 = new CollectionDevice
{
BaudRate = 4800,
ComPort = 3,
DataPoints = 1,
Name = "Trimble Yuma 2",
WAAS = true
};
var toughbook = new CollectionDevice
{
BaudRate = 4800,
ComPort = 3,
DataPoints = 1,
Name = "Panasonic Toughbook",
WAAS = true
};
var other = new CollectionDevice
{
BaudRate = 0,
ComPort = 0,
DataPoints = 0,
Name = "Other",
WAAS = false
};
this.AvailableCollectionDevices.Add(yuma1);
this.AvailableCollectionDevices.Add(yuma2);
this.AvailableCollectionDevices.Add(toughbook);
this.AvailableCollectionDevices.Add(other);
this.SelectedCollectionDevice = this.AvailableCollectionDevices.First();
}
/// <summary>
/// Gets or sets the GPS collection device.
/// </summary>
public CollectionDevice SelectedCollectionDevice
{
get
{
return selectedCollectionDevice;
}
set
{
selectedCollectionDevice = value;
this.OnPropertyChanged("SelectedCollectionDevice");
}
}
/// <summary>
/// Gets or sets a collection of devices that can be used for collecting GPS data.
/// </summary>
[XmlIgnore]
public List<CollectionDevice> AvailableCollectionDevices { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notifies objects registered to receive this event that a property value has changed.
/// </summary>
/// <param name="propertyName">The name of the property that was changed.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
/// <summary>
/// CollectionDevice model
/// </summary>
[Serializable]
public class CollectionDevice : INotifyPropertyChanged
{
/// <summary>
/// Gets or sets the COM port.
/// </summary>
private int comPort;
/// <summary>
/// Gets or sets a value indicating whether [waas].
/// </summary>
private bool waas;
/// <summary>
/// Gets or sets the data points.
/// </summary>
private int dataPoints;
/// <summary>
/// Gets or sets the baud rate.
/// </summary>
private int baudRate;
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the COM port.
/// </summary>
public int ComPort
{
get
{
return this.comPort;
}
set
{
this.comPort = value;
this.OnPropertyChanged("ComPort");
}
}
/// <summary>
/// Gets or sets the COM port.
/// </summary>
public bool WAAS
{
get
{
return this.waas;
}
set
{
this.waas = value;
this.OnPropertyChanged("WAAS");
}
}
/// <summary>
/// Gets or sets the COM port.
/// </summary>
public int DataPoints
{
get
{
return this.dataPoints;
}
set
{
this.dataPoints = value;
this.OnPropertyChanged("DataPoints");
}
}
/// <summary>
/// Gets or sets the COM port.
/// </summary>
public int BaudRate
{
get
{
return this.baudRate;
}
set
{
this.baudRate = value;
this.OnPropertyChanged("BaudRate");
}
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notifies objects registered to receive this event that a property value has changed.
/// </summary>
/// <param name="propertyName">The name of the property that was changed.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public override string ToString()
{
return this.Name;
}
}
}
同じ問題がありました。私の問題は、プロパティ名のバインドが間違っていたことでした。出力ウィンドウを見ると、実行時のすべてのバインディングエラーを確認できます。
System.Windows.Dataエラー:40:BindingExpressionパスエラー: 'SelectedProtectedWebsiteTemplate'プロパティが 'object' '' ProtectedWebsitesViewModel '(HashCode = 32764015)'で見つかりません。 BindingExpression:Path = SelectedProtectedWebsiteTemplate.Name; DataItem = 'ProtectedWebsitesViewModel'(HashCode = 32764015);ターゲット要素は 'TextBox'(Name = '');ターゲットプロパティは 'Text'(タイプ 'String')