My UserControl's TextBlock binding doesn't update even once - c#

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

Related

Issue Saving Model from ViewMode/Viewl MVVM

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

Bind label Content to string property

I want to bind the content of a label to a local property called "Status".
Codesnipped:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var logFilePath = GetPathToAppDirPlusLocalPath("LogFiles/MainLog.txt");
MainLog = new Log(#logFilePath);
MainLog.Add("MainWindow initialized");
this.DataContext = this;
}
private string _Status = null;
public string Status
{
get
{
return _Status;
}
set
{
_Status = value;
NotifyPropertyChanged("Status"); //Call the method to set off the PropertyChanged event.
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged(string propertyName = "")
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
XAML:
<Border x:Name="brd_Status" Grid.Row="9" Grid.ColumnSpan="10"
HorizontalAlignment="Stretch" VerticalAlignment="Bottom"
Background="Black" DataContext="Status">
<Label x:Name="lbl_Status" Content="{Binding Path=Status,UpdateSourceTrigger=PropertyChanged}"
Grid.Row="9" Grid.ColumnSpan="10"
HorizontalAlignment="Center" VerticalAlignment="Bottom"
FontSize="16" Foreground="White" FontFamily="Asenine">
</Label>
</Border>
I also tried Content = {Binding Status...} but makes no difference.
The label is just "null", while the property "Status" is "abcd1234...".
I debugged it and but, I am not sure where to search for a failure...
The issue is that MainWindow is not implementing INotifyPropertyChanged so even though you have and are raising the appropriate event, the runtime hasn't registered for it.
Change your class definition to:
public partial class MainWindow : Window, INotifyPropertyChanged
Also consider using a proper view model (look up MVVM), puting INPC on a view object is very bad design, and note that an UpdateSourceTrigger on a Label is useless as Label controls cannot be changed in the UI.

WPF data binding not working after Initialize

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.)

Bindings on a ContentControl do not update the data

I have the code:
<TextBox Width="200" Text="{Binding Value}"></TextBox>
Which works. However the "Value" can be different types. So if I have an bool I want to show a checkbox. I rewrote it as as follow, which kinda works:
<ContentControl Content="{Binding Value}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type sys:Boolean}">
<CheckBox IsChecked="{Binding Path=.}"></CheckBox>
</DataTemplate>
<DataTemplate DataType="{x:Type sys:Double}">
<TextBox Width="200" Text="{Binding Path=.}"></TextBox>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
But now the property isn't updated like before. I have tried setting Mode=Twoway, but it still do not work.
Edit
It worked perfectly fine when I only had the textbox, editing the text of the textbox updated the model. However when I tried doing this with the second code (ContentControl) it just doesn't work.
Code
I'm using Mvvm-light togheter with bindings. The "Value" is bound to an instance of Property
[JsonObject]
public class Property<T> : INotifyPropertyChanged
{
[JsonProperty]
public String name;
public Property(String name, T value)
{
this._value = value;
this.name = name;
}
[JsonIgnore]
public T Value {
get { return _value; }
set {
_value = value;
hot = true;
NotifyPropertyChanged("Value");
}
}
[JsonProperty(PropertyName = "value")]
private T _value;
[JsonIgnore]
public String Name { get { return name; } set { name = value; } }
[JsonProperty]
public bool hot = false;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You should implement INotifyPropertyChanged interface in order to track property changes. I'm sure everything works fine then.
This works for me:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private object value;
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
DataContext = this;
}
public object Value
{
get { return value; }
set
{
this.value = value;
NotifyPropertyChanged("Value");
}
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Value = true;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

what's wrong with my databinding?

I've copied code from the blank panorama project and made some adjustments, but somewhere something ain't right.
I've got my textblock set up:
<TextBlock Grid.Column="0" Grid.Row="0" Text="{Binding ElementName=CurrentPlaceNow, Path=Temperature}" />
My model looks like this:
public class CurrentPlaceNowModel : INotifyPropertyChanged
{
#region PropertyChanged()
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private string _temperature;
public string Temperature
{
get
{
return _temperature;
}
set
{
if (value != _temperature)
{
_temperature = value;
NotifyPropertyChanged("Temperature");
}
}
}
}
And defined defined in the MainViewModel():
public CurrentPlaceNowModel CurrentPlaceNow = new CurrentPlaceNowModel();
Finally I've added a modifier to a buttonclick:
App.ViewModel.CurrentPlaceNow.Temperature = "foo";
Now, why isn't anything showing up in the textbox?
Your Binding should navigate through the ViewModel. Binding to an ElementName tries to look at another object in the Visual Tree.
Change your Binding to this:
<TextBlock
Grid.Column="0"
Grid.Row="0"
Text="{Binding CurrentPlaceNow.Temperature}" />
Verify your ViewModel's property is formatted properly:
private CurrentPlaceNowModel _CurrentPlaceNow = new CurrentPlaceNowModel();
public CurrentPlaceNowModel CurrentPlaceNow
{
get { return _CurrentPlaceNow; }
set
{
_CurrentPlaceNow = value;
NotifyPropertyChanged("CurrentPlaceNow");
}
}
As long as your View's DataContext is your MainViewModel, you are good to go.
You are using ElementName wrong. ElementName is when you want to bind to another XAML control, not to (view)model.
To bind to model, set instance of that model to DataContext property and bind only Path.

Categories

Resources