WPF les deux sens de la liaison ne fonctionne pas

J'ai un contexte de données (UserPreferences) attribué à ma fenêtre principale, et une zone de texte qui lie les deux sens d'une propriété à l'intérieur d'un contexte de données de propriétés (CollectionDevice) dans le contexte.

Lorsque la Fenêtre des charges, la zone de texte de ne pas se lier à l'propriétés dans mon modèle. J'ai vérifier dans le débogueur que le contexte de données est défini sur le modèle de l'objet et les propriétés du modèle sont correctement affectés. Tout ce que je reçois sont cependant une série de zone de texte avec les 0.

Quand je rentre les données dans la zone de texte, la mise à jour des données dans le modèle. Le problème arrive juste quand je charge les données et de les appliquer au contexte de données, la zone de texte ne sont pas mises à jour.

Lorsque j'enregistre le modèle de la base de données, le bon de données est enregistré dans la zone de texte. Quand je restaurer le modèle de la base de données, les données appropriée est appliquée. Lorsque le modèle est appliqué au contexte de données au sein de mon constructeur, la zone de texte du datacontext contient les données correctes et ses propriétés sont attribuées comme ils devraient l'être. Le problème est l'INTERFACE utilisateur de ne pas tenir compte de cela.

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>

Modèle <-- 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 <-- Où la zone de texte se lie à.

///<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;
}
}

Quelqu'un peut me pointer dans la bonne direction? Je suppose que la question est de ma liaison en XAML; je ne le trouve pas si. J'ai besoin c'est d'être deux-tenu du fait que les données peuvent changer à tout moment pendant la durée de vie des applications dans le modèle de données (base de données est mise à jour par synchronisation) et l'INTERFACE utilisateur doit tenir compte de ces changements, mais l'utilisateur peut appliquer les modifications au modèle via l'INTERFACE utilisateur.

Mise à jour de 1

J'ai essayé de forcer la zone de texte databind être mis à jour, mais cela ne fonctionne pas ainsi.

BindingExpression be = this.BaudRateTextBox.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();

J'ai aussi essayé le réglage de la UpdateSourceTrigger à PropertyChanged et qui ne semble pas résoudre le problème.

<TextBox Name="BaudRateTextBox" Text="{Binding Path=SelectedCollectionDevice.BaudRate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="10, 10, 0, 0" MinWidth="80" ></TextBox>

Mise à jour 2

J'ai essayé de suivre avec quelques la documentation de Microsoft et il ne semble pas résoudre le problème. Les valeurs restent toujours à 0 lorsque la fenêtre de charges. La liaison n'est pas mis à jour après que j'ai restaurer l'état de l'objet à partir de la base de données. La liaison filaire est bien parce que je suis entrée des données, le contexte de données est mise à jour. Pour une raison quelconque, il agit comme Un passage lorsque je l'ai mis à de Deux.

Mise à jour 3

J'ai essayé de déplacer le code dans la fenêtre de chargement de l'événement et de le constructeur, mais cela ne semble pas aider. Chose que j'ai trouvé intéressant, c'est que l'événement PropertyChanged ne pas me faire virer pendant le processus de désérialisation. Je ne pense pas que c'est important dans ce cas parce que l'objet est entièrement restauré correctement et puis je viens de l'attribuer au contexte de données de toute façon. J'ai déplacé le contexte de données de la XAML et dans le WindowLoaded afin de tester si le code XAML est le problème. Le résultat était le même.

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;
}

Mise à jour 4 - Problème identifié

J'ai identifié le problème, mais encore besoin d'une résolution. Le point de l'ensemble de la liaison de données est donc que je n'ai pas à effectuer cette assignation manuelle. Est-il quelque chose de mal avec mon INotify implémentations?

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;
}
La première chose que je voudrais faire est de changer List<CollectionDevice> à ObservableCollection<CollectionDevice>
Il y a même une classe personnalisée-je utiliser qui supporte le multi-thread mises à jour: code.google.com/p/mutinyirc/source/browse/MvvmFoundation.Wpf/... Dans le cas où vous en avez besoin.
Merci @Ekoostik, la raison pour laquelle je ne l'ai pas fait comme un observable est parce qu'il n'a pas besoin d'être observé. La collecte est mis en place dans les objets constructeur et il n'est jamais en train de changer. Il est rempli avec pré-déterminé de dispositifs de collecte. La dernière entrée est un appareil avec le Nom "les Autres", ce qui permet ensuite à l'utilisateur de saisir des données. C'est pourquoi le SelectedCollectionDevice est observé, mais pas le AvailableCollectionDevice.
J'ai copié / collé ton code, sans changement) et tout fonctionne très bien. La seule différence... j'ai juste attribuée manuellement le datacontext dans le constructeur de Fenêtre dans le code derrière. J'ai même ajouté un bouton qui met à jour une propriété sur la SelectedCollectionDevice et le point de vue reflète le changement. Je ne sais pas quel est le problème, mais j'espère que cela peut vous aider à déboguer.
Salut @Kevin merci pour l'astuce. J'ai essayé d'ajouter un bouton à l'INTERFACE utilisateur et de lui affecter une valeur à une des propriétés au sein de SelectedCollectionDevice et à jour sur l'INTERFACE utilisateur correctement. J'ai aussi déplacé le contexte de données à partir du XAML et dans le constructeur. Le résultat final est que l'objet est toujours pas lié correctement lorsque la Fenêtre des charges. De la zone de texte est vide. Toutes les mises à jour après que la Fenêtre est chargé est immédiatement représenté sur la zone de texte, pas seulement pendant le chargement. D'autres idées?

OriginalL'auteur Johnathon Sullinger | 2014-03-07