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.
Related
My label only seems to get the data from the property it is bound to once. I have the Property raising the Property Changed event in the setter, but when the value of the property gets changed, it raises the event properly (I know this because of the break point I set), but the text in the Label on the window doesn't change. I should maybe also note that the window with the label isn't the main window, but a new one that pops up.
ViewModel:
public class PurchaseVerificationViewModel : ViewModelBase
{
private WindowService.WindowService windowService = new WindowService.WindowService();
private string _verificationQuestion = "Question"; //default so i can check if it changed in the window
public string VerificationQuestion
{
get { return _verificationQuestion; }
set
{
if (_verificationQuestion != value)
{
_verificationQuestion = value;
OnPropertyChanged(nameof(VerificationQuestion));
}
}
}
}
Window:
<Window>
<Window.DataContext>
<viewmodels:PurchaseVerificationViewModel/>
</Window.DataContext>
<Grid>
<Label Content="{Binding VerificationQuestion, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>
ViewModelBase:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
There's no problem with your implementation of the INotifyPropertyChanged, since you are correctly invoking the notification that your property has been modified.
So if the problem is not with the one who's raising the notification, might it rather be with what is actively listening to it?
And the problem is that you're defining the DataContext of your Window to the class itself, rather than to the instance which you are utilizing and modifying in the code-behind of your application.
What is actually happening under the hoods, due to the way you defined your DataContext in xaml, is that a new PurchaseVerificationViewModel class is being constructed (is the not the one who are modifying on your logic) and therefore your VerificationQuestion will return it's default value (or rather the private backing field default value, "Question").
In reality the problem is that you have induced your listener to listen to the wrong thing.
Since you want the content of the Label (target) to be update based on a source change, what you have to do, is to set as the DataContextof the Window the specific instance which you are modifying on the logic of your application, and make sure you define it as a property!
public PurchaseVerificationViewModel myViewModel {get;set;}
For instance after InitializeComponent(), on your page constructor, you could initialize the property and set it as the DataContext, like this:
myViewModel = new PurchaseVerificationViewModel();
this.DataContext = myViewModel;
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.
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";
}
}
I am trying to change multiple buttons visibility from another class. This is supposed to be easy, but I just don't understand it.
The xaml part is straight forward:
<button x:Name="whatever" Visibility="{Binding whateverName}"
The view-model could be something like this?
private Visibility vis;
public Visibility Vis
{
get { return vis; }
set { vis = value; }
}
But if that is the case, how do I pass my button name?
To go a bit further, a services file is trying to modify the visibility value..
Thanks in advance.
Since you're using Bindings, you don't need the button name identifier.
The connection is made in the Binding part of the XAML:
<Button x:Name="whatever" Visibility="{Binding whateverName}"/>
What is happening there is that you are saying the Visibility property of the whatever button will be bound to the whateverName property value in your view model.
So your View model needs to look like this:
private Visibility vis;
public Visibility whateverName
{
get { return vis; }
set { vis = value; }
}
To change the visibility of your button you need to change the value of whateverName in your view model.
However, if you try, you'll notice that that won't work. The reason is that in order for the change to take effect on the button, the View model must notify the view that its property has changed. This is done with the INotifyPropertyChanged interface.
So your view model will need to look something like this:
public class Viewmodel : INotifyPropertyChanged
{
private Visibility vis;
public Visibility whateverName
{
get { return vis; }
set
{
vis = value;
OnPropertyChanged("whateverName");
}
}
public void OnPropertyChanged(string pName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(pName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
In the PropertyChanged event you must pass the property name that you want to notify. In my example I just used a string value that matches the property name but there are various techniques to eliminate that "magic string".
Here's one SO question that has good answers.
I'm have implemented a custom TextBox:
public class MyTextBox : TextBox
{
// ...
}
that I'm using from XAML:
<MyTextBox Text="{Binding MyProperty}" />
and it's bound to a property in my ViewModel.
public class MyDataContext : INotifyPropertyChanged
{
public string MyProperty
{
get { return _myPropertyBackingField; }
set
{
_myPropertyBackingField = value;
PropertyChanged(this, new PropertyChangedEventArgs("MyProperty"));
}
}
// ...
}
Question: How can I, in MyTextBox, detect that MyProperty is changed?
MyProperty = "NewValue";
Preferably, I would like to distinguish a programmatical change from when the change was triggered by the user editing the value. That is, I don't think overriding OnPropertyChanged works for me.
You can register to the PropertyChanged event of the TextBox's DataContext.
var dataContext = DataContext as MyDataContext;
dataContext.PropertyChanged += dataContext_PropertyChanged;
// check for the propertyname and react
void dataContext_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "MyProperty")
{
// Do things
}
}
So if your viewmodel raises PropertyChanged you textbox also gets notified. But I think that's bad practice. What do you want to achieve?
OP here.
I realised after a while that it's the binding that keeps track of updating the TextBox from the source (DataContext). So a possible path to take would be to call GetBindingExpression(TextProperty) and work something out from that.
However, I solved it by overriding TextBoxBase.OnTextChanged:
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
if (!IsFocused)
{
// Do stuff here
}
}
Since the control is not focused, the change must have been done programatically. This is not perfect since a programatical change might come when the TextBox has focus, but it is good enough for me.