PropertyChangeCallBack not firing on DependencyProperty in a UserControl - wpf

Stumped : I am in the initial stages of creating a month planner control that need to carry out some UI action I The control when supplying a date from the ViewModel bound in XAML. When I run the code, the ViewModel does return the value but the PropertyChangeCallBack is not firing. I have done exactly the same thing in an public class ExtendedDataGrid : DataGrid control and that work fine but it is almost like you have to do something different with UserControls. Here is the basis of my code:
public partial class DiaryMonthViewUserControl : UserControl
{
private DateTime _topDate;
public DiaryMonthViewUserControl()
{
InitializeComponent();
}
// Property to get the date from the ViewModel for processing. This will fire and event to set the month we want to display
public static readonly DependencyProperty TheDateProperty = DependencyProperty.Register("TheDate", typeof(DateTime),
typeof(DiaryMonthViewUserControl),
new FrameworkPropertyMetadata(DateTime.Today, OnDatePropertyChanged, null));
public void SetTheCurrentDate(DateTime CurrentDate)
{
_topDate = CurrentDate;
// Like Outlook, whatever date we supply, I want the first top level displayed date for the month
if (_topDate.Day > 1)
_topDate = _topDate.AddDays(-(_topDate.Day-1)); // First day of the month
// Get to the first Monday before the 1st of the month if not on a Monday
while (_topDate.DayOfWeek != DayOfWeek.Monday)
_topDate = _topDate.AddDays(-1);
// I will set the UI here once I have solved this problem.
MakeChangesToTheUIPlease();
}
private static void OnDatePropertyChanged(DependencyObject Source, DependencyPropertyChangedEventArgs e)
{
var control = Source as DiaryMonthViewUserControl;
if (control != null)
control.SetTheCurrentDate((DateTime)e.NewValue);
}
[Bindable(true)]
public DateTime TheDate
{
get { return (DateTime)GetValue(TheDateProperty); }
set { SetValue(TheDateProperty, value); }
}
}
I have also tried using new PropertyChangedCallback(OnDatePropertyChanged) as a parameter and that still does not work.
My binding is as follows:
<my:DiaryMonthViewUserControl HorizontalAlignment="Left" Margin="12,12,0,0" x:Name="diaryMonthViewUserControl1"
VerticalAlignment="Top" Height="325" Width="476" TheDate="{Binding Path=CurrentDate}" />
When I run the code my ViewModel breaks on the getter for CurrentDate and if I remove the CurrentDate binding then it does not. The problem is that the call back is not firing and, for the life of me, I cannot fathom why.
Any help would be much appreciated, particularly links to articles that may cover this problem.

The PropertyChanged callback only fires if the immediate property is changed, that means the whole DateTime object would need to be replaced, if properties of the DateTime object are changed the event will not be raised. This is why it works for primitive types like int.
If you want to execute some method whenever any property of the DateTime changed you could implement a DateTime class which provides notifications. You can then execute the method whenever any of the properties change.

Related

Simple FastWPFGrid MVVM Example

I am using the FastWPFGrid control found here:
https://github.com/FormatD/FastWpfGrid
I have looked at the sample applications and while there is a lot there to help with the implementation of the actual grid, I am at a bit of a loss as to how to get the binding to work. I am trying to bind the model of the fastgrid to a property of my datacontext - GridViewModel - this in turn is a view model which inherits from the fastgrid viewmodelbase.
So far, so good. The problem is that when the data changes, the notification isn't happening. See my simple example below. In this example, when I change the number of rows nothing happens. If I manually refresh the xaml (by changing the name of the binding to an invalid one and then back) it then updates. I just need to know how to trigger the notifypropertychanged from code.
I would appreciate a very simple example along the lines of the below:
My Xaml:
<fastWpfGrid:FastGridControl Grid.Row="1" Grid.Column="0" Model="{Binding SummaryVm}"/>
The main viewmodel (the datacontext of my form)
public SummaryGridViewModel SummaryVm{ get; set; }
// This Event fires when I know that the row count has changed (for example)
void OnRunListPropertyChanged(Message.RunListPropertyChanged obj)
{
// **This is where I need help to get the view to update
}
The Grid View Model
public class SummaryGridViewModel : FastGridModelBase
{
public SummaryGridViewModel(RunListCalculationQueueManager runList)
{
RunList = runList;
}
RunListCalculationQueueManager RunList { get; }
public override int ColumnCount => 15;
public override int RowCount =>
RunList.ActiveRun == null ? 0 : RunList.BaselineRun == null ? 1 : 3;
}
So the event in the main viewModel fires as expected, but no change is reflected in the view.
I am not necessarily looking for this code to be fixed, but a simple working example of what I am trying to do would be great.
Found the solution, it was pretty simple really -
FastGridModelBase exposes several notify methods. Calling
SummaryVM.NotifyRefresh
caused the notification to fire and everything works well.

