I'm currently learning WPF, DataContexts & DataBinding. My goal is to have a Taskbar task (using NotifyIconWpf) that has a continuous thread running the background to monitor a network.
I've managed to get a UI element (shown in screenshot) bound to the ProgramClock class, but it does not update when the ProgramClock changes, most likely because something in the INotifyPropertyChanged parameters are wrong.
The closest similar problem I've found is UI not being updated INotifyPropertyChanged however I haven't been able to figure out what to change the DataPath in the XAML, or how to make INotifyPropertyChanged work properly.
Note that the BackgroundWorker thread successfully updates the App's static ProgramClock (checked with a separate WinForm) and that time is initially loaded in the WPF, so it's probably the PropertyChanged not being called properly.
ProgramClock
public class ProgramClock : INotifyPropertyChanged
{
private DateTime _myTime;
public event PropertyChangedEventHandler PropertyChanged;
private ClockController clockController;
public ProgramClock()
{
this._myTime = DateTime.Now;
clockController = new ClockController();
MessageBox.Show("created new clock");
}
public DateTime MyTime
{
get
{
return this._myTime;
}
set
{
if (_myTime == value) return;
_myTime = value;
//System.Windows.Forms.MessageBox.Show(PropertyChanged.ToString());
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(_myTime.ToString()));
}
}
public string MyTimeString
{
get { return this._myTime.ToString(); }
}
public void UpdateTime()
{
this.MyTime = DateTime.Now;
}
}
Bubble CS
public partial class InfoBubble : System.Windows.Controls.UserControl
{
public InfoBubble()
{
InitializeComponent();
this.DataContext = App.ClockBindingContainer;
}
}
Bubble XAML
<UserControl x:Class="FileWatcher.Controls.InfoBubble"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008">
<Border
Background="White"
BorderBrush="Orange"
BorderThickness="2"
CornerRadius="4"
Opacity="1"
Width="160"
Height="40">
<TextBlock
Text="{Binding Path=MyTimeString}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
</UserControl>
Main app
public partial class App : System.Windows.Application
{
private TaskbarIcon tb;
private ResourceDictionary _myResourceDictionary;
public static ProgramClock _programClock = new ProgramClock();
private void Application_Startup(object sender, StartupEventArgs e)
{
NotifIconStarter();
}
public static ProgramClock ClockBindingContainer
{
get { return _programClock; }
}
}
One problem is in your invocation of the PropertyChanged event. You need to pass the name of the property that is changing to the PropertyChangedEventArgs not the new value.
So use:
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("MyTime"));
instead of:
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(_myTime.ToString()));
However, you are actually binding to another property - MyTimeString.
Ultimately the property you are binding to needs to raise the event.
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(_myTime.ToString()));
You should pass property name:
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("MyTime");
However I suggest you to get PostSharp library - it has nice features that enable you to write normal properties and "decorate it by attribute" with automatic raising of PropertyChanged. If you do not want to use PostSharp at least create some method like:
public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
and call it in your setter. ([CallerMemberName] is C# 5.0 feature which automatically pass "caller" name (in setter it will pass property name )
You are not notifying the change in the property you are binding to (which is MyTimeString), so WPF knows MyTime changes, but does MyTimeString change too? was never notified.
Try to change this:
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(_myTime.ToString()));
To this:
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("MyTimeString")); // Not MyTime!
Related
I know how to get cpu load value using performance counter but have no idea how to make a label display it in real time
Bind your label content to your ViewModel:
XAML (YourView.xaml.cs):
<Label Content="{Binding CPUText}" />
Your View Model would look like this:
public class YourViewModel : INotifyPropertyChanged
{
public void GetCpuText()
{
//your code here....
//it would populate your CPUText property...
CPUText = .... (your code to get the cpu info)
}
private _cpuText;
public string CPUText
{
get
{
return _cpuText;
}
set
{
_cpuText = value;
NotifyPropertyChanged("CPUText");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
An example to make this work is to create your view, set the DataContext of that view to your ViewModel class:
var view = new YourView();
view.DataContext = new YourViewModel();
view.GetCpuText();
I've a hard time understanding why ICommand.CanExecutes always contains the previous value instead of the new value if a nested property is used instead of a normal property.
The problem is described below and I seriously can't figure out a way to fix this besides using some form of "Facade" pattern where I create properties in the viewmodel and hook them to their corresponding property in the model.
Or use the damn CommandManager.RequerySuggested event. The reason this is not optimal is because the view presents over 30 commands, just counting the menu, and if all CanExecute updates every time something changes, it will take a few seconds for all menuitems / buttons to update. Even using the example down below with only a single command and button together with the command manager it takes around 500ms for the button to enable/disable itself.
The only reason I can think of is that the CommandParameter binding is not updated before the CanExecute is fired and then I guess there is nothing you can do about it.
Thanks in advance :!
For example
Let's say we've this basic viewmodel
public class BasicViewModel : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set {
this.name = value;
RaisePropertyChanged("Name");
Command.RaiseCanExecuteChanged();
}
}
private Project project;
public Project Project
{
get { return project; }
set {
if (project != null) project.PropertyChanged -= ChildPropertyChanged;
if (value != null) value.PropertyChanged += ChildPropertyChanged;
project = value;
RaisePropertyChanged("Project");
}
}
private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e) {
Command.RaiseCanExecuteChanged();
}
public DelegateCommand<string> Command { get; set; }
public BasicViewModel()
{
this.Project = new Example.Project();
Command = new DelegateCommand<string>(this.Execute, this.CanExecute);
}
private bool CanExecute(string arg) {
return !string.IsNullOrWhiteSpace(arg);
}
private void Execute(string obj) { }
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName = null) {
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
and this model
public class Project : INotifyPropertyChanged
{
private string text;
public string Text
{
get { return text; }
set
{
text = value;
RaisePropertyChanged("Text");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName = null)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Now in my view I've this textbox and button.
<Button Content="Button" CommandParameter="{Binding Path=Project.Text}" Command="{Binding Path=Command}" />
<TextBox Text="{Binding Path=Project.Text, UpdateSourceTrigger=PropertyChanged}" />
It works, every time I type something in the textbox the CanExecute is invoked, BUT the parameter is always set to the previous value. Let say I write 'H' in the textbox, CanExecute is fired with parameter set to NULL. Next I write 'E', now the textbox contains "HE" and the CanExecute fires again. This time with the parameter set to 'H' only.
For some strange reason the parameter is always set to the previous value and when I check the Project.Text it's set to "HE" but parameter is still set to only 'H'.
If I now change the command parameter to
CommandParameter="{Binding Path=Name}"
and the Textbox.Text to
Text={Binding Path=Name, UpdateSourceTrigger=PropertyChanged}"
everything works perfectly. The CanExecute parameter always contain the latest value and not the previous value.
The facade pattern you're talking about it standard WPF practice. The main problem with the way that you're doing it is that when events are raised, their subscribed event handlers execute in the order that they are subscribed. The line of code where you have:
if (value != null) value.PropertyChanged += ChildPropertyChanged;
This subscribes to the "PropertyChanged" Event of your "Project" class. Your UIElements are also subscribed to this same "PropertyChanged" event through your binding in the XAML. In short, your "PropertyChanged" event now has 2 subscribers.
The thing about events is that they fire in a sequence and what's happening in your code, is that when the event fires from your "Project.Text" it executes your "ChildPropertyChanged" event, firing your "CanExecuteChanged" event, which finally runs your "CanExecute" function(which is when you're seeing the incorrect parameter).
THEN, after that, your UIElements get their EventHandlers executed by that same event. And their values get updated.
It's the order of your subscriptions causing the problem. Try this and tell me if it fixes your problem:
public Project Project
{
get { return project; }
set {
if (project != null) project.PropertyChanged -= ChildPropertyChanged;
project = value;
RaisePropertyChanged("Project");
if (project != null) project.PropertyChanged += ChildPropertyChanged;
}
}
This is how I would have done this, and it works as expected. The only difference here is I'm using RelayCommand instead of DelegateCommand - they fundamentally have the same implementation so they should be interchangeable.
When the user enters the text and then clicks the button the execute method of the RelayCommand gets the expected text - simple.
XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Grid.Column="0"
Grid.Row="0"
Text="{Binding Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Column="0"
Grid.Row="1"
Content="Test"
VerticalAlignment="Bottom"
HorizontalAlignment="Center"
Command="{Binding Path=TextCommand, Mode=OneWay}" />
</Grid>
ViewModel:
public sealed class ExampleViewModel : BaseViewModel
{
private string _text;
public ExampleViewModel()
{
TextCommand = new RelayCommand(TextExecute, CanTextExecute);
}
public string Text
{
get
{
return _text;
}
set
{
_text = value;
OnPropertyChanged("Text");
}
}
public ICommand TextCommand { get; private set; }
private void TextExecute()
{
// Do something with _text value...
}
private bool CanTextExecute()
{
return true;
}
}
I found this great attached property from swythan on the prism codeplex discussion forum that did the trick very well. Of course it does not answer why the command parameter is set to the previous value but it fixes the problem in a nice way.
The code is slightly modified from the source, enabling the possibility to use it on controls in a TabItem by calling HookCommandParameterChanged when the OnLoaded event is invoked.
public static class CommandParameterBehavior
{
public static readonly DependencyProperty IsCommandRequeriedOnChangeProperty =
DependencyProperty.RegisterAttached("IsCommandRequeriedOnChange",
typeof(bool),
typeof(CommandParameterBehavior),
new UIPropertyMetadata(false, new PropertyChangedCallback(OnIsCommandRequeriedOnChangeChanged)));
public static bool GetIsCommandRequeriedOnChange(DependencyObject target)
{
return (bool)target.GetValue(IsCommandRequeriedOnChangeProperty);
}
public static void SetIsCommandRequeriedOnChange(DependencyObject target, bool value)
{
target.SetValue(IsCommandRequeriedOnChangeProperty, value);
}
private static void OnIsCommandRequeriedOnChangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is ICommandSource))
return;
if (!(d is FrameworkElement || d is FrameworkContentElement))
return;
if ((bool)e.NewValue)
HookCommandParameterChanged(d);
else
UnhookCommandParameterChanged(d);
UpdateCommandState(d);
}
private static PropertyDescriptor GetCommandParameterPropertyDescriptor(object source)
{
return TypeDescriptor.GetProperties(source.GetType())["CommandParameter"];
}
private static void HookCommandParameterChanged(object source)
{
var propertyDescriptor = GetCommandParameterPropertyDescriptor(source);
propertyDescriptor.AddValueChanged(source, OnCommandParameterChanged);
// N.B. Using PropertyDescriptor.AddValueChanged will cause "source" to never be garbage collected,
// so we need to hook the Unloaded event and call RemoveValueChanged there.
HookUnloaded(source);
}
private static void UnhookCommandParameterChanged(object source)
{
var propertyDescriptor = GetCommandParameterPropertyDescriptor(source);
propertyDescriptor.RemoveValueChanged(source, OnCommandParameterChanged);
UnhookUnloaded(source);
}
private static void HookUnloaded(object source)
{
var fe = source as FrameworkElement;
if (fe != null)
{
fe.Unloaded += OnUnloaded;
fe.Loaded -= OnLoaded;
}
var fce = source as FrameworkContentElement;
if (fce != null)
{
fce.Unloaded += OnUnloaded;
fce.Loaded -= OnLoaded;
}
}
private static void UnhookUnloaded(object source)
{
var fe = source as FrameworkElement;
if (fe != null)
{
fe.Unloaded -= OnUnloaded;
fe.Loaded += OnLoaded;
}
var fce = source as FrameworkContentElement;
if (fce != null)
{
fce.Unloaded -= OnUnloaded;
fce.Loaded += OnLoaded;
}
}
static void OnLoaded(object sender, RoutedEventArgs e)
{
HookCommandParameterChanged(sender);
}
static void OnUnloaded(object sender, RoutedEventArgs e)
{
UnhookCommandParameterChanged(sender);
}
static void OnCommandParameterChanged(object sender, EventArgs ea)
{
UpdateCommandState(sender);
}
private static void UpdateCommandState(object target)
{
var commandSource = target as ICommandSource;
if (commandSource == null)
return;
var rc = commandSource.Command as RoutedCommand;
if (rc != null)
CommandManager.InvalidateRequerySuggested();
var dc = commandSource.Command as IDelegateCommand;
if (dc != null)
dc.RaiseCanExecuteChanged();
}
}
Source: https://compositewpf.codeplex.com/discussions/47338
I am new to MVVM and am writting a little test-application using it.
I've got a Model, which represents the data structure -
a ViewModel and a View (parent class is Page), too.
Now i would like to pass some initial data to the Model,
so the window could show me these.
In a sample application of David Anderson, he passes the data
as method-parameter, which is actually the right way and causes
not the trigger of the PropertyChanged-Event, but my Model-class
is quite "fat" - it gots a lot of properties (> 30).
So, how shall i realize it in this case?
I don't think a method with more then 30 parameters is the right
way to handle that. Or am i wrong?
Does someone has an idea, how professionals realize this?
Here is my used code:
View (PersonPropertiesView is a subclass of the Page-class)
XAML
<TextBlock Text="{Binding Person.ID, UpdateSourceTrigger=PropertyChanged}" />
Code-Behind
public PersonPropertiesView()
{
InitializeComponent();
this.DataContext = new PersonPropertiesViewModel();
}
ViewModel (PersonPropertiesViewModel)
Code
private Person _Person;
public Person Person
{
get
{
return this._Person;
}
}
public Person()
{
this._Person = new Individual();
this._Person.ID = 12;
}
Model (Person, inherits INotifyPropertyChanged)
private long _ID;
public long ID
{
get
{
return this._ID;
}
set
{
if (this._ID != value)
{
this._ID = value;
OnPropertyChanged("ID");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (propertyName != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
If i try to compile this code, i get the System.Reflection.TargetInvocationException exception. Does someone knows why?
My first reaction would be: why? I'm not sure this is too important - when setting these properties immediately after construction, I would imagine it's fairly likely that this occcurs before the ViewModel is bound to any View (though without the context, I couldn't be sure).
The cost of attempting to fire the PropertyChanged event with no observers is likely too small to measure.
In the code you've posted, this code will throw an exception when there are no listeners:
protected void OnPropertyChanged(string propertyName)
{
if (propertyName != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
You need to check if the event is null, not the property name:
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
we have a problem with data binding on windows phone (using xaml). i have created a simple example, which should allow to reproduce the problem.
Here is our model-class:
public class Data : INotifyPropertyChanged
{
private int value = 0;
public int Value
{
get
{
return value;
}
set
{
this.value = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Value"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public Data()
{
var t = new Thread(new ThreadStart(() =>
{
while (true)
{
Thread.Sleep(2000);
Value += 1;
}
}));
t.IsBackground = true;
t.Start();
}
}
which uses a thread to update the value-property and fire the PropertyChanged-event.
Now i want to bind this value-property to a gui control:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<TextBlock Text="{Binding Path=Value}" />
</Grid>
public MainPage()
{
InitializeComponent();
DataContext = new Data();
}
when the value first changes (and the PropertyChanged-event gets fired) the data binding system tries to copy the value of Data.Value to TextBlock.Text, which results in an invalid cross-thread exception, as this event is not fired on the ui thread.
my question: shouldn't the .NET databinding framework recognize that i'm binding to a ui control and perform the thread switching itself? i know that i can simply use a dispatcher to fire the PropertyChanged-event on the main thread, but i'd like to have my model-class more seperated from the gui component.
is there a better solution to this problem? i am unable to use the DependencyObject approach, because our core project (which contains the model class) should run on Windows Phone AND Android, and Android doesn't support the System.Windows-namespace.
One way to solve this would be to store a reference to the dispatcher on your view model and only use it to execute the property changed event if it is not null. Then you can set the dispatcher property in your VM's constructor.
I do like this:
public event PropertyChangedEventHandler PropertyChanged;
private void PropertyEventChanged(string propertyName)
{
if (PropertyChanged == null) return;
if (Application.OpenForms.Count > 0 && Application.OpenForms[0].InvokeRequired)
Application.OpenForms[0].Invoke(new MethodInvoker(() => PropertyChanged(this, new PropertyChangedEventArgs(propertyName))));
else
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
So i have something along the lines of
private ObservableCollection<ViewModel> _internal;
public ObservableCollection<ViewModel> BoundInternal{get;set}; //this is Binded in the Itemssource like ItemSource={Binding BoundInternal}
Now In my code i do something like
BoundInternal=_internal, However the problem is the BoundInternal isn't trigger any collectionChanged event. I have to use the Add method. So I am wondering if there is a solution to this.
Here is what I suspect your code ought to look like like (although its not quite a match for what you currently doing):-
public class YourClassHoldingThisStuff : INotifyProperyChanged
{
private ObservableCollection<ViewModel> _internal;
public ObservableCollection<ViewModel> BoundInternal
{
get { return _internal; }
set
{
_internal = value;
NotifyPropertyChanged("BoundInternal");
};
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new ProperytChangedEventArgs(name));
}
}
In this case the _internal field becomes the source of the value of BoundInternal directly and you should only assign it via BoundInternal, (don't assign a value directly to _internal). When that occurs anything currently bound to it will be informed of the change.
If for some reason you really do need to maintain _internal as a separate reference from the backing field of BoundInternal then:-
public class YourClassHoldingThisStuff : INotifyProperyChanged
{
private ObservableCollection<ViewModel> _internal;
private ObservableCollection<ViewModel> _boundInternal;
public ObservableCollection<ViewModel> BoundInternal
{
get { return _boundInternal; }
set
{
_boundInternal = value;
NotifyPropertyChanged("BoundInternal");
};
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new ProperytChangedEventArgs(name));
}
}
Now at some point in your code when you do BoundInternal = _internal, anything bound to it will be informed of the change.
Every ItemsControl has a, Items property which has a Refresh() method that you can call, which will update your list.
MyList.Items.Refresh()