So what I want is when SelectedModel.TechName is updated that it physically saves to the model so that as long as the application is running it will remain whatever the user enters.
I have 2 views SelectedModel.TechName is called in both views. It pulls the data from the model however when I change views the data resets.
Any Suggestion?
edit: I am trying to make the data entered persistent, I thought setting the value would do this however every time i change between views it resets the data. In fact it blinks the data then resets it.
Field from DefaultView.Xaml
<StackPanel Grid.Row="0" Grid.Column="6" Grid.ColumnSpan="1" Margin="5 5 5 0">
<TextBox Name="techName" Text="{Binding SelectedModel.TechName,Mode=TwoWay}" BorderBrush="#FF4A5780" Grid.RowSpan="2"/>
</StackPanel>
<TextBlock x:Name="TextUpdate" Grid.Column="5" HorizontalAlignment="Left" Margin="41,0,0,0"
Grid.Row="1" Text="{Binding SelectedModel.TechName}" TextWrapping="Wrap" VerticalAlignment="Center"/>
DataModel.cs Model File
namespace callFlow.Models
{
public class DataModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string techName;
public DataModel()
{
}
public string TechName
{
get { return techName; }
set { techName = value;
OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string techName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(techName));
}
}
}
DefaultViewModel.cs
namespace callFlow.ViewModels
{
public class DefaultViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public DefaultViewModel() { }
private ObservableCollection<DataModel> model = new ObservableCollection<DataModel>();
private DataModel selectedModel;
private DataModel _SelectedModel;
public DataModel SelectedModel
{
get { return _SelectedModel ?? (_SelectedModel = new SelectedModel()); }
set { _SelectedModel = value;
OnPropertyChanged(); }
}
public void changeSelectedModel(DataModel newSelectedModel)
{
SelectedModel.TechName = newSelectedModel.TechName;
}
private void OnPropertyChanged([CallerMemberName] string techNameVM = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(techNameVM));
}
}
}
On your binding you have
UpdateSourceTrigger=Explicit
in
Text="{Binding SelectedModel.TechName,Mode=TwoWay,
UpdateSourceTrigger=Explicit}"
When you do that, you have to write code to update the source property. Which is the viewmodel property.
Since you don't do that, the viewmodel will not get updated when you type text in there.
You should either remove that off the binding or write some more code.
There are multiple potential issues in your code. First, you use Explicit as UpdateSourceTrigger, but you never call UpdateSource, at least you do not show that in your code. Consequently, the property will never be updated. Use PropertyChanged or LostFocus instead.
If you set the UpdateSourceTrigger value to Explicit, you must call the UpdateSource method or the changes will not propagate back to the source.
Furthermore, you implement INotifyPropertyChanged in your view models, but you never call OnPropertyChanged. Hence, bindings will never be updated when a property changes its value. Your properties should look like below. This applies to all properties that you expose.
public string TechName
{
get { return techName; }
set
{
if (techName != value)
{
techName = value;
OnPropertyChanged();
}
}
}
It is not clear how you create your views and set their DataContext. If you create the data context view model in the XAML of your view, it will be created each time you instantiate a new view.
Simple solution
Remove the UpdateSourceTriger=Explicit from your DefaultView.xaml
<TextBox Name="techName" Text="{Binding SelectedModel.TechName,Mode=TwoWay}" BorderBrush="#FF4A5780" Grid.RowSpan="2"/>
Call the OnPropertyChanged method in the DataModel.TechName's setter. Like this:
public string TechName
{
get {
return techName;
}
set {
techName = value;
OnPropertyChanged();
}
}
Better solution
There are a few problems with your code. Here's how to fix them:
DefaultView.xaml
Remove the UpdateSourceTrigger=Explicit. It requires you to update the binding manually (from code) and you're not doing that.
<StackPanel Grid.Row="0" Grid.Column="6" Grid.ColumnSpan="1" Margin="5 5 5 0">
<TextBox Name="techName" Text="{Binding SelectedModel.TechName,Mode=TwoWay}" BorderBrush="#FF4A5780" Grid.RowSpan="2"/>
<TextBlock x:Name="TextUpdate" Grid.Column="5" HorizontalAlignment="Left" Margin="41,0,0,0"
Grid.Row="1" Text="{Binding SelectedModel.TechName}" TextWrapping="Wrap" VerticalAlignment="Center"/>
DataModel.cs
You were not calling the OnPropertyChanged method in TechName's setter, that's why it wasn't updating. I've done that and refactored the code a bit
public class DataModel : ObservableObject
{
private string _techName;
public string TechName
{
get => _techName;
set {
_techName = value;
OnPropertyChanged();
}
}
}
DefaultViewModel.cs
Here I've just removed the empty default constructor, the extra private DataModel field and refactored the code.
public class DefaultViewModel : ObservableObject
{
private ObservableCollection<DataModel> Models = new ObservableCollection<DataModel>();
private DataModel _selectedModel;
public DataModel SelectedModel
{
get => _selectedModel ?? (_selectedModel = new SelectedModel());
set {
_selectedModel = value;
OnPropertyChanged();
}
}
}
INotifyPropertyChanged implementation - ObservableObject.cs
I've added this class to simplify the rest of the code, since you were using the same code in both DataModel.cs and DefaultViewModel.cs
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Related
I know this has been asked for many times. I read a lot of them and tried different ways but still could not get it to work.
The xaml code is a UserControl:
<Grid Name="middle">
<d:TextBlock Text="{x:Bind LayerNodeData.CleanName, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" Foreground="WhiteSmoke" FontSize="12" FontFamily="Arial" VerticalAlignment="Center" RelativePanel.RightOf="visibleUI" DoubleTapped="OnEditNameBegin" />
</Grid>
I set both this.DataContext and the Grid's DataContext to the data instance.
c#
public ucLayerRow(ImageLayerNode data)
{
LayerNodeData = data;
DataContext = LayerNodeData;
this.InitializeComponent();
middle.DataContext = LayerNodeData;
LayerNodeData.NotifyPropertyChanged("CleanName"); // test if it work
RefreshUI();
}
Model class
public partial class ImageLayerNode : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
// PropertyChanged is always null.
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public string mCleanName = string.Empty;
public string CleanName {
get => mCleanName;
set { mCleanName = value; NotifyPropertyChanged();}
}
....
}
I tried add a breakpoint to the PropertyChanged and found that it is always null and thus never get called. I also tried changing the mode to OneWay, TwoWays but still nothing.
The textblock is away empty not even getting a value once.
The user control is added like this to the main page. Not sure if it is related.
var rowUI = new ucLayerRow(layerNode);
layerContainer.Children.Add(rowUI);
My UserControl's TextBlock binding doesn't update even once
During the testing, the problem looks that you use design time for usercontrol. <d:TextBlock/> please remove d: and make your usercontrol like the following.
Xaml
<Grid>
<TextBlock
VerticalAlignment="Center"
FontFamily="Arial"
FontSize="12"
Foreground="Red"
Text="{x:Bind LayerNodeData.CleanName, Mode=OneWay}" />
</Grid>
Code behind
public sealed partial class ucLayerRow : UserControl
{
public ucLayerRow(ImageLayerNode data)
{
this.InitializeComponent();
LayerNodeData = data;
}
public ImageLayerNode LayerNodeData { get; set; }
}
public partial class ImageLayerNode : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
// PropertyChanged is always null.
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
private string mCleanName = string.Empty;
public string CleanName
{
get => mCleanName;
set { mCleanName = value; NotifyPropertyChanged(); }
}
}
I have a Textbox in WPF which has its "Text" Property bound to a string "EmployeeSource.ID" with Mode=TwoWay. My problem is that when i change the EmployeeSource object, the binding does not work. What is wrong in my approach?
XAML
<TextBox x:Name="NameTextBox" Margin="5,5,10,5" TextWrapping="Wrap"
Text="{Binding SelectedEmployee.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="1" Grid.Column="1" />
Code Behind
private Employee _selectedEmployee;
public Employee SelectedEmployee
{
get { return _selectedEmployee; }
set
{
_selectedEmployee = value;
UpdateTextBoxes();
}
}
private void UpdateTextBoxes()
{
NameTextBox.Text = SelectedEmployee?.Name;
}
Please try the code below. You need to implement the INotifyPropertyChanged interface inorder to achieve data binding in WPF. This is the basic concept of WPF data binding and MVVM pattern. This should work for you.
Code behind:
public class YourClassName : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private Employee _selectedEmployee;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
// The constructor is private to enforce the factory pattern.
private YourClassName()
{
_selectedEmployee = new Employee();
}
public Employee selectedEmployee
{
get
{
return this._selectedEmployee;
}
set
{
if (value != this._selectedEmployee)
{
this._selectedEmployee = value;
NotifyPropertyChanged("selectedEmployee");
}
}
}
}
XAML :
<TextBox x:Name="NameTextBox" Margin="5,5,10,5" TextWrapping="Wrap"
Text="{Binding selectedEmployee.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="1" Grid.Column="1" />
I'm having some problems with data binding. It seems that values update up until the point when the form displays, after which it has no interest in updating.
On my view I have a label.
<Label Background="{Binding info_bg}" Foreground="{Binding info_fg}" Margin="5" Grid.Row="0" FontFamily="Arial Rounded MT Bold" FontSize="24" Grid.Column="0" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" >
<Label.Content>
<AccessText TextWrapping="Wrap" Text="{Binding info}" TextAlignment="Center" VerticalAlignment="Center" />
</Label.Content>
</Label>
In the code behind
public Client()
{
_cvm = new ClientViewModel();
this.DataContext = _cvm;
InitializeComponent();
}
In the ClientViewModel class (extends a CommonBase class which has the INotifyPropertyChanged)
public class ClientViewModel : CommonBase
{
private string _info = "";
public string info
{
get
{
return _info;
}
set
{
_info = value;
NotifyPropertyChanged("info");
}
}
public ClientViewModel()
{
this._info = "TEST UPDATE";
}
When I run this, the label shows TEST UPDATE as expected. In my code behind, I created a Window_KeyUp event to push the keys pressed through to the ClientViewModel class by calling _cvm.ProcessKey(e.Key);
public void ProcessKey(string key)
{
this._info = key;
}
MessageBox.Show(Info); gives me the key I pushed, so I know it's getting through, but the View isn't updating.
CommonBase class in case I've messed up here.
public class CommonBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Thank you.
Do not set the field like this this._info = key;
Instead set the property this.info = key;
This will invoke the set of the property and the PropertyChanged event will be raised. That is what is being observed by the view so it will respond.
(And while you are at it, start the properties with an uppercase.)
My WPF app is working in a strange way for me - some binding works, other not.
I have following situation:
A textbox - user provides an ID. Based on this ID an object is loaded or created. Some other properties are updated by values coming from the loaded/new object.
Binding for the ID textbox works fine. However, two other views (any other) not.
My code samples:
XAML:
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
<TextBlock Text="ID" FontFamily="Segoe UI Light" />
<TextBox x:Name="TB_PacientID" Width="100px" HorizontalAlignment="Left" Margin="5,0,0,0" Text="{Binding Path=PacientID}"/>
<TextBlock x:Name="TBL_NovyPacient" Text="novĂ˝ pacient" Margin="5,0,0,0" Foreground="Green" FontWeight="Bold" Visibility="{Binding Path=IsNewPacient,UpdateSourceTrigger=PropertyChanged,Converter={StaticResource BTVConverter}}"/>
</StackPanel>
<WrapPanel x:Name="WP_PacientData" Margin="-2,5,2,5" Visibility="{Binding PacientLoaded,Converter={StaticResource BTVConverter}}">
...
Viewmodel:
public int? PacientID
{
get
{
if (CurrentPacient == null)
return null;
return CurrentPacient.id;
}
set
{
if (value != null)
{
_pacient = App.instance.sessionData.serviceProxy.pacientById(value.Value);
if (_pacient == null)
{
CurrentPacient = new Pacient() { id = value.Value };
IsNewPacient = true;
}
else
{
CurrentPacient = _pacient;
}
OnPropertyChanged();
PacientLoaded = true;
}
}
}
// ...
public bool IsNewPacient
{
get{ return _isNewPacient; }
set
{
_isNewPacient = value;
OnPropertyChanged();
}
}
//...
public bool PacientLoaded
{
get{ return _pacientLoaded; }
set
{
_pacientLoaded = value;
OnPropertyChanged();
}
}
The idea:
User inputs the ID, an object is loaded or created and the WrapPanel is shown. If the object is newly created the TextBlock is shown as well.
The converters are working fine (tested in another window).
When the window loads, the binding is established well (if I set some fake values in ctor). When changing the ID in textbox, nothing other updates - except for the ID itself - the setter is fired well and the new value is read after OnPropertyChanged is called.
I hope I'm missing something very easy and stupid.
-Edit:
Current state:
TB_PacientID is working (updading), TBL_NovyPacient and WP_PacientData not working (updating).
I want:
All thee views updating from viewmodel (the code properties).
-Edit 2
I created a very simple example of my problem from scratch:
A window - two textboxes:
<Window x:Class="bindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TextBox x:Name="TestTextBox" Text="{Binding ID, Mode=TwoWay}"/>
<TextBox x:Name="SecondTextBox" Text="{Binding IsNew, Mode=TwoWay}"/>
</StackPanel>
</Window>
Codebehind:
namespace bindingTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new TestViewModel();
}
}
}
And the viewmodel class:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace bindingTest
{
public abstract class ViewModelBase
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class TestViewModel : ViewModelBase
{
private bool _attOne;
private int? id;
private bool _isNew;
public bool IsNew
{
get
{
return _isNew;
}
set
{
_isNew = value;
OnPropertyChanged();
}
}
public int? ID
{
get
{
return id;
}
set
{
this.id = value;
IsNew = true;
OnPropertyChanged();
}
}
}
}
And what I simply want - If I change the number in the first textbox I want to have True in the second textbox automatically.
Yes, I am stupid.
My ViewModel base class lost the INotifyPropertyChanged interface while merging from another project.
So I called the OnPropertyChanged, but it has been my own OnPropertyChanged instead of implementation of the interface which is WPF binding waiting for.
I had threethings to point in your code sample:
You should use a TwoWay binding for setting the ID.
Are you sure the _pacient = App.instance.sessionData.serviceProxy.pacientById(value.Value); code returns always the same object instance.
Are you correctly using the INotifyPropertyChanged interface in most cases you raising a property change events looks like this: RaisePropertyChanged('PropertyName'); you are invoking: 'OnPropertyChanged();'
Hope this helps...
I'm new to MVVM, just recently started my first project following the MVVM pattern. I have an issue trying to validate an ObservableCollection using the IDataErrorInfo Interface. My ObservableCollection looks like this:
ObservableCollection<Magazine> magazineRepository;
public ObservableCollection<Magazine> MagazineRepository
{
get { return magazineRepository; }
set
{
if (value != null)
{
bladRepository = value;
OnPropertyChanged("MagazineRepository");
}
}
}
And my XAML like this:
<ListBox x:Name="listMagazineRepository"
Grid.ColumnSpan="2"
ItemsSource="{Binding}"
DataContext="{Binding MagazineRepository}"
DisplayMemberPath="Navn"
SelectedItem="{Binding Path=SelectedItem}"/>
<TextBox x:Name="txtName" Grid.Row="1" Grid.Column="0"
Text="{Binding ElementName=listMagazineRepository, Path=SelectedItem.Navn, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
<TextBox x:Name="txtPrice" Grid.Row="2" Grid.Column="0"
Text="{Binding ElementName=listMagazineRepository, Path=SelectedItem.Pris, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
It's just a simple listBox containing objects, when you select an item, the selected objects properties is displayed in the textboxes, and is then bound to the listbox object.
My Problem is, that when I set my code up like this, the only way I can figure out how to validate my data is in the Domain Model, which really isn't a good practise, I'd like to validate in the ViewModel before it gets there. Basically I want to validate each property in the MagazineRepository, in the ViewModel, How would you go about doing this?
PS: I'm new to posting on this board (and programming boards in general) if my question is lacking information, please let me know and I will supply the needed details.
Thanks a lot.
If i understand correctly you want to validate the Magazine object. If that's the case, one way to do it is to wrap that class in a viewmodel, let's call it MagazineVM, that implements IDataErrorInfo and keep the magazine object updated. You then bind to the view a list of MagazineVM. As a very simple example:
public class MagazineVM : IDataErrorInfo, INotifyPropertyChanged
{
private Magazine _magazine;
public int FirstMagazineProperty
{
get { return _magazine.FirstMagazineProperty; }
set { _magazine.FirstMagazineProperty = value; RaisePropertyChanged("FirstMagazineProperty"); }
}
//INotifyPropertyChanged implementation
//IDataErrorInfo implementation
}
Firstly, as Dtex says, you should use a MagazineViewModel class rather than a Magazine class. E.G.
public class MagazineViewModel : INotifyPropertyChanged, IDataErrorInfo
{
private string navn;
private string pris;
private string error;
public string Navn
{
get { return navn; }
set
{
if (navn != value)
{
navn = value;
RaisePropertyChanged("Navn");
}
}
}
public string Pris
{
get { return pris; }
set
{
if (pris != value)
{
pris = value;
RaisePropertyChanged("Pris");
}
}
}
public string Error
{
get { return error; }
set
{
if (error != value)
{
error = value;
RaisePropertyChanged("Error");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public string this[string columnName]
{
get
{
var result = string.Empty;
switch (columnName)
{
case "Pris":
if (string.IsNullOrWhiteSpace(Pris))
{
result = "Pris is required";
}
break;
case "Navn":
if (string.IsNullOrWhiteSpace(Navn))
{
result = "Navn is required";
}
break;
}
return result;
}
}
private void RaisePropertyChanged(string PropertyName)
{
var e = PropertyChanged;
if (e != null)
{
e(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
The important property to note is "public string this[string columnName]". ColumnName will be one of your bound properties and this is where you can do validation.
The next thing to consider is your MainViewModel (Your DataContext). E.G.
public class MainViewModel : INotifyPropertyChanged
{
//Use a readonly observable collection. If you need to reset it use the .Clear() method
private readonly ObservableCollection<MagazineViewModel> magazines = new ObservableCollection<MagazineViewModel>();
private MagazineViewModel selectedItem;
//Keep the item being edited separate to the selected item
private MagazineViewModel itemToEdit;
public ObservableCollection<MagazineViewModel> Magazines { get { return magazines; } }
public MagazineViewModel SelectedItem
{
get { return selectedItem; }
set
{
if (selectedItem != value)
{
selectedItem = value;
RaisePropertyChanged("SelectedItem");
//When the selected item changes. Copy it to the ItemToEdit
//This keeps the the copy you are editing separate meaning that invalid data isn't committed back to your original view model
//You will have to copy the changes back to your original view model at some stage)
ItemToEdit = Copy(SelectedItem);
}
}
}
public MagazineViewModel ItemToEdit
{
get { return itemToEdit; }
set
{
if (itemToEdit != value)
{
itemToEdit = value;
RaisePropertyChanged("ItemToEdit");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MainViewModel()
{
//Ctor...
}
//Create a copy of a MagazineViewModel
private MagazineViewModel Copy(MagazineViewModel ToCopy)
{
var vm = new MagazineViewModel();
vm.Navn = ToCopy.Navn;
vm.Pris = ToCopy.Pris;
return vm;
}
private void RaisePropertyChanged(string PropertyName)
{
//...
}
}
The only thing missing here is how you copy the changes back to the original view model. You could do it before the selected item changes (if the ItemToEdit is valid) or have a Commit button that is only enabled when the ItemToEdit is valid. If you can allow your original view models to go into an invalid state you don't need to worry about the copying.
Finally the XAML
An implicit style to show the error tooltip
<Style
TargetType="{x:Type TextBox}">
<Setter
Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Style>
And the controls and bindings
<ListBox
ItemsSource="{Binding Magazines}"
DisplayMemberPath="Navn"
SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}" />
<TextBox
Margin="5"
x:Name="txtName"
Grid.Row="1"
Grid.Column="0"
Text="{Binding ItemToEdit.Navn, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
<TextBox
Margin="5"
x:Name="txtPrice"
Grid.Row="2"
Grid.Column="0"
Text="{Binding ItemToEdit.Pris, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
The TextBoxes bind to ItemToEdit. ItemToEdit will be an in-sync copy of the SelectedItem.