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;
}
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
Vous devez vous connecter pour publier un commentaire.
Par défaut, le
Text
propriété de la zone de texte est mis à jour uniquement lorsque le focus est perdu. C'est le comportement par défaut. Avez-vous vérifier avec votre DataContext?Si vous souhaitez remplacer ce comportement, vous devez inclure la propriété
UpdateSourceTrigger
de cette façon:Réglage
UpdateSourceTrigger
's de la valeur àPropertyChanged
, la modification est reflétée dans la zone de texte lorsque vous modifiez la valeur de votre propriété liée, dès que la modification du texte.Utile tutoriel sur l'utilisation de
UpdateSourceTrigger
propriété est ici.Désolé pour ma réponse tardive! Je suis content que vous avez fixé!
+1 La simplicité et la généralisation de cette réponse m'a aidé. Je voulais que le comportement par défaut et avait tout simplement
{Binding MyProp}
, mais a dû définir explicitement les deuxMode
àTwoWay
etUpdateSourceTrigger
àLostFocus
Mon scénario est, je suis également à la liaison au-dessus de liaison comme selectedRow. Mais j'ai un problème, lorsque mon textbox et je vais cliquez sur annuler. Il sera redirigé vers principal usercontrol. après cela, je vais double-cliquez sur une même ligne, la zone de texte est vide. ici, je ne suis pas d'enregistrer l'enregistrement. il faut aller chercher la valeur de modèle sélectionné la ligne d'objet et de se lier à la zone de texte. mais, c'est un échec. Ce qui doit être fait ici? peut-u s'il vous plaît me préciser. Merci!
La lecture de la première ligne a été assez 😀
OriginalL'auteur Alberto Solano
Bien, j'étais en mesure de déterminer le problème et le résoudre. Il s'est avéré être une compilation de choses à l'origine de ce.
Tout d'abord, mon modèle.
UserPreferences <-- MainWindow est lié aux données.
Dans la définition pour la
SelectedCollectionDevice
je n'étais pas à la recherche pour voir si le périphérique sélectionné a été autres. Tous les autres appareils (yuma1, panasonic, etc) ont pré-déterminé des valeurs de propriété qui ne sont jamais modifiés. Lorsque l'utilisateur sélectionne "Autres" de la zone de texte sont affichés et ils peuvent entrer manuellement les données. Le problème était que lorsque la saisie manuellement des données a été restauré à partir de la base de données au cours de la fenêtre de chargement, je n'étais pas assigner les données personnalisées dansSelectedCollectionDevice
à l'objet correspondant dans la collection.Au cours de la fenêtre de la charge, de la
Combobox.SelectedItem
a été mis à l'index de laSelectedCollectionDevice
. LeCombobox.ItemsSource
a été mis à laAvailableCollectionDevices
collection.Lorsque le code ci-dessus est exécuté, le combo tire la valeur par défaut de l'objet à partir de sa source de données, qui a toutes les valeurs à zéro. Dans la zone de liste déroulante
SelectionChanged
cas j'ai affecté le Contexte de DonnéesSelectedCollectionDevice
à zéro suite de l'élément associé à la zone de liste modifiable.Si longue histoire courte, j'ai ajouté le code ci-dessus dans la définition pour la
SelectedCollectionDevice
pour appliquer la valeur de laAvailableCollectionDevices
Liste<>. De cette façon, lorsque la liste déroulante a les "Autres" de la valeur sélectionnée, elle tire la valeur de la collection avec les données correctes. Lors de la désérialisation, je suis juste la désérialisation duSelectedCollectionDevice
et non pas la Liste<> c'est pourquoi les données est toujours en cours a remplacé lorsque la fenêtre de chargement.Ce qui explique aussi pourquoi la ré-affectation de la le contexte de données
SelectedCollectionDevice
bien avec les locauxviewModel.SelectedCollectionDevice
était le travail. J'ai été de remplacer le zéro suite objet associé à la zone de liste déroulante, qui a créé le contexte de données au cours de laSelectionChanged
événement. Je ne suis pas en mesure de définir le DataContext dans le code XAML et supprimer l'assignation manuelle.Merci pour toute l'aide, il m'a aidé à affiner ma débogage jusqu'à ce que j'ai finalement résolu le problème. Beaucoup apprécié!
OriginalL'auteur Johnathon Sullinger
Pas une réponse, mais je voulais poster le code qui fonctionne sur ma machine pour aider les OP...
Complète xaml page...
Code complet derrière...
J'ai aussi déménagé ma restaurer le code et la valeur des affectations hors du constructeur et dans l'événement Chargé de la fenêtre afin de voir si c'était juste un problème de timing. À la fin de l'événement Chargé, je assigner l'objet valide pour le contexte de données et l'INTERFACE utilisateur encore montre des zéros.
Je vois... je viens de mentionner que vous pourriez essayer de changer les propriétés à l'extérieur de chargement de la page... mais vu ce commentaire et a supprimé mon commentaire ci-dessus.
L'objet est désérialisé à partir d'un état enregistré, puis assigné au contexte de données. Au point où j'ai effectuer cette mission, l'objet est entièrement désérialisé et la validité des valeurs de propriété. Je suis à peu près à une perte sur ce.
Quand j'exécute la désérialisation, mon événement PropertyChanged ne pas me faire virer. Cela aurait-il quelque chose à faire avec elle? Je pense que ce ne serait pas, sachant que j'affecter l'objet que mon contexte de données après que l'objet est correctement désérialisé et les propriétés sont définies correctement. J'ai pensé que je le mentionne quand même au cas où il pourrait avoir un certain effet.
OriginalL'auteur Kevin
J'ai eu le même problème. Mon problème était de liaison nom de la propriété a été mauvais. Si vous chercher à la sortie de la fenêtre vous pouvez voir toutes les erreurs de liaison au cours de l'exécution.
OriginalL'auteur user1546570