C# XAML Databinding has no effect when property is changed - c#

Okay I've been wracking my brain a lot about this one, I'm missing something, I just can't figure out what. Ultimately I'm trying to set databinding so I can update values to be shown on the fly, but for the life of me, it's not working.
The XAML is:
<TextBox x:Name="textBox" HorizontalAlignment="Left"
Height="37" Margin="85,38,0,0" TextWrapping="Wrap"
Text="{Binding Path=TBBind}" VerticalAlignment="Top"
Width="121" />
Note that I have the {Binding Path=TBBind} set.
The code behind is:
using System.ComponentModel;
using System.Windows;
namespace Databinding_Practice_2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
TBBind = "test";
}
private string _tBBind;
public string TBBind
{
get { return _tBBind; }
set
{
if (value != _tBBind)
{
_tBBind = value;
OnPropertyChanged("TBBind");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
MessageBox.Show("OnPropertyChanged triggered");
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
Help me obi-w.... oh wait, help me anyone!

Assuming that you are trying to use the MVVM pattern (which stands for Model-View-ViewModel):
Your MainWindow is the View.
You should create another class to be the View Model, like this:
public class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
TBBind = "test";
}
private string _tBBind;
public string TBBind
{
get { return _tBBind; }
set
{
if (value != _tBBind)
{
_tBBind = value;
OnPropertyChanged("TBBind");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
MessageBox.Show("OnPropertyChanged triggered");
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Your MainWindow code behind will become like this after removing all ViewModel related stuff to the MainWindowViewModel class:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
Now, you should link the View with the ViewModel, there are many ways to do this. Here is one of them:
In the XAML of MainWindow, have the following inside the Window element:
<Window.DataContext>
<wpfApplication5:MainWindowViewModel />
</Window.DataContext>
<Grid>
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="37" Margin="85,38,0,0" TextWrapping="Wrap" Text="{Binding TBBind}" VerticalAlignment="Top" Width="121" />
</Grid>
Please note that WpfApplication5 is the name of the namespace in my WPF project. This will probably be different in your case.

Try:
public MainWindow()
{
InitializeComponent();
DataContext = this;
TBBind = "test";
}
The difference here sets the critical DataContext property. This is the cornerstone of the MVVM pattern, which you are implementing here. You should consider separating the View Model responsibility into another class, and then setting the View's DataContext to an instance of that class, but the approach you have taken here works for simple cases.

Related

WPF DataBinding not updating with INotifyPropertyChanged

I'm trying to create a very simple data binding app for practice but I can't get it to work, I've looked at a lot of different solutions but none of them help and I can't figure out the problem.
MainWindow.xaml:
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<TextBlock Text="{Binding BindText, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
Window1.xaml:
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<TextBox Text="{Binding BindText, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
ViewModel:
using System.ComponentModel;
namespace bindtest
{
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string bindText = "Hello";
public string BindText
{
get { return bindText; }
set
{
bindText = value;
OnPropertyChanged("BindText");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
The text displays correctly when it first loads but then won't update. The text in MainWindow is meant to update when the text in window1 changes.
Any solutions?
Thanks
As JanDotNet suggests, you need to use a single instance of the view model. So in your app level code for instance you would do something like:
public partial class App : Application
{
private void App_Startup(object sender, StartupEventArgs e)
{
try
{
ViewModel vm = new ViewModel();
MainWindow w = new MainWindow(vm);
Window1 w1 = new Window1(vm);
w.Show();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
And then your window constructors modified like so:
public partial class MainWindow : Window
{
pulic MainWindow(ViewModel vm)
{
InitializeComponent();
DataContext = vm;
}
}
Since you are creating your view model via:
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
you have 2 distinct instances of the view models. You have to bind the same instance of your view models against the views.
How to bind the same instance against 2 views?
The simplest way in your case is, to create a singleton:
public class ViewModel : INotifyPropertyChanged
{
public ViewModel Instance {get; } = new ViewModel();
// ....
}
and bind to it:
<Window DataContext="{Binding Source={x:Static local:ViewModel.Instance}}" /* ... */>
Note that it is not the best way....
You should use PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); or
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName)
to ensure that the handler wasn't unsubscribed beween checking for null and invoking the event handler!

Simple XAML binding in a textblock text

I have a TextBlock control inside a HubSection in a Windows 8.1 Universal app.
<TextBlock x:Name="api_enabled_label"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Text="{Binding APIinfotext}" />
Now when the page is launched, in the contrustor, there is a method that is run.
public string APIinfotext { get; set; }
public sealed partial class MainPage : Page {
VoipMS voip_service = new VoipMS("shoukatali#hotmail.com", "Kitt0cat");
public string APIinfotext { get; set; }
public MainPage() {
this.InitializeComponent();
// disable sections until API is enabled
mainpagehub.Sections[1].IsEnabled = false;
mainpagehub.Sections[2].IsEnabled = false;
//check for API and enable sections
checkAPI();
}
private async void checkAPI() {
//irrelevant code above
switch (result) {
case "success":
APIinfotext = "Your API is connected";
break;
//irrelevant code below
}
}
So why dosnt this work? I set the DataContext of the Textblock to the current class (which is the MainPage partial class) and the property is a public property.
Note: Today is my first time working with .net 4.5 with XAML after a huge break at the .net 2.0 framework with WinForms.
Your binding doesn't know that APIinfotext property has changed. To let the bindings know that the property has changed you can do one of the following. The first one is the easiest.
1) implement INotifyPropertyChanged interface and raise the PropertyChanged changed event once APIinfotext has changed (PropertyChanged("APIinfotext"));
2) Have an event called APIinfotextChanged with the standard event signature and raise that event after the property has changed.
3) Implement your property as a DependencyProperty (not an ideal solution in this case).
You might be missing the part where you have to RaiseProperyChange NotifyPropertyChage to update the bindings. your Model should implement the INotifyPropertyChanged interface
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
then
RaisePropertyChanged("APIinfotext");
http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.propertychanged.aspx
Looks like you need a very simple example of what the other two are talking about. Let's assume nothing. You need to set the DataContext correctly, plus raise the event. This is as simple as I can put it, when you click on the button it will change the TextBox because I change the Property which raises the event.
XAML
<Page>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<TextBox Text="{Binding APIinfotext}" Height="100" Width="400" HorizontalAlignment="Left"/>
<Button x:Name="myButton" Content="Change Text" Height="200" Width="400" Click="myButton_Click"/>
</StackPanel>
</Grid>
</Page>
C# (Pay attention, to the SET part of the APIinfotext)
using System.ComponentModel; // INotifyPropertyChanged
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
private string _apiinfotext = "Default Text";
public string APIinfotext
{
get { return _apiinfotext; }
set
{
_apiinfotext = value;
RaisePropertyChanged("APIinfotext");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
}
private void myButton_Click(object sender, RoutedEventArgs e)
{
this.APIinfotext = "Don't confuse movement for progress.";
}
}

Set Datasource of Textbox to Inner Class in xaml

I have a wpf gui page with a textbox that is bound to a property of an innerclass in my window. I have defined the textbox to be bound like so:
XAML:
<TextBox Name="shhh" Text="{Binding Path=derpDerp, Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}" />
CodeBehind:
namespace ...
{
public partial class MainWindow : Window
{
innerclass definition....
public Herp derp;
public MainWindow()
{
...
derp = new Herp();
shhh.DataContext = derp;
...
}
{code that changes derp.derpDerp}
}
}
InnerClass:
public class Herp : INotifyPropertyChanged
{
private secret = "";
public event PropertyChangedEventHandler PropertyChanged;
public Herp(string derp)
{
secret = derp;
}
public string derpDerp
{
get{ return secret; }
set{ secret = value; onPropertyChanged("derpDerp"); }
}
private void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
What I was wondering is if I can declare the source of the textbox in the xaml. I have seen many examples that say to set the textbox to the datacontext of the parent like the window or a container around the textbox. However i don't find that very intuitive if only 1 control needs the data. It would make sense if I have several textboxes and a stackpanel with a datacontext.
In my implementation I create the object in code and set the datacontext to just the textbox. Is there an equivalent xaml solution?
Something like:
<TextBox Source="something" Path=derpDerp..../>
without setting a datacontext to a container or the window. Also, I didn't know how to set the datacontext of the window to my property correctly because it's an inner class with a namespace of the namespace.the window class or something like that.
What would be the proper way of just giving the textbox a datasource or if not possible how do I reference the innerclass and set the source to the window?
Yes, you can create an instance of a class and set it as DataContext on any control in XAML. The general solution would be like this:
<Window x:Class="MyProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyProject">
<Window.Resources>
<local:Herp DerpDerp="This is Derp!!" x:Key="derp"/>
</Window.Resources>
<Grid>
<TextBox Text="{Binding Source={StaticResource derp}, Path=DerpDerp}"/>
</Grid>
Notice that I defined a new xmlns object called local, which points to the namespace in which the class I'm trying to create resides (in this case, it's Herp).Then, in my Window.Resources, I create an instance of Herp, and set a value for the DerpDerp property. Also notice that I gave the class a key, which is necessary in order for the TextBox to find it and bind to it.
Big note: In order for you to be able to create an instace of a class in XAML, the class needs to have a parameter-less constructor! So I changed Herp a little bit:
namespace MyProject
{
public class Herp : INotifyPropertyChanged
{
private string m_derp;
public Herp()
{
}
public string DerpDerp
{
get { return m_derp; }
set { m_derp = value; OnPropertyChanged("DerpDerp"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Finally, in your TextBox, you use the Source element in your binding to bind to the object:
<TextBox Text="{Binding Source={StaticResource derp}, Path=DerpDerp}"/>

Listbox Trouble with Binding to ItemSource using a ObservableCollection

I am having trouble binding to the ItemsSource of a List box control. I would like to be able to add text lines to the List box when the user preforms certain actions.
The SystemControls.xmal Code:
<ListBox Grid.Column="4" Grid.Row="1" Grid.RowSpan="9" ItemsSource="{Binding ListBoxInput}" Height="165" HorizontalAlignment="Left" Name="listBox1" VerticalAlignment="Top" Width="250" ></ListBox>
The SystemControls.xmal.cs code snippet:
public partial class SystemControls : UserControl, ISystemControls
{
IDriver _Driver;
ISystemControls_VM _VM;
public SystemControls(IDriver InDriver, ISystemControls_VM InVM)
{
_VM = InVM;
_Driver = InDriver;
DataContext = new SystemControls_VM(_Driver);
InitializeComponent();
}
The SystemControls_VM.cs This should be where the heart of the problem is. I have gotten it to work in the constructor, when i try to add lines later in the code, for example when a user press a button, it does nothing:
public class SystemControls_VM:ViewModelBase, ISystemControls_VM
{
IDriver _Driver;
public ObservableCollection<string> _ListBoxInput = new ObservableCollection<string>();
public SystemControls_VM(IDriver InDriver)
{
_Driver = InDriver;
ListBoxInput.Add("test");//Works here
}
public ObservableCollection<string> ListBoxInput
{
get
{
return _ListBoxInput;
}
set
{
_ListBoxInput = value;
//OnPropertyChanged("ListBoxInput");
}
}
public void OnButtonClickGetNextError()
{
ListBoxInput.Add("NextErrorClicked");//Does not work here
}
public void OnButtonClickClear()
{
ListBoxInput.Clear();//Or Here
}
Also in case it's needed the OnPropertyChangedEventHandler:
namespace XXX.BaseClasses.BaseViewModels
{
/// <summary>
/// Provides common functionality for ViewModel classes
/// </summary>
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate{};
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
1) Your public property is called _ListBoxInput but you're binding to ListBoxInput (no underscore). Make _ListBoxInput private.
2) Because the collection is already observable, you don't need the OnPropertyChanged for your listbox to update.
3) It looks like something might be off with the way you're managing your public vs private ListBoxInput collections. You're calling .Add on your public property (which will immediately raise an event on the observable collection) but then you'll end up adding it to the private collection as well, and then you're calling PropertyChanged on the public property. It's confusing: try my code below and see how it works. (Note in your constructor you add to _ListBoxInput but in your button click event you add to ListBoxInput.)
4) Try adding this.DataContext = this in your constructor
public partial class MainWindow : Window {
public ObservableCollection<string> ListBoxInput { get; private set; }
public MainWindow() {
InitializeComponent();
this.ListBoxInput = new ObservableCollection<string>();
this.DataContext = this;
}
private void AddListBoxEntry_Click(object sender, RoutedEventArgs e) {
this.ListBoxInput.Add("Hello " + DateTime.Now.ToString());
}
}
and in the xaml, take a look at the binding Mode.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding ListBoxInput, Mode=OneWay}"
Height="165" HorizontalAlignment="Left"
Name="listBox1" VerticalAlignment="Top" Width="250" />
<Button Grid.Column="1" Grid.Row="0" Name="AddListBoxEntry"
Margin="0,0,0,158" Click="AddListBoxEntry_Click" >
<TextBlock>Add</TextBlock>
</Button>
</Grid>
5) On a separate note, here's another way you could do your INotifyPropertyChanged (I find this cleaner)
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate{};
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
So got the answer from another source, but figured I would post it here for referance.
So what was happening was that I was setting the data context to one instance of SystemControls_VM while my _VM referance which was handling the button click was going to another instance of SystemControls_VM. That was also why it looked like the button click was working and the List was being populated but no data was getting to the Control itself
I changed the following section of code and it works:
public partial class SystemControls : UserControl, ISystemControls
{
IDriver _Driver;
SystemControls_VM _VM;
public SystemControls(IDriver InDriver, SystemControls_VM InVM)
{
_VM = InVM;
_Driver = InDriver;
DataContext = InVM;//new SystemControls_VM(_Driver);
InitializeComponent();
}

Dependency Property vs INotifyPropertyChanged in ViewModel for Windows 8 application

I have created blank C#/XAML Windows 8 application. Add simple XAML code:
<Page
x:Class="Blank.MainPage"
IsTabStop="false"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<StackPanel
Margin="0,150"
HorizontalAlignment="Center">
<TextBlock
x:Name="xTitle"
Text="{Binding Title, Mode=TwoWay}"/>
<Button Content="Click me!" Click="OnClick" />
</StackPanel>
</Grid>
</Page>
And the simple code in C# part:
public sealed partial class MainPage
{
private readonly ViewModel m_viewModel;
public MainPage()
{
InitializeComponent();
m_viewModel = new ViewModel
{
Title = "Test1"
};
DataContext = m_viewModel;
}
private void OnClick(object sender, RoutedEventArgs e)
{
m_viewModel.Title = "Test2";
}
}
Now I want to implement ViewModel. I have two way:
Use Dependency Property
Implement INotifyPropertyChanged
For first approach it is:
public class ViewModel : DependencyObject
{
public string Title
{
get
{
return (string)GetValue(TitleProperty);
}
set
{
SetValue(TitleProperty, value);
}
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string)
, typeof(ViewModel)
, new PropertyMetadata(string.Empty));
}
For second it is:
public class ViewModel : INotifyPropertyChanged
{
private string m_title;
public string Title
{
get
{
return m_title;
}
set
{
m_title = value;
OnPropertyChanged("Title");
}
}
protected void OnPropertyChanged(string name)
{
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I prefer the first way, because it allows use coerce (Silverlight for web and for WP7 doesn't have coerce functionality.. WinRT too.. but I'm still looking and hope) and looks more natural for me. But unfortunately, it works as OneTime for the first approach.
Could anybody explain to me why MS abandon using Dependency Property for implementing view model?
You should not be using a DependencyProperty in your ViewModel - you should only use them in your controls. You will never want to bind one ViewModel to another, also ViewModels do not need to persist their values nor provide default values, nor provide property metadata.
You should only use INotifyPropertyChanged in your ViewModels.

Categories