Update WPF Window Asynchronously

I am creating a simple WPF app that when you click on a button it runs through a few steps like copy the file to a new location, convert the file then it copies the new file back to the original location.
The steps are working fine but I would like to have the WPF window update to which step it is on and hide the button while it is running.
The window only updates once it has finished running my code. I think I used to be able to do this on classic forms with me.refresh but this doesn't work on WPF.
Is something I can do to update the window after each step is complete?
Thank you
Button1.Visibility = Windows.Visibility.Hidden
FileCopy("C:\Test.xsf", AppPath & "\Convert\test.xsf")
Image7.Visibility = Windows.Visibility.Hidden
Image3.Visibility = Windows.Visibility.Visible
Program.StartInfo.FileName = xDefs
Program.StartInfo.Arguments = "/q"
Program.Start()
Program.WaitForExit()
Image5.Visibility = Windows.Visibility.Visible
FileCopy("AppPath & "\Convert\test.csv, "C:\Test.csv")
Button1.Visibility = Windows.Visibility.Visible
In order to update the UI while your program is busy, you'll need to use the Dispatcher class to add your update request onto the UI message queue. Take this synchronous example:
public void DoWorkWithFile(string filePath)
{
CopyFile(filePath);
ConvertFile(filePath);
CopyFileBack();
}
We could use the Dispatcher class to break this up and feed messages back to the UI in between tasks:
public void DoWorkWithFile(string filePath)
{
CopyFile(filePath);
RunOnUiThread((Action)delegate { SomeUiTextBlock.Text = "Copied" });
ConvertFile(filePath);
RunOnUiThread((Action)delegate { SomeUiTextBlock.Text = "Converted" });
CopyFileBack();
RunOnUiThread((Action)delegate { SomeUiTextBlock.Text = "Copied back" });
}
private object RunOnUiThread(Action method)
{
return Dispatcher.Invoke(DispatcherPriority.Normal, method);
}
I know this is a VB.NET tagged question but I'll just go ahead and share a C# solution. I hope you know enough of it to port it to VB. This is the first time and posting anything to stackoverflow, if it solves your problem please mark it as the answer :-)
You must first know a thing or two (actually a lot more) on data binding. You basically create a view model, define the property that changes with time and bind this to the window. In this case you must define a value to keep track of the current operation and let the button control.
Disclaimer, I wrote this in notepad and haven't tested it on visual studio. Be on the lookout for typos.
using System.ComponentModel;
namespace FileConverter
{
//define the various states the application will transition to
public enum OperationStatus
{
CopyingFileToNewLocation
ConvertingFile,
CopyingFileToOriginalLocation
OperationCompelete
}
//Defines the view model that shall be bound to the window.
//The view model updates the UI using event notifications. Any control that had enabled
//binding will get updated automatically
public class ViewModel : INotifyPropertyChanged//This interface defines an event used to raise an event and notify subscribers of a changed in data
{
private OperationStatus _FileConvertionStatus;
public event PropertyChangedEventHandler PropertyChanged;
public OperationStatus FileConvertionStatus
{
get
{
return _FileConvertionStatus;
}
set
{
_FileConvertionStatus=value;
//Notify all UIElements / objects that had subscribed to this property that it has changed
RaisePropertyChanged(this,"FileConvertionStatus");
}
}
public void RaisePropertyChanged(object sender,string propertyName)
{
//check if there is any object that had subscribed to changes of any of the data properties in the view model
if(PropertyChanged!=null)
PropertyChanged(sender,new PropertyChangedEventArgs(propertyName));
}
public void StartFileConvertion(string filePath)
{
//Any time we change the property 'FileConvertionStatus', an event is raised which updates the UI
this.FileConvertionStatus=OperationStatus.CopyingFileToNewLocation;
StartCopyingToNewLocation(); //call your copying logic
this.FileConvertionStatus=OperationStatus.ConvertingFile;
StartFileConvertion(); //call your conversion logic
this.FileConvertionStatus=OperationStatus.CopyingFileToOriginalLocation();
CopyFileToOriginalLocation(); //...
this.FileConvertionStatus=OperationStatus.OperationCompelete;
}
}
}
//Now for the UI section
In the constructor of the window, you must bind the window to the view model right after this window has been initialized
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ViewModel vm=new ViewModel();
//setting the data context property the window implicitly binds the whole window to our view model object
this.DataContext=vm;
string filePath="c:\file.txt";
//start the file manipulation process
vm.StartFileConvertion(filePath);
}
}
//Next step we need to bind the button to the 'FileConvertionStatus' property located in the view model. We don't bind the button to the whole view model, just the property that it's interested in. Having bound the window to the view model in the previous code, all child elements get access to the public properties of this view model (VM from now on). We do the property binding in XAML
..Button x:Name="btnStartFileProcessing" Enabled="{Binding FileConvertionStatus}"...
We're almost there. One this is missing. You'll notice that the 'Enabled' property is a Boolean value. The 'FileConvertionStatus' property is enum. Same way you can't assign an enum to a Boolean directly, you need to do some convertion. This is where converters come in.
Converters allow you to define how one property can be converted to a different one in XAML. In this case we want the button to be enabled only when file conversion is successful. Please do some reading into this.
Create a class as shown below:
using System.Windows.Data;
namespace FileConverter
{
public class OperationStatusToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType,object parameter,System.Globalization.CultureInfo culture)
{
OperationStatus status=(OperationStatus)value;
switch(status)
{
case OperationStatus.OperationCompelete:
return true; //enable the button when everything has been done
default:
return false;//disable the button as the file processing is underway
}
}
public object ConvertBack(object value, Type targetType,object parameter,System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Next step is to define the converter in XAML code. Think of this as initializing it though it can't be further from the true :-). Its more of importing the namespace into the xaml.Put the code below in the App.XAML file. Doing such declaration in the App.XAML file makes the code visible globally.
xmlns:MyConverters="clr-namespace:FileConverter"
In the Application.Resources XAML tag, declare the converter as shown below
<Application.Resources>
<MyConverters:OperationStatusToBooleanConverter x:Key="OperationStatusToBooleanConverter"/>
</Application.Resources>
Final Step
Redo the binding code in the button to include the converter.
...Button Enabled="{Binding FileConvertionStatus,Converter={StaticResource OperationStatusToBooleanConverter}}" x:Name="btnStartFileProcessing" ...
Please note that I haven't thread-optimized this code, the main problem is that all work is being done on the UI thread which can lead to the window hanging if an operation takes long.
The amount of work needed to properly set the binding up as per MVVM code standards is a lot. It might seem like an over-kill and at times, it actually is. Keep this in mind though, once the UI gets complex MVVM will definitely save the day due to the separation of concerns and binding strategies.

WPF MVVM Bind User Control to Main Window View Model

I asked a similar question a while back here WPF MVVM User Control. I got some answers, but they were way off, so I guess I wasn't clear in what I want to do....
I am working on a WPF application using MVVM. The app is built using a component based approach, so I have some user controls that I've defined that will be used throughout the application. As an example, I have an Address control. I want to use it an multiple places throughout the application. Here's an example. See here:
http://sdrv.ms/1aD775H
The part with the green border is the Address control. The control has its own View Model.
When I place it on a window or other control, I need to tell it the PK of the customer to load addresses for. So I created a Customer ID DependencyProperty:
public partial class AddressView : UserControl
{
public AddressView()
{
InitializeComponent();
}
public static DependencyProperty CustomerIdProperty = DependencyProperty.Register("CustomerId", typeof(int), typeof(AddressView),
new UIPropertyMetadata(0, AddressView.CustomerIdPropertyChangedCallback, AddressView.CustomerIdCoerce, true));
public int CustomerId
{
// THESE NEVER FIRE
get { return (int)GetValue(CustomerIdProperty); }
set { SetValue(CustomerIdProperty, value); }
}
private static void CustomerIdPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
// THIS NEVER FIRES
AddressView instance = (AddressView)d;
instance.CustomerId = (int)args.NewValue;
}
enter code here
private static object CustomerIdCoerce(DependencyObject d, object value)
{
return value; // <=== THIS FIRES ON STARTUP
}
}
Then in the MainWindowView I have:
<vw:AddressView Grid.Row="1"
Grid.Column="0"
x:Name="AddressList"
CustomerId="{Binding ElementName=TheMainWindow, Path=SelectedCustomer.Id, Mode=TwoWay}"/>
Note my comments in the user control's CS. The Coerce fires on startup. The callback never fires, nor do the CustomerId getter or setter.
What I would like to happen seems simple, I just can't make it work....
When a customer is selected the CustomerId should be passed to the Address UserControl. Then in the VM for the Address UserControl should handle getting & saving the data.
So, again, 2 questions:
1) Anyone see what's wrong?
2) How does the UserControl DP send the PK to the ViewModel?
If anyone's interested, my sample project is here: http://sdrv.ms/136bj91
Thanks
Your CustomerId getter and setter will never fire in this situation. They are there simply as helper methods in case you want to access the CustomerIdProperty property from your code behind.
Your CustomerIdPropertyChangedCallback method will not fire because your binding expression is incorrect. You need to bind to the DataContext of MainWindow and not the window itself:
...
CustomerId="{Binding ElementName=TheMainWindow, Path=DataContext.SelectedCustomer.Id}"
...
Also, make sure that you are calling the INotifyPropertyChanged PropertyChanged event when the property bound to the ComboBox is changed.
Try this :
CustomerId="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=DataContext.YourSelectedItem.TheProperty}"
I am not sure how to manage your seleted item in window, so please change yourSelectedItem.TheProperty accordingly.

Order In Chaos Of Control Initialization Steps

I would like to know when is actually what happening inside initalization process of controls when I start a WPF application?
When are DP initalized? When Binding? When does DataContext get set? Is DataContext avaialbe in constructor of a control? Is there any kind of order?
I realized I ran into a trap that once I set a value on getter/setter of a DP inside constructor of a control the DP value gets updated but immediately also the values gets rolled back to default value which was null.
So my guess is that contructors get initalized first and then dependency properties.
Can somebody help me out with this?
Edit: Just for Rachel. The dp receives the value 234 and immedialty rolls back to null. I think its because constructor gets called first and then subsequently the initalizing of dps happens which sets dp back to null because null is default value. Am i thinking wrong about this? What is the order of initalization steps of a control or dependency object.
class MySuperDuperCoolClass : ContentControl
{
public MySuperDuperCoolClass()
{
InitalizeComponents();
this.MySuperDuperProperty = "234";
}
public string MySuperDuperProperty
{
get { return (string)GetValue(MySuperDuperPropertyProperty);}
set { SetValue(MySuperDuperPropertyProperty, value);}
}
public static DependencyProperty MySuperDuperPropertyProperty =
DependencyProperty.Register("MySuperDuperProperty", typeof(string), typeof(MySuperDuperCoolClass),
new PropertyMetadata(null));
}
I find the DispatcherPriority Enum useful for recalling the exact event order:
Send
Normal - Constructors run here
DataBind
Render
Loaded
Background
ContextIdle
ApplicationIdle
SystemIdle
Inactive
Invalid
Input
As you can see, Constructors get run first, followed by data bindings.
DependencyProperties get initialized when the object gets created, just like any other property, so that would occur prior to the constructor being run so the property exists in the constructor.
Setting the DataContext property or other DependencyProperties works just like any other property you are setting. If you set them with a binding, they'll get evaluated after the constructor. If you set them in the XAML, they'll get set in the Constructor. If you set them in the Loaded event, they'll get set after everything has been constructed, bound, and rendered.
You also might find this SO answer useful:
Sequence of events when a Window is created and shown
As requested, here is the sequence of major events in WPF when a
window is created and shown:
Constructors and getters/setters are called as objects are created, including PropertyChangedCallback, ValidationCallback, etc on the
objects being updated and any objects that inherit from them
As each element gets added to a visual or logical tree its Intialized event is fired, which causes Styles and Triggers to be
found applied in addition to any element-specific initialization you
may define [note: Initialized event not fired for leaves in a logical
tree if there is no PresentationSource (eg Window) at its root]
The window and all non-collapsed Visuals on it are Measured, which causes an ApplyTemplate at each Control, which causes additional
object tree construction including more constructors and
getters/setters
The window and all non-collapsed Visuals on it are Arranged
The window and its descendants (both logical and visual) receive a Loaded event
Any data bindings that failed when they were first set are retried
The window and its descendants are given an opportunity to render their content visually
Steps 1-2 are done when the Window is created, whether or not it is
shown. The other steps generally don't happen until a Window is
shown, but they can happen earlier if triggered manually.
Edit based on code added to question
Your DependencyProperty.Register method looks funny to me. The signature of the method doesn't match any of the overloads for that method, and you're using what appears to be a custom UIProperty class to set the default value instead of the normal PropertyMetadata.
I can confirm that if your code runs as expected with a normal DependencyProperty.Register signature, so the likely cause of your problem is either somewhere within your custom code, or its with how you are using/setting the property.
The code I used for a quick sample test is this:
public partial class UserControl1 : ContentControl
{
public UserControl1()
{
InitializeComponent();
this.TestDependencyProperty = "234";
}
public string TestDependencyProperty
{
get { return (string)GetValue(TestDependencyPropertyProperty); }
set { SetValue(TestDependencyPropertyProperty, value); }
}
public static DependencyProperty TestDependencyPropertyProperty =
DependencyProperty.Register("TestDependencyProperty", typeof(string), typeof(UserControl1),
new PropertyMetadata(null));
}
and the XAML is
<ContentControl x:Class="WpfApplication1.UserControl1"
x:Name="TestPanel" ...>
<Label Content="{Binding ElementName=TestPanel, Path=TestDependencyProperty}"/>
</ContentControl>
In WPF you are setting default values for DP with PropertyMetaData not via constructor.
public partial class UserControl1 : ContentControl
{
public UserControl1()
{
InitializeComponent();
}
public string TestDependencyProperty
{
get { return (string)GetValue(TestDependencyPropertyProperty); }
set { SetValue(TestDependencyPropertyProperty, value); }
}
public static DependencyProperty TestDependencyPropertyProperty =
DependencyProperty.Register("TestDependencyProperty", typeof(string), typeof(UserControl1),
new PropertyMetadata("234"));
}

WPF DateTimePicker not setting date after validation

I'm trying to validate the selected date in a datetime picker control and setting it to today's date if the date selected is > Datetime.Today.The issue I'm facing is that I'm not able to set the SelectedDate property of a datetimepicker control via xaml.I feel something is wrong with my binding, please can you help?
Following is the code.Please can you tell me what 'am I doing wrong?
<Controls:DatePicker Height="20"
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
x:Name="dateControl"
IsTodayHighlighted="True"
Margin="5,10,5,20"
SelectedDate="{Binding Path=BindingDate, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
public class Context : INotifyPropertyChanged
{
public Context() { }
private DateTime bindingDate = DateTime.Today;
public DateTime BindingDate
{
get
{
return bindingDate;
}
set
{
if (DateTime.Compare(DateTime.Today, value) < 0)
{
MessageBox.Show("Please Select Today date or older, Should not select future date");
//This is not reflected anytime in SelectedDate property of the control, why???
value = DateTime.Today;
}
bindingDate = value;
OnPropertyChanged("BindingDate");
}
}
..and yes I'm setting the datacontext of the window like the following:
public Window1()
{
InitializeComponent();
this.DataContext = new Context();
}
Any suggestions would be highly appreciated.
Thanks,
-Mike
That is because the BindingDate setter will never be called if you set value for your local variable bindingDate and your ui will never be notified.
Instead of setting
private DateTime bindingDate = DateTime.Today.AddDays(13);
try setting
BindingDate = DateTime.Today.AddDays(13);
EDIT
But selecting a future date in the datepicker will remain even after showing the messagebox because the selection is already made in the control and will not reset back.
But you can consider other alternatives like blocking all future dates from selection by using the BlackoutDates or DisplayDates property of the datepicker or you can conside using custom validation rules as mentioned in the below post
Date picker validation WPF
You could consider implementing INotifyPropertyChanging also, and not only INotifyPropertyChanged.
In that way you can alsol notify that your property is about to change, and run some code accordingly.
And of course notify that your property has effectively changed.

Resources