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!
Related
I am binding a RadioButton visibility using BoolToVisConverter.
I put this in xaml file:
xmlns:VM="clr-namespace:ScreenS.ViewModel"
<Window.DataContext>
<VM:MainViewModel />
</Window.DataContext>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />
</Window.Resources>
<RadioButton x:Name="SCB0" Visibility="{Binding ShowSCB0, Converter={StaticResource BoolToVisConverter}, FallbackValue=Hidden}" />
In the MainViewModel file, I enter:
using System.ComponentModel;
namespace ScreenS.ViewModel
{
public class MainViewModel : INotifyPropertyChanged
{
private bool _scb0;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public bool ShowSCB0
{
get { return _scb0; }
set
{
_scb0 = value;
NotifyPropertyChanged("ShowSCB0");
}
}
}
Finally, in the MainWindow file, I set:
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
MainViewModel mainView => DataContext as MainViewModel;
private void Window_Loaded(object sender, RoutedEventArgs e)
{
mainView.ShowSCB0 = true;
}
Up to here, all works very well.
Problem is when I try to change this value from another class.
I am using:
class abc
{
MainViewModel viewModel = new MainViewModel();
public void someFunction()
{
viewModel.ShowSCB0 = true;
}
This does not set the visibility..
I am getting a bit lost, where am i going wrong?
You have to pay attention the way you instantiate your view models, especially when they are shared. Right now all depending types use their own instance of MainViewModel (or different references). That's why modifying the value of one instance is not reflected on the other instance.
Make use of ResourceDictionary. Consider to make the MainViewModel globally accessible by creating a shared instance inside App.xaml resources.
App.xaml
<Application ... >
<Application.Resources>
<ResourceDictionary>
<VM:MainViewModel x:Key="SharedMainViewModel" />
</ResourceDictionary>
</Application.Resources>
</Application>
MainWindow.xaml
<Window.DataContext>
<StaticResource ResourceKey="SharedMainViewModel" />
</Window.DataContext>
MainWindow.xaml.cs (fixed constructor)
public MainWindow()
{
InitializeComponent();
// The DataContext is initialized via XAML
}
Abc.cs
class Abc
{
private MainViewModel mainViewModel;
public Abc()
{
this.mainViewModel = Application.Current.Resources["SharedMainViewModel"] as MainViewModel;
}
}
Hi :) I'm trying to figure out how the INotifyPropertyChanged work with a very basic application. I am simply having a button in my mainWindow and when you press it, it should fire of an event to update a textBox, which have been bound to a specific attribute. However, even though the events gets fired of, they are always null and therefore the textBox is not updated.
<Window x:Class="StockViewer.MainWindow"
<!--Just erased some basic xaml here-->
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:RandomGen/>
</Window.DataContext>
<Grid>
<Button Click="incr" Height="30" VerticalAlignment="Top" Background="DarkGoldenrod"></Button>
<TextBlock VerticalAlignment="Top" Margin="40" Text="{Binding price, UpdateSourceTrigger=PropertyChanged}" Background="Aqua"></TextBlock>
</Grid>
When the button is pressed, the price should change:
public partial class MainWindow : Window
{
private RandomGen gen;
public MainWindow()
{
gen = new RandomGen();
InitializeComponent();
}
private void incr(object sender, RoutedEventArgs e)
{
gen.price = 7;
}
}
class RandomGen : NotifiedImp
{
public RandomGen()
{
_i = 3;
}
private int _i;
public int price
{
get { return _i; }
set
{
_i = value;
OnPropertyChanged("price");
}
}
}
class NotifiedImp: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this,new PropertyChangedEventArgs(propertyName));
}
}
}
It's just really strange, the handler is always null. Thank you :)
You have two instances of RandomGen, one initialized in your XAML:
<Window.DataContext>
<local:RandomGen/>
</Window.DataContext>
And another initialized in your MainWindow constructor:
gen = new RandomGen();
This means when you update gen.price = 7; you aren't updating the instance which is your DataContext.
One solution would be remove your <Window.DataContext> setting in XAML and set DataContext in your MainWindow constructor, like so:
public MainWindow()
{
gen = new RandomGen();
InitializeComponent();
DataContext = gen;
}
The most MVVM like solution would be to use a ICommand on your RandomGen object to update price rather than using an event handler, then use this command in your XAML, like:
<Button Command="{Binding IncrementPriceCommand}"></Button>
Then it is up to you how you initialize DataContext, you wouldn't need to keep the RandomGen backing field either way.
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.
My problem is quite simple, I want to have a MainView which in turn will have multiple Views which are dynamic and intractable, like in the diagram below:
But to do this you need multiple ViewModels, and I do not know how to organise them.
My original Idea is to have a MainViewModel, within which I will create properties that will return all my ChildViewModels as shown below, but It seems unprofessional to me and a bad practice.
public class MainViewModel : BaseViewModel
{
private EditPropertiesViewModel _editPropertiesViewModel;
public EditPropertiesViewModel EditPropertiesViewModel
{
get { return _editPropertiesViewModel; }
set
{
_editPropertiesViewModel = value;
base.OnPropertyChanged();
}
}
private UsersDetailsViewModel _usersDetailsViewModel;
public UsersDetailsViewModel UsersDetailViewModel
{
get { return _usersDetailsViewModel; }
set
{
_usersDetailsViewModel = value;
base.OnPropertyChanged();
}
}
//etc. etc..
}
Then from My MainView, I would set the Datacontext to the MainViewModel
Please help me I have no idea what to do, I am totally paused right now.
If you wish to achieve this without PRISM you can make use of ContentControl. For every region you create ContentControl and for every ContentControl you create its ViewModel property. Then you manipulate selected ViewModel associated with ContentControl and ContentControl adjusts view based on type of ViewModel assigned. For clarification take a look
XAML:
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type viewModel:SubArticleViewModel}">
<view:SubArticleView/>
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding ArticleViewModel}"/>
C#
class BaseViewModel : INotifyPropertyChanged
{
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
class MainViewModel
{
public BaseViewModel ArticleViewModel { get; set; }
}
class SubArticleViewModel : BaseViewModel
{
}
Whenever you assign
ArticleViewModel = new SubArticleViewModel();
DataTemplate defined as resource will be placed as Content of Control.
Above way out creates a lots of work and is more vulnerable for omission. PRISM would be a better choice anyway.
Create AppViewModel class with static ctor like this:
class AppViewModel : BaseViewModel
{
static AppViewModel()
{
_AppModel = new AppViewModel();
}
private static AppViewModel _AppModel;
public static AppViewModel Current
{
get { return _AppModel; }
}
private AppViewModel()
{
//Initialize view models here
MainPageModel = new MainPageViewModel();
}
//VIEW MODELS
public MainPageViewModel MainPageModel { get; private set; }
}
Create BaseViewModel class. All of your VM's should be inherited from it:
class BaseViewModel //implement INotifyPropertyChanged if needed
{
public AppViewModel AppModel
{
get { return AppViewModel.Current; }
}
}
So now you can create UserControl called "MainView":
public partial class MainView : UserControl
{
public MainView()
{
InitializeComponent();
//Prevent view updating in Designer
if (DesignerProperties.GetIsInDesignMode(this))
{
return;
}
var mainVM = AppViewModel.Current.MainPageModel;
DataContext = mainVM;
}
}
In the MainWindow.xaml:
<Window x:Class="MVVM_Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:MVVM_Test.Views"
Title="MainWindow" Height="350" Width="525">
<views:MainView />
</Window>
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}"/>