Is it possible to expose a public event from my ViewModel is such a way as to allow it to be bound to a custom DependencyProperty in my View?
My application is written in C# using the .NET 4.5 framework. It has a MVVM architecture with no code-behind in the view and custom DependencyProperty classes to bind WPF-specific behvaiours of the View to properties exposed by the ViewModel.
There is a set of properties that I would like the ViewModel to be able to expose that represent events to which the View needs to respond. For example, when a top level ViewModel object is about to be Disposed I would like the WPF View implementation to respond by closing the corresponding Window. This could occur when a configuration process has displayed a Dialog Window, the user has enetered and confirmed the information and the ViewModel has passed it to the Model and is no longer required.
I am aware that there are many questions that are specific to solving the 'show dialog from ViewModel' question; this is not one of them and I have a solution to that one.
I've read through the MSDN documentation for DependencyProperties and can't find anything specific to binding to event properties.
What I would like to achieve is something similar to the code below. This code builds, but results in a typical System.Windows.Data Error: 40 : BindingExpression path error: 'RequestCloseEvent' property not found error when the MainWindow is shown.
I am aware that there are many questions that go along the lines of 'please help me debug my System.Windows.Data Error: 40 issue'; this is (probably) not one of these either. (But I'd be happy if that's all it really is.)
Source for the custom DependencyProperty in WindowBindableProperties.cs:
using System;
using System.Threading;
using System.Windows;
namespace WpfEventBinding
{
public static class WindowBindableProperties
{
#region ViewModelTerminatingEventProperty
/// <summary>
/// Register the ViewModelTerminatingEvent custom DependencyProperty.
/// </summary>
private static DependencyProperty _viewModelTerminatingEventProperty =
DependencyProperty.RegisterAttached
(
"ViewModelTerminatingEvent",
typeof(ViewModelTerminatingEventHandler),
typeof(WindowBindableProperties),
new PropertyMetadata(null, ViewModelTerminatingEventPropertyChanged)
);
/// <summary>
/// Identifies the ViewModelTerminatingEvent dependency property.
/// </summary>
public static DependencyProperty ViewModelTerminatingEventProperty
{ get { return _viewModelTerminatingEventProperty; } }
/// <summary>
/// Gets the attached ViewModelTerminatingEvent dependecy property.
/// </summary>
/// <param name="dependencyObject">The window attached to the WindowViewModel.</param>
/// <returns>The ViewModelTerminatingEventHandler bound to this property</returns>
public static ViewModelTerminatingEventHandler GetViewModelTerminatingEvent
(DependencyObject dependencyObject)
{
return (dependencyObject.GetValue(ViewModelTerminatingEventProperty)
as ViewModelTerminatingEventHandler);
}
/// <summary>
/// Sets the ViewModelTerminatingEvent dependency property.
/// </summary>
public static void SetViewModelTerminatingEvent(
DependencyObject dependencyObject,
ViewModelTerminatingEventHandler value)
{
dependencyObject.SetValue(ViewModelTerminatingEventProperty, value);
}
/// <summary>
/// Gets the ViewModelTerminatingEvent dependency property.
/// </summary>
private static void ViewModelTerminatingEventPropertyChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
Window instance = d as Window;
if (null != instance)
{
if (null != e.OldValue)
{
throw new System.InvalidOperationException(
"ViewModelTerminatingEvent dependency property cannot be changed.");
}
if (null != e.NewValue)
{
// Attach the Window.Close() method to the ViewModel's event
var newEvent = (e.NewValue as ViewModelTerminatingEventHandler);
newEvent += new ViewModelTerminatingEventHandler(() => instance.Close());
}
}
}
#endregion
}
}
Source for MainWindow.xaml:
(This example contains code-behind to simplify the Stop Button implementation.)
<Window x:Class="WpfEventBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:WpfEventBinding"
v:WindowBindableProperties.ViewModelTerminatingEvent="{Binding Path=RequestCloseEvent}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="{Binding Path=CloseCommandName}" Click="StopButton_Click" ></Button>
</Grid>
</Window>
Source for MainWindow.xaml.cs (code behind):
using System.Windows;
namespace WpfEventBinding
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void StopButton_Click(object sender, RoutedEventArgs e)
{
MainWindowViewModel vm = (DataContext as MainWindowViewModel);
if (null != vm)
{
vm.Stop();
}
}
}
}
Source for the MainWindowViewModel.cs:
using System;
using System.ComponentModel;
namespace WpfEventBinding
{
public delegate void ViewModelTerminatingEventHandler();
class MainWindowViewModel
: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// Raised by the ViewModel to indicate to the view that it is no longer required.
// Causes System.Windows.Data Error: 40 : BindingExpression path error. Is it
// Possible to bind to an 'event' property?
public event ViewModelTerminatingEventHandler RequestCloseEvent;
// This has to have the public 'get' to allow binding. Is there some way to
// do the same thing for the 'event'?
public String CloseCommandName { get; private set; }
public MainWindowViewModel()
{
CloseCommandName = "Close";
}
internal void Stop()
{
ViewModelTerminatingEventHandler RaiseRequestCloseEvent =
RequestCloseEvent;
if (null != RaiseRequestCloseEvent)
{
RaiseRequestCloseEvent();
}
}
internal void Start()
{
OnPropertyChanged("CloseCommandName");
OnPropertyChanged("ViewModelTerminatingEvent");
}
private void OnPropertyChanged(String propertyName)
{
PropertyChangedEventHandler RaisePropertyChangedEvent = PropertyChanged;
if (RaisePropertyChangedEvent != null)
{
var propertyChangedEventArgs = new PropertyChangedEventArgs(propertyName);
RaisePropertyChangedEvent(this, propertyChangedEventArgs);
}
}
}
}
Source for App.xaml:
<Application x:Class="WpfEventBinding.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Application.Resources>
<!-- Nothing to see here. Move along... -->
</Application.Resources>
</Application>
Source for App.xaml.cs
using System.Windows;
namespace WpfEventBinding
{
public partial class App : Application
{
public App()
{
Startup += new StartupEventHandler(App_Startup);
}
void App_Startup(object sender, StartupEventArgs e)
{
MainWindowViewModel vm = new MainWindowViewModel();
MainWindow window = new MainWindow();
// Make sure this is set before attempting binding!
window.DataContext = vm;
vm.Start();
window.Show();
}
}
}
It appears that the public event ViewModelTerminatingEventHandler RequestCloseEvent; syntax is not sufficient to allo the data binding to occur. A similar problem is see if the public String CloseCommandName { get; private set; } is declared as public String CloseCommandName; without the { get; private set; }. However, there is no { get; private set; } for events, which use the {add{} remove{}} syntax (and that does not solve the problem either).
Is what I'm attempting possible and if so, what have I missed?
View closing means window closing event. So you basically want react on events in the view. I read recently this arcticle, there was a very good image
and also mentioned EventBehavior existence.
Your best bet, if you don't want any code behind, is to use behaviors. Behavior is a simple attached property, which can perform actions, to example rising application-wide commands, which ViewModel can then catch without MVVM issues.
Here is an example of behavior:
public static class FreezeBehavior
{
public static bool GetIsFrozen(DependencyObject obj)
{
return (bool)obj.GetValue(IsFrozenProperty);
}
public static void SetIsFrozen(DependencyObject obj, bool value)
{
obj.SetValue(IsFrozenProperty, value);
}
public static readonly DependencyProperty IsFrozenProperty =
DependencyProperty.RegisterAttached("IsFrozen", typeof(bool), typeof(FreezeBehavior), new PropertyMetadata(OnIsFrozenChanged));
private static void OnIsFrozenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
var freezable = d as Freezable;
if (freezable != null && freezable.CanFreeze)
freezable.Freeze();
}
}
}
it's used like this
<DropShadowEffect ShadowDepth="2" local:FreezeBehavior.IsFrozen="True"/>
It can be attached to any freezable to freeze it. In your case you want to subscribe to event and invoke command or set property, or whatever to inform ViewModel.
What you are asking for is kinda weird, but I'm not going to get into a big long discussion about that....
You don't bind to events - you expose them and the view can add handlers for the events.
Of course this means you will have to put some code behind into the view - but this is fine provided it is UI related. To complete the decoupling your view should only handle the viewmodel as an interface, this means you can easily swap out viewmodels at a later stage.
(Note that I've avoided talking about event triggers).
Related
Im trying to make a WPF based program that will read the textbox input (user input) from a usercontrol and save it as a object proprety. The usercontrol is added to MainWindow. From MainWindow, you have a button which displays a message box with the textbox value. I'm not sure how to connect it all.
Error = CS0103 The name 'minJpValue' does not exist in the current
context WpfApp1
Please help
(Usercontrol .xaml code)
<TextBox x:Name="minJpValue"/>
(Custom class)
public class jpData
{
public double minJpValue
{
get { return minJpValue; }
set { minJpValue = value; }
}
}
(MainWindow .cs code)
private void clickclick(object sender, RoutedEventArgs e)
{
MessageBox.Show(minJpValue);
}
The issue can be easily fixed using the MVVM Patterns,
The code for the usercontrol will be similar to this
<Grid Background="Red">
<TextBox x:Name="minJpValue" Text="{Binding Path=minJpValue}" />
</Grid>
Create new ViewModel for the MainWindow as follows
public class MainWindowViewModel : INotifyPropertyChanged
{
/// <summary>
/// Property Changed Event Handler
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
private String _minJpValue;
public String minJpValue {
get { return _minJpValue; }
set {
_minJpValue = value;
OnPropertyChanged(nameof(minJpValue));
}
}
}
Add the usercontrol into your MainWindow view and in the codebehind set the datacontext to the ViewModel as follows
public MainWindowViewModel CurrentModel = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = CurrentModel;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(CurrentModel.minJpValue);
}
This can solve your current problem. Please see that its working as per expected.
For this quick fix use:
MessageBox.Show(minJpValue.Text);
But a better way would be to store the value on a VM (View Model) and bind the control to it using the MVVM pattern. I provide a basic example on
Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding
Ok let's Understand wt have u done wrong.
<TextBox Name="minJpValue"/>
This line of will Create an object for TextBox and the object will be referred with ur name
minJpValue as u mentioned this will be inside another Usercontrol object and u r try to minJpValue in MainWindow how does it even know that something like minJpValue this exists
So Here is the answer:
objjpData.minJpValue = minJpValue.Text;//Inside user control when ever u change the `minJpValue` u need to set it too bjjpData.minJpValue
pass this objjpData to mainwindow or where u wanna acesses then
MessageBox.Show(objjpData.minJpValue);
Option 2:
MessageBox.Show(UserControlObject.minJpValue.Text);
And PS: Check MVVM once
I have created the following custom Dependency Property in the code behind.
This Dependency property of type infragistics XamDataGrid and so the owner.
I'm trying to get a reference of the grid through this property.
The following code compiles with no errors or warnings. However, the Dependency Property does not show in the XAML intelliSense.
I have tried typing the full name as well. It is not recognizing this DP.
I have cleaned the project and Rebuilt it.
I have even closed Visual Studio and reopened it.
using System.Windows;
using Infragistics.Windows.DataPresenter;
namespace Demo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public static readonly DependencyProperty DPRProperty =
DependencyProperty.Register(
"DPR",
typeof(XamDataGrid),
typeof(MainWindow),
new FrameworkPropertyMetadata(null));
public XamDataGrid DPR
{
get { return (XamDataGrid)GetValue(DPRProperty); }
set { SetValue(DPRProperty, value); }
}
}
}
The problem is that you expect the Dependency Property to show up in your base classes XAML "code"
however, you did not created a DP for Window but for MainWindow
If you would use a <MainWindow /> tag, it would show up
if you could explain what you try to archive, one may could help you further
edit
Think i understood now what your goal is
To get a reference to any object, you do not need a DP but rather have to order everything correctly
things required:
The Window (obviously)
A DataContext
Some Code-Behind
An Attached-Property
The Attached Property is looking pretty much like this
public class Initialized
{
public static DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command",
typeof(ICommand),
typeof(Initialized),
new UIPropertyMetadata(CommandChanged));
public static DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter",
typeof(object),
typeof(Initialized),
new UIPropertyMetadata(null));
public static void SetCommand(DependencyObject target, ICommand value)
{
target.SetValue(CommandProperty, value);
}
public static void SetCommandParameter(DependencyObject target, object value)
{
target.SetValue(CommandParameterProperty, value);
}
public static object GetCommandParameter(DependencyObject target)
{
return target.GetValue(CommandParameterProperty);
}
private static void CommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
var type = target.GetType();
var ev = type.GetEvent("Initialized");
var method = typeof(Initialized).GetMethod("OnInitialized");
if ((e.NewValue != null) && (e.OldValue == null))
{
ev.AddEventHandler(target, Delegate.CreateDelegate(ev.EventHandlerType, method));
}
else if ((e.NewValue == null) && (e.OldValue != null))
{
ev.RemoveEventHandler(target, Delegate.CreateDelegate(ev.EventHandlerType, method));
}
}
public static void OnInitialized(object sender, EventArgs e)
{
var control = sender as FrameworkElement;
var command = (ICommand)control.GetValue(CommandProperty);
var commandParameter = control.GetValue(CommandParameterProperty);
command.Execute(commandParameter);
}
}
In your DataContext (lets name it MainWindowDataContext), create a new ICommand property (Lets name it CmdInitialized) that puts the parameter passed to the command into your instance variable
Then, in your XAML code you just use the AttachedProperty like usual
<!-- `ev:` is the XAML namespace the Initialized class is located in -->
ev:Initialized.Command="{Binding CmdInitialized}"
ev:Initialized.CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
the important part however is: the DataContext needs to be already attached in the window BEFORE the compontents are initialized
this means that you have to edit your window code to something like this:
public MainWindow()
{
this.DataContext = new MainWindowDataContext();
InitializeComponent();
}
Afterwards, you should be fine
if you need solutions for the ICommand, search for RelayCommand on google :)
I'm trying to make two WPF projects and a DLL project in my solution following MVVM.
A first WPF to be used as a management panel for the second WPF (ex: you write text, press a button and the text is displayed in the second WPF Window).
I want to put my Models in the DLL.
My problem is that i don't know how to display the text (notify ?) in the second WPF.
I implemented INotifyPropertyChanged in the viewmodel.
And then I'm stuck, I don't know what to do...
My Solution looks like this :
WPF_Solution
DisplayWPF
MainWindow.xaml
Dll_mvvm
Text.cs :
ManagementWPF
DisplayViewModel.cs
MainWindow.xaml
Both Display and Management refer to the DLL.
Text.cs :
public class Text
{
string _textToDisplay;
/// <summary>
/// Text to display on screen
/// </summary>
public string TextToDisplay
{
get { return _textToDisplay; }
set { _textToDisplay = value; }
}
}
DisplayViewModel.cs :
public class DisplayViewModel : INotifyPropertyChanged
{
private Text _text;
/// <summary>
/// Constructs the default instance of a ToDisplayViewModel
/// </summary>
public DisplayViewModel()
{
_text = new Text();
}
/// <summary>
/// Accessors
/// </summary>
public Text Text
{
get { return _text; }
set { _text = value; }
}
public string TextToDisplay
{
get { return Text.TextToDisplay; }
set
{
if (Text.TextToDisplay != value)
{
Text.TextToDisplay = value;
RaisePropertyChanged("TextToDisplay");
}
}
}
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));
}
}
}
Management Panel :
public partial class MainWindow : Window
{
private DisplayViewModel _displayThings;
private Affichage.MainWindow _displayer = new Affichage.MainWindow();
public MainWindow()
{
InitializeComponent();
_displayThings = (DisplayViewModel)base.DataContext;
_displayer.Show();
}
private void disp_btn_Click(object sender, RoutedEventArgs e)
{
_displayThings.TextToDisplay = textBox.Text;
}
}
And the WPF are just a button and a textbox in the Control window, and a textbox in the Display window. Linked to the viewmodel
<Window.DataContext>
<!-- Declaratively create an instance of DisplayViewModel -->
<local:DisplayViewModel />
</Window.DataContext>
The TextBox is binded with Text="{Binding TextToDisplay}"
Should my Viewmodel be share din the DLL too ?
How to notify the other project that ther was a change in the model ?
Remove the following markup as it will create a new instance of the DisplayViewModel class:
<Window.DataContext>
<!-- Declaratively create an instance of DisplayViewModel -->
<local:DisplayViewModel />
</Window.DataContext>
...and assign the DataContext property of the child window to the instance of the DisplayViewModel class that you are actually setting the TextToDisplay property of:
public partial class MainWindow : Window
{
private DisplayViewModel _displayThings = = new DisplayViewModel();
private Affichage.MainWindow _displayer = new Affichage.MainWindow();
public MainWindow()
{
InitializeComponent();
_displayer.DataContext = _displayThings;
_displayer.Show();
}
private void disp_btn_Click(object sender, RoutedEventArgs e)
{
_displayThings.TextToDisplay = textBox.Text;
}
}
I am at my wits' end. Newbie to WPF so unsure where I am going wrong.
I have an MS Word Interop Add-in that captures selected text, along with various metadata. It composes my model and passes the data to it.
For this stage of development, I made my model a singleton just so that I know everything is pointing to the same reference.
Anyhow, my model is composed of a list of entries.
public sealed class MYMODELSingleton : ObservableObject, IMYMODEL
{
private static MYMODELSingleton instance;
private MYMODELSingleton()
{
isActive = true;
EntryList = new List<MyEntry.IMyEntry>();
}
public static MYMODELSingleton Instance
{
get
{
if(instance == null)
{
instance = new MYMODELSingleton();
}
return instance;
}
}
public bool isActive { get; set; }
private List<MyEntry.IMyEntry> _entryList;
public List<MyEntry.IMyEntry> EntryList
{
get { return _entryList; }
set { _entryList = value; OnPropertyChanged("EntryList"); }
}
public void Add(IMYEntry mEntry)
{
try {
EntryList.Add(mEntry);
OnPropertyChanged("EntryList");
}
catch(ArgumentException ae)
{
throw ae;
}
}
ObservableObject is a custom class I made that implements INotifyPropertyChanged
public abstract class ObservableObject : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
#if DEBUG
MessageBox.Show("Inside Event!");
#endif
var handler = this.PropertyChanged;
if (handler != null)
{
#if DEBUG
MessageBox.Show("Event Fired!");
#endif
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion // INotifyPropertyChanged Members
}
This is my ViewModel
public class MyEntryViewModel : ObservableObject
{
private MYMODELSingleton activeMYMODEL= MYMODELSingleton.Instance;
public MyProjectModel.MYMODEL.MYMODELSingleton ActiveMYMODEL
{
get
{
return activeMYMODEL;
}
set
{
activeMYMODEL = value;
OnPropertyChanged("ActiveMYMODEL");
}
}
private void ModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "EntryList") {
int index = activeMYMODEL.EntryList.Count - 1;
MyEntry = activeMYMODEL.EntryList[index];
UpdateGui(MyEntry.Source);
}
}
public void UpdateGui(SelectionState selState)
{
TxtDocName = selState.SelectionDocName;
TxtDocPage = selState.SelectionPage;
TxtDocText = selState.SelectionText;
}
public MyEntryViewModel()
{
this.MyEntry = new MyEntry();
activeMYMODEL.PropertyChanged += new PropertyChangedEventHandler(ModelPropertyChanged);
//This is to notify me when the VM is created
#if DEBUG
System.Windows.Forms.MessageBox.Show("ViewModelOpened!");
#endif
}
~MyEntryViewModel()
{
activeMYMODEL.PropertyChanged -= new PropertyChangedEventHandler(this.ModelPropertyChanged);
#if DEBUG
System.Windows.Forms.MessageBox.Show("ViewModel Closed!");
#endif
}
}
}
Relevant WPF Code
xmlns:custns="clr-namespace:MyProjectViewModel.MyEntryViewModel;assembly=MyProjectViewModel"
Title="MyProject" Height="350" Width="525" SizeToContent="Height">
<Window.Resources>
<custns:MyyEntryViewModel x:Key="MyProjectObj" />
</Window.Resources>
<Grid Name="gridEntry" DataContext="{StaticResource MyProjectObj}" >
<TextBlock x:Name="docNameTxtBx" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="20" TextWrapping="Wrap" Text="{Binding TxtDocName, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
The problem is, while the entry gets added to mymodel's composed List no problem, the handler in OnPropertyChanged() always equals null. This, despite the fact that I know the ViewModel is instantiated (due to the messageboxes popping up).
FWIW, Model, ViewModel, WPF, and AddIn are all in different assemblies. The Viewmodel subscribes to the Model's event in its constructor, so why is my handler null when it's called???
UPDATE
After discussing this with a colleague, I realized the issue lies in how the MS Word ADDIN and the VM both compose the Model. Both are running in different threads and, apparently, both are holding separate instances of my Singleton! I am not sure how that is happening. I even changed my Singleton's code, making the private instance "volatile" and adding a static Object that I then lock when Instance.get() is called.
private static volatile MyModelSingleton instance;
private static object syncRoot = new Object();
private MyModelSingleton()
{
isActive = true;
EntryList = new List<MyEntry.IMyFEntry>();
}
public static MyModelSingleton Instance
{
get
{
lock (syncRoot)
{
if (instance == null)
{
instance = new MyModelSingleton();
}
}
return instance;
}
}
Nonetheless, both the Word Interop Addin and the Viewmodel are holding separate instances. What am I missing?
in the wpf code you presented, you never bind against ActiveMYMODEL. becuase it is never bound in you xaml, there is never a hook into you notify property changed event created.
I am just starting with WPF and I am trying to setup binding between a local variable and a label. Basicaly I want to update the label when local variable changes. I was searching for solution but they all just use textbox as a source not just class variable and I am not even sure it works this way. So here is my code.
public partial class MainWindow : Window
{
int idCounter;
public MainWindow()
{
InitializeComponent();
Binding b = new Binding();
b.Source = idCounter;
b.Mode = BindingMode.OneWay;
b.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
myLabel.SetBinding(Label.ContentProperty,b);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
idCounter++;
}
}
Button does work, idCounter changes value, but it does not update in label so I guess binding is wrong. Can someone tell me what is wrong? Thanks
Your code will work if you change your class to this...
public partial class Window1 : Window, INotifyPropertyChanged
{
private int _idCounter;
public int IdCounter
{
get { return _idCounter; }
set
{
if (value != _idCounter)
{
_idCounter = value;
OnPropertyChanged("IdCounter");
}
}
}
public Window1()
{
InitializeComponent();
myLabel.SetBinding(ContentProperty, new Binding("IdCounter"));
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
e.Handled = true;
IdCounter++;
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
var handler = System.Threading.Interlocked.CompareExchange(ref PropertyChanged, null, null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
Some of the issues you were having are...
The window itself should implement INotifyPropertyChanged so that the binding engine can place an ear on it.
the IdCounter needs to be public and have a public getter on it so that the binding engine can 'get' it.
You should set the DataContext to whatever class has declared IdCounter (the MainWindow in this case). Part of the problem was that the binding engine had no DataContext.
The BindingMode setting was a red-herring since a Label binds that way by default.
The UpdateSourceTrigger was a red-herring since the content of the label does not have a mechanism to update the source property. A label's content is not like a text box where the user can type something that the code needs to know about. When you're binding to something that the user cannot change, forget about UpdateSourceTrigger, it's the Target property that counts.
The handler should mark the event. This is good practice and did not affect the binding.
The binding constructor needs only the path.
This code will give you your expected result; i.e., that the label updates when the button is clicked. Checked, compiled, and executed on vs2013, .net 4.5.
The other respondents said you should use a View Model. I agree with this 100%, and overall it's a good thing to consider.
You want to use a property to do this, as well as implementing INotifyPropertyChanged so that the label's content gets updated when the property changes.
Here's an example using a simple ViewModel
xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:converters="clr-namespace:WpfApplication1"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Label Width="200" Height="50" Content="{Binding MyLabel}"/>
<Button Height="30" Width="100" Content="Increment" Click="Button_Click" />
</StackPanel>
</Window>
xaml.cs:
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
MainViewModel vm = new MainViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = vm;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
vm.MyLabel += 1;
}
}
}
MainViewModel.cs:
namespace WpfApplication1
{
public class MainViewModel : INotifyPropertyChanged
{
#region Members
private int _myLabel;
#endregion Members
#region Properties
public int MyLabel
{
get
{
return _myLabel;
}
set
{
_myLabel = value;
NotifyPropertyChanged("MyLabel");
}
}
#endregion Properties
public MainViewModel()
{
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
Note: Ideally, you would want to use a Command for the Button instead of a Click event handler
You cannot bind to something that is private or a field so convert it into public property. You can find more as to what is a valid binding source here
If you want changes to your property be picked up by UI you should implement INotifyPropertyChanged interface and raise event each time value of the property changes. So idCounter should look more like this:
private int _idCounter;
public int idCounter
{
get { return _idCounter; }
set
{
if (_idCounter != value)
{
_idCounter = value;
OnPropertyChanged("idCounter");
}
}
}
When you create binding to property you use Path
Binding works in binding context so you need to specify from where to take this Path. Easiest way to do that is to set DataContext. So in your case initialization should look more like this:
Binding b = new Binding("idCounter");
b.Mode = BindingMode.OneWay;
b.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
myLabel.SetBinding(Label.ContentProperty, b);
DataContext = this;
As #d.moncada suggested in his answer you should create dedicated view model