Binding in XAML not working, if variable changed in code (WPF) - c#

I'm quite new to coding in C# and XAML and I just can't get the binding to work in XAML. It works once, when I initialise the presenter class, but doesn't update the Textbox Text, if I change the bound variable in the code afterwards.
When the program starts, "200" is displayed in the Textbox. If I press the Button, all the Messageboxes are displayed (showing "100"), but the Textbox still shows "200" instead of "100".
I tried many solutions I found online, but none seemed worked.
The Presenter Class (ViewModel):
class Presenter : ObservableObject
{
float _xText;
public float xText
{
get { return _xText; }
set
{
_xText = value;
RaisePropertyChangedEvent("xText");
}
}
public ICommand Update
{
get { return new DelegateCommand(_Update); }
}
public Presenter()
{
_xText = 200f;
}
void _Update()
{
MessageBox.Show("_Update");
_xText = 100f;
//Debug
MessageBox.Show(_xText.ToString());
MessageBox.Show(xText.ToString());
}
}
The XAML Code (View):
<TextBox IsReadOnly="False"
IsEnabled="True"
Text="{Binding Path=xText, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
<Button Command="{Binding Update}"/>
The ObservableObject class:
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I hope someone has a solution or can explain where I went wrong.
Thanks.

You are updating the backing field not the property so NotifyPropertyChanged is not getting called. Try the property instead
void _Update()
{
MessageBox.Show("_Update");
xText = 100f;
//Debug
MessageBox.Show(_xText.ToString());
MessageBox.Show(xText.ToString());
}

To fix your problem:
You are setting the backing variable _xText so it isn't running the property xText. (As #Ken-Tucker said)
(EXTRA CHANGES)
I never use float in C# or in the (SQL Server) database. (You will regret it.)
See here "the difference between decimal, double and float".
See if you can replace:
RaisePropertyChangedEvent("xText");
with
RaisePropertyChanged(() => xText);
You will avoid a lot of cases of getting stung with typoes.
You have a property named xText? Hope it is just for the question, otherwise not good. You are writing for the reader. Start here with naming guidelines.

Related

How to handle a numeric box ValueChanged event in View Model?

I'm trying to detect when a numeric box value was changed by a user and handle it in my view model.
The numeric DoubleBox is defined in XAML like this:
<numeric:DoubleBox Value="{Binding LeadR}" Grid.Column="1" MinValue="0" MaxValue="1000" IsEnabled="{Binding IsNotMeasuring}" ValueChanged="{Binding DoubleBox_ValueChanged}"/>
In my ViewModel.cs:
private void DoubleBox_ValueChanged(object sender, ValueChangedEventArgs<double?> e)
{
// Omitted Code: Insert code that does something whenever the text changes...
}
When I right click DoubleBox_ValueChanged in XAML and "Go to definition", it will navigate to the method in WM. But when I run the code, Visual Studio shows this error:
System.Windows.Markup.XamlParseException: ''Provide value on 'System.Windows.Data.Binding' threw an exception.' Line number '123' and line position '162'.'
Can anyone tell me how to solve this?
You're using the code-behind and not the viewmodel; the error means that you have not associated a DataContext to the Window/UserControl/whatever the DoubleBox is contained in. You have to setup a ViewModel and bind it to the Container of the DoubleBox to make binding work. I'll give you a quick example.
public class ViewModel : INotifyPropertyChanged
{
private double _leadR;
public double LeadR
{
get
{
return _leadR;
}
set
{
_leadR = value;
OnPropertyChanged(nameof(LeadR));
OnLeadRChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void OnLeadRChanged()
{
//Do whatever you want with the new value of LeadR
}
}
Then in your container you can set the DataContext even in the constructor like
public class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
Hope this helps with your question.
If you want to react on a value change on DoubleBox, simply do it in the setter of LeadR.
private double _leadr;
public double LeadR
{
get => _leadr;
set
{
if (Math.Abs(_leadr - value) > 10E-12)
{
_leadr = value;
OnPropertyChanged();
// The value changed, do something with it here
}
}
}
You do not need and should not handle the ValueChanged event in the view model. Other options are writing an attached property, a tigger action or a behavior, but that might be to complex for what you want to achieve.
The binding exception that you get seems to originate from using a wrong type, since the inner exception of the XamlParseException is an InvalidCastException.

Databinding to a textbox and sharing the object throughout the project

I'm having difficulties with getting a bound textbox to update. I'm still new to WPF development and I must be missing a fundamental concept as I've read nearly everything available on the internet at this point and I'm still confused. Below is my code. First, an overview of what I'm doing to better set the context for my question.
Mainwindow is a Window that contains tabs that load various pages using frame source tags. I believe this might be causing me issues as I'm not sure where the actual object is getting instantiated for each tab, just that the XAML is being loaded.
Scratchpad is a class that contains a textbox, which is going to be updated and used by almost all classes that perform any type of operation to report status and any errors.
Textbox XAML (this is in "ScratchPad_View.xaml" for context)
<TextBox x:Name="scratchMessage"
Text="{Binding Path=ScratchMessage, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Right"
Height="300"
Width ="500"
TextWrapping="Wrap"
VerticalAlignment="Top"/>
Code behind XAML
public partial class ScratchPad : Page
{
public ScratchPad()
{
InitializeComponent();
ScratchPad_Model ScratchPad_Model = new ScratchPad_Model();
this.DataContext = ScratchPad_Model;
}
}
Model Implementation
class ScratchPad_Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string _scratchMessage;
public string ScratchMessage;
{
get
{
return _scratchMessage;
}
set
{
if (value != _scratchMessage)
{
_scratchMessage = value;
OnPropertyChanged("ScratchMessage");
}
}
}
// Create the OnPropertyChanged method to raise the event
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Most of this I have cobbled together via responses to other questions on StackOverflow and reading numerous databinding tutorials however it's still not clicking. I'm not sure how to update the contents of the textbox and since I'm loading the page that contains the textbox in the XAML of my mainwindow I'm not sure I'm even referencing the correct object. The mainwindow loads this page in a frame tag, copied below.
<Frame Source="Common/View/ScratchPad_View.xaml" ></Frame>
In the code behind for this XAML, I have the following.
public partial class MainWindow
{
// Create scratchpad object for logging and status display
ScratchPad scratchPad = new ScratchPad();
public MainWindow()
{
InitializeComponent();
}
private void StartVault(object sender, RoutedEventArgs e)
{
// Creates the authentication prompt view object and pass the scratchPad reference for reporting
authPrompt_View _authPrompt_View = new authPrompt_View(scratchPad);
}
}
I pass the reference to the ScratchPad object that I created in the initialization of the mainwindow to all classes so that they can update the contents of the textbox, however I haven't had much luck in getting the binding to work. Once it works, I'm still not quite sure how I'm supposed to append text to the textbox. There's probably a great deal of problems here but I'm hoping to fix some of my conceptual problems and get a better understanding of what I'm doing wrong, thanks in advance!
You can use Application.Properties to set global properties for your project. So probably in SETTER method of textbox bound variable (in your case ScratchMessage), you need to set property in global application properties collection.
Below links explains it very well:
https://msdn.microsoft.com/en-us/library/aa348545(v=vs.100).aspx
http://www.c-sharpcorner.com/Resources/842/application-properties-in-wpf-and-c-sharp.aspx
My understanding is that , You have created the ViewModel for ScratchPad inside the constructor and assigning the DataContext in the same.
So, other windows will not have access to the DataContext.
My suggestion is that Maintain a base ViewModel class and inherit the base Viewmodel in all other ViewModel's.
Add ScratchMessage property inside base viewModel.
So you can access the ScratchMessage property from other viewModel's too.
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _scratchMessage;
public string ScratchMessage
{
get { return _scratchMessage; }
set
{
_scratchMessage = value;
this.OnPropertyChanged("ScratchMessage");
}
}
// Create the OnPropertyChanged method to raise the event
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class ViewModel1 : BaseViewModel
{
ViewModel1()
{
this.ScratchMessage = "Message";
}
}

[WPF C#]PropertyChanged function called but update does not happen

I have a small problem with my C# code and binding a property.
Here I have the following xaml:
<Image Source="{Binding downloaded, Source={StaticResource itemsViewSource}}" Width="20" Height="20" Margin="5" HorizontalAlignment="Right" VerticalAlignment="Top"/>
And there is the code I'm trying to make working:
class Ressource : INotifyPropertyChanged
{
public String downloaded { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
Debug.WriteLine("Property changed.");
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
My problem is that the NotifyPropertyChanged function is called (the debug appears), the string content is changed but I don't see my image appear.
Does anyone have a solution to this.
Thanks!
EDIT:
After multiple useful answers but no change appearing even if the propertyChanged function is called,I'm starting to wonder if maybe changing the path of the image source is really possible.
Can the image be updated when the path is changed?
Here is the code after the changes suggested:
public class Ressource : INotifyPropertyChanged
{
public String downloaded
{
get
{
return _downloaded;
}
set
{
_downloaded = value;
if (PropertyChanged != null)
PropertyChanged(this,new PropertyChangedEventArgs("downloaded"));
}
}
You should change the property declaration so that view can be notified what source property is changed in View Model and update the control for notified property's value.
private String _downloaded;
public String downloaded
{
get
{
return _downloaded;
}
set
{
_downloaded = value;
NotifyPropertyChanged("downloaded");
}
}
The C# View Model Source Code is
class Ressource : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private String _downloaded;
public string downloaded
{
get { return _downloaded; }
set
{
_downloaded= value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("downloaded"));
}
}
}
Add UpdateSourceTrigger=PropertyChanged to you binding.
You can only apply databinding when the accessibility level is public. The default accessibility level of a class is internal. You haven't applied an accessibility level, so your class Ressource is internal. Make it public and it should work.
public class Ressource : INotifyPropertyChanged
UPDATE 1:
If your image has been set to build action Resource you can try this string: "/AssemblyName;component/Assets/available.png".
OR for .Net Framework 4.5:
"pack://application:,,,/AssemblyName;component/Assets/available.png"
Replace AssemblyName with your assembly name (you can use Assembly.GetExecutingAssembly().GetName().Name to get your assembly name dynamically)
Okay so that was a rookie mistake but I prefere showing what was the problem since I found no topic explaining what could have been the problem.
However I found it myself so... I guess no one really needs it. Anyway:
I have not been clear enough.
In my example, I have the image source bound to the downloaded field in the Resource class.
The problem was that the resource objects were contained in another class which did not implement INotifyPropertyChanged.
Once I did, everything works fine.
Thanks to everyone one who tried to help me, sorry for the noobness and lack of clarity.
Code following if anyone struggles with this:
Part of Resource :
public class Ressource : INotifyPropertyChanged
{
private String _downloaded;
public String downloaded
{
get { return this._downloaded; }
set
{
this._downloaded = value;
raiseProperty("downloaded");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void raiseProperty(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
And the container :
class personalSeance : INotifyPropertyChanged
{
public ObservableCollection<Ressource> listRess { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void raiseOnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
And the simplest binding ever:
<Image Source="{Binding downloaded}" Width="20" Height="20" Margin="3" HorizontalAlignment="Right" VerticalAlignment="Top"/>

Binding variable with TextBlock

I have my XAML code (inside a Page of the standard blank page template) as
<Grid>
<TextBlock x:Name="tbBindingBlock">
</Grid>
And I have an int named iTestBindingin the code-behind. Is there a simple (since I'm only binding one variable and not a collection) way of binding the two together so that the latest value of iTestBinding (which keeps getting changed from the code-behind) is always displayed inside tbBindingBlock ?
Edit : The code behind is pretty short :
public sealed partial class MainPage : Page
{
public int iTestBinding=0;
you can bind your value directly using the below code
<TextBlock x:Name="tbBindingBlock" Text="{Binding iTestBinding}">
keep your value to bind as a property in code behind like this
public int iTestBinding{ get; set; }
and set the datacontext in page loaded event like this
this.DataContext = this;
if you want to update the value on button click and to be reflected in UI, you need to implement the PropertyChangedEventHandler.
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
// take a copy to prevent thread issues
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
call RaisePropertyChanged("changed property name") where-ever you updated the value of the property.
Its better to keep a custom get set for the property so that we can call this method from the setter.for example
private string myText;
public string MyText {
get {
return myText;
}
set {
myText = value;
RaisePropertyChanged("MyText");
}
}

C# WPF MVVM TextBox value doesn't change

I am a beginner to use MVVM in WPF and found that it seem impossible to change the value of a textbox or a label. Here is an example.
In Xaml:
The original value of Name is "Peter".
But after I press a button which invoke a command in the ViewModel and change the value of Name to be
"John". So, suppose the value of the text box will be changed to John as well. However, it doesn't change.
I have found a lot of example in the net and found that none of them implemented this kind of functions. What I have learnt from them is to use Command and ItemsSource of ListView.
The value of ListView will change when I use button to raise command to change the ItemsSource of the view. Its value will change automatically when the Binding to ItemsSource changed.
However, I cannot make the value of TextBox or Label change even the value of the bindings to them are changed already.
Actually, I am really quite young in MVVM. I think I still have so much that I don't know.
Could you give me an example of how exactly I should do to make change to textbox after a button click? By the way, I am not quite sure how to make command for button. It seem to involve so much codes that I found in the sample from the net. Is there any simplier way?
Thank you very much.
Your ViewModel needs to implement INotifyPropertyChanged .
Documentation see here
public class Bar : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string foo;
public string Foo
{
get { return this.foo; }
set
{
if(value==this.foo)
return;
this.foo = value;
this.OnPropertyChanged("Foo");
}
}
private void OnPropertyChanged(string propertyName)
{
if(this.PropertyChanged!=null)
this.PropertyChanged(this,new PropertyChangedEventArgs(propertyName));
}
}
Your view model should implement INotifyPropertyChanged so that WPF knows that you've altered a value of a property.
Here is an example from
// This is a simple customer class that
// implements the IPropertyChange interface.
public class DemoCustomer : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private string customerNameValue = String.Empty;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
var listeners = PropertyChanged;
if (listeners != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public string CustomerName
{
get
{
return this.customerNameValue;
}
set
{
if (value != this.customerNameValue)
{
this.customerNameValue = value;
NotifyPropertyChanged("CustomerName");
}
}
}
}

Categories