How to refresh binding data in silverlight mvvm using NotificationObject - silverlight

i'm working on a textbox required TimeSpan value. the input content need to be validated and may in several different formats (for ex 1300 means 13:00). I do some work to check and convert it in viewmodel. but after that how can i refresh the text in textbox?
<TextBox Text="{Binding Path= OpenHourFromText, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" ></TextBox>
OpenHourFromValue is a string property that i used for validation and data binding
public class MainPageViewModel : NotificationObject{
public string OpenHourFromText
{
get
{
//OpenHourFrom is a TimeSpan property that contain the value
if (OpenHourFrom != null)
{
return GetOpeningHourText(OpenHourFrom); //fomat the time
}
else
{
return "";
}
}
set
{
//do validation and convert here. 1300 will be changed to 13:00 TimeSpan type
OpenHourFrom = ConvertToTimeSpan(value);
RaisePropertyChanged("OpenHourFromText");
}
}
public TimeSpan OpenHourFrom { get; set; }
}
the viewmodel is inherit from Microsoft.Practices.Prism.ViewModel.NotificationObject
After i input 1300 in the textbox, the OpenHourFrom is updated. But the text of textbox is not changed to 13:00. why? please help, many thx.

When TextBox is setting some value it won't call get.The solution to this can be like replacing RaisePropertyChanged("OpenHourFromText") with Dispatcher.BeginInvoke(() => RaisePropertyChanged("OpenHourFromText"));It will delay firing that event.
set
{
//do validation and convert here. 1300 will be changed to 13:00 TimeSpan type
OpenHourFrom = ConvertToTimeSpan(value);
Dispatcher.BeginInvoke(() => RaisePropertyChanged("OpenHourFromText"));
}

You're raising a PropertyChange notification for the property UpdateTimeText, while your actual property name is OpenHourFromText
Change your PropertyChange notification to raise the notification for the correct property, and it should update for you.

Related

Silverlight control's input validation

I want to implement two types of validation in my silverlight application. I want "business-logic" rules to be implemented in viewmodel(like end date is not earlier than start date) which i have already accomplished, and input validation somewhere on main control, where input fields are(like date is in bad format). Is there anything silverlight can "help" me with? I mean there is at least UnsetValue there for me, but is there any event associated or i have to catch all OnChanged events? Also is there a way to manually display red border around control when i want to?
Sorry, it was not obvious from my question, but i finished with the part that includes "business-logic" rules - my viewmodel indeed implements INotifyDataErrorInfo, i'm troubled with second type of validation.
Implement INotifyDataErrorInfo on your ViewModel to enable validation on View Model level.
Implement INotifyDataErrorInfo on your properties
then on your property that is binded in XAML use a friendly display name:
private DateTime? _datumP = DateTime.Now;
[Display(Name = "Date", ResourceType = typeof(CommonExpressions))]
public DateTime? DatumP
{
get
{
return _datumP;
}
set
{
if (_datumP != value)
{
_datumP = value;
RaisePropertyChanged(DatumPonudbePropertyName);
}
ValidateDate(DatumPonudbe, DatumPonudbePropertyName);
}
}
Then your method to validate dates:
public void ValidateDate(DateTime? value, string propertyName)
{
RemoveError(propertyName, CommonErrors.DatumNull_ERROR);
if (value == null)
AddError(propertyName, CommonErrors.DatumNull_ERROR, false);
}
And now for the XAML part:
<sdk:DatePicker Width="100" SelectedDate="{Binding DatumP, Mode=TwoWay,
NotifyOnValidationError=True, ValidatesOnNotifyDataErrors=True,
ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" />
P.S.
CommonExpressions and CommonErrors are my Resource files for multilanguage, you can use plain strings here.

How to rollback selected SelectedValue of the combo box using WPF MVVM

I have something like it will pop to the user for getting confirmation of changes. If he clicks no I am setting the selectedValue in view model to the previous selection. But its not getting displayed correctly in view. Please help.
Very simple solution for .NET 4.5.1+:
<ComboBox SelectedItem="{Binding SelectedItem, Delay=10}" ItemsSource="{Binding Items}" />
It's works for me in all cases.
Just fire NotifyPropertyChanged without value assignment to rollback.
If the user clicks no and you try to revert the value and then call OnPropertyChanged, WPF will swallow the event since it is already responding to that event. One way to get around this is to use the dispatcher and call the event outside of the current event context.
System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => { OnPropertyChanged("ComSelectedValue"); }), null);
WPF seems to validate that the bound property has changed before updating the UI. So simply invoking an NotifyPropertyChanged()/OnPropertyChanged() doesn't do the trick.
The problem is that since the property hasn't changed, WPF doesn't think it needs to refresh the combo box.
here is the incredibly hackish way I handle it in my ViewModels;
private int _someProperty = -1;
public int SomeProperty
{
if (_someProperty != -1 && doYesNo() == Yes)
{
_someProperty = value;
}
else if (_someProperty != -1)
{
int oldValue = _someProperty;
_someProperty = -1; // Set to a different/invalid value so WPF sees a change
Dispatcher.BeginInvoke(new Action(() => { SomeProperty = oldValue; }));
}
else
{
_someProperty = value;
}
NotifyPropertyChanged("SomeProperty");
}
Not pretty but it works reliably.
Assumptions:
- You show a dialog box (with a message and OKCancel buttons) when a user selects some value from ComboBox.
- If user presses OK, everything is OK. :)
- If user presses Cancel, you say vmPropSelectedValue=previousValue.
This won't work. Why?
Don't have exact answer, but I believe when you show the dialog the system has just changed the selected value and has just notified the Source (via binding infrastructure) about the changed value . If at this moment (when source has control) you now change the value of ViewModel property from your VM code, which you expect would trigger OnPropertyChanged of INotifyPropertyChanged, which you expect would ask the WPF to update the target with your requested value. However, the WPF has not yet completed the cycle - its still waiting for the Source to return the control back to it. So it simply rejects your request (otherwise it would go in infinite loop).
If this was confusing, try this:
Cycle starts:
1. User changes value on UI. WPF changes target.
2. binding infrastructure requests Source to update itself.
3. Source updates itself (VM property).
4. Source returns control back to binding infra.
Cycle End.
Experts: Can't find some documentation in this regard. Above is my belief how things work. Please rectify if incorrect.
Short Answer:
AFAIK, this can't be done via pure VM code alone. You will have to put some code-behind code.
Here's one way: http://www.amazedsaint.com/2008/06/wpf-combo-box-cancelling-selection.html
In most WPF applications you bind a view model to the user interface with a TwoWay mode and then you're set to go.
However this goes against the typical user experience, where when you edit something and you don't save, you don't see that editing reflected throughout your entire application, even if you don't save your changes to the Database.
The mechanism available in WPF is the UpdateSourceTrigger property of the Binding. With this property you can control when the User Interface updates the ViewModel that it is bound to. This allows you to update only when the user saves what he's editing or something similar.
An example of a XAML Binding with the UpdateSourceTrigger set to Explicit:
"{Binding Path=Privado, UpdateSourceTrigger=Explicit, Mode=TwoWay}"
And when you want to really save to the ViewModel you call:
UpdateSource();
What if you tried to raise the property changed event asynchronously? This is similar the examples from shaun and NebulaSleuth.
public int SomeProperty
{
get { return m_someProperty; }
set
{
if (value == m_someProperty)
return;
if (doYesNo() == No)
{
// Don't update m_someProperty but let the UI know it needs to retrieve the value again asynchronously.
Application.Current.Dispatcher.BeginInvoke((Action) (() => NotifyOfPropertyChange("SomeProperty")));
}
else
{
m_someProperty = value;
NotifyOfPropertyChange("SomeProperty");
}
}
}
Here is the general flow that I use:
I just let the change pass through the ViewModel and keep track of whatever's passed in before.
(If your business logic requires the selected item to not be in an invalid state, I suggest moving that to the Model side). This approach is also friendly to ListBoxes that are rendered using Radio Buttons as making the SelectedItem setter exit as soon as possible will not prevent radio buttons from being highlighted when a message box pops out.
I immediately call the OnPropertyChanged event regardless of the value passed in.
I put any undo logic in a handler and call that using SynchronizationContext.Post()
(BTW: SynchronizationContext.Post also works for Windows Store Apps. So if you have shared ViewModel code, this approach would still work).
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public List<string> Items { get; set; }
private string _selectedItem;
private string _previouslySelectedItem;
public string SelectedItem
{
get
{
return _selectedItem;
}
set
{
_previouslySelectedItem = _selectedItem;
_selectedItem = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
SynchronizationContext.Current.Post(selectionChanged, null);
}
}
private void selectionChanged(object state)
{
if (SelectedItem != Items[0])
{
MessageBox.Show("Cannot select that");
SelectedItem = Items[0];
}
}
public ViewModel()
{
Items = new List<string>();
for (int i = 0; i < 10; ++i)
{
Items.Add(string.Format("Item {0}", i));
}
}
}
I realize this is an old post but it seems no one has done this the right way. I used System.Interactivity.Triggers and Prism to process the SelectionChanged event and manually trigger the SelectedItem. This will prevent undesired Selected Item Changes in both the UI and the View-Model.
My view:
<Window x:Class="Lind.WPFTextBlockTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Lind.WPFTextBlockTest"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Interactivity;assembly=Microsoft.Practices.Prism.Interactivity"
Title="MainWindow" Height="649" Width="397">
<Window.DataContext>
<vm:MainWindowViewModel/>
</Window.DataContext>
<StackPanel>
<ComboBox ItemsSource="{Binding Data}" SelectedItem="{Binding SelectedData, Mode=OneWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<prism:InvokeCommandAction Command="{Binding TryChangeSelectedData}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
My View-Model (BindeableBase and DelegateCommand from Prism 5):
public class MainWindowViewModel : BindableBase
{
public ObservableCollection<string> Data { get; private set; }
private string selectedData;
public string SelectedData
{
get { return selectedData; }
set
{
SetProperty(ref selectedData, value);
}
}
public DelegateCommand<SelectionChangedEventArgs> TryChangeSelectedData { get; private set; }
public MainWindowViewModel()
{
Data = new ObservableCollection<string>() { "Foo", "Bar", "Dick", "Head" };
SelectedData = Data.First();
TryChangeSelectedData = new DelegateCommand<SelectionChangedEventArgs>(args =>
{
var newValue = args.AddedItems.Cast<string>().FirstOrDefault();
if (newValue == "Dick")
this.OnPropertyChanged(() => this.SelectedData);
else
SelectedData = newValue;
});
}
}

Data Validation in Silverlight 4

I have control in SL4. I want data validation on button click. Big problem is normally SL4 give validation using binding property.
like example given shown in this example
http://weblogs.asp.net/dwahlin/archive/2010/08/15/validating-data-in-silverlight-4-applications-idataerrorinfo.aspx
<TextBox Text="{Binding Name,Mode=TwoWay,ValidatesOnDataErrors=true}"
Height="23"
Width="120"
HorizontalAlignment="Left"
VerticalAlignment="Top" />
BUT I WANT TO SHOW ERROR MESSAGE LIKE THIS ....
using my own code like on button click i check
(textbox1.text == null ) then set this style of error to textbox1
One way of deferring validation is to set the property UpdateSourceTrigger=Explicit in the bindings. If you do this, the bindings won't update the source objects, and hence won't cause validation errors, until you explicitly tell the bindings to do so. When your button is clicked, you force an update on the bindings, using a line such as the following for each control:
someTextBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
You then have your property setters throwing exceptions for invalid data.
This approach can be a bit of a pain if there are quite a lot of controls to force binding updates on.
Also, forcing an update on the bindings has to be done in the code-behind of a control. If you're using a Command with the button as well then you might run in to an issue. Buttons can have both a Command and a Click event handler, and both will execute when the button is clicked on, but I don't know the order in which this happens or even if an order can be guaranteed. A quick experiment suggested that the event handler was executed before the command, but I don't know whether this is undefined behaviour. There is therefore the chance that the command will be fired before the bindings have been updated.
An approach to programmaticaly creating validation tooltips is to bind another property of the textbox and then deliberately cause an error with this binding.
'sapient' posted a complete solution, including code on the Silverlight forums (search for the post dated 07-08-2009 4:56 PM). In short, he/she creates a helper object with a property whose getter throws an exception, binds the Tag property of the textbox to this helper object and then forces an update on the binding.
'sapient's code was written before Silverlight 4 was released. We'll 'upgrade' his/her code to Silverlight 4. The class ControlValidationHelper becomes the following:
public class ControlValidationHelper : IDataErrorInfo
{
public string Message { get; set; }
public object ValidationError { get; set; }
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get { return Message; }
}
}
It's easy enough to knock up a quick demo application to try this out. I created the following three controls:
<TextBox x:Name="tbx" Text="{Binding Path=Text, ValidatesOnDataErrors=True, NotifyOnValidationError=True, Mode=TwoWay}" />
<Button Click="ForceError_Click">Force error</Button>
<Button Click="ClearError_Click">Clear error</Button>
The Text property and the event handlers for the two buttons live in the code-behind and are as follows:
public string Text { get; set; }
private void ForceError_Click(object sender, RoutedEventArgs e)
{
var helper = new ControlValidationHelper() { Message = "oh no!" };
tbx.SetBinding(Control.TagProperty, new Binding("ValidationError")
{
Mode = BindingMode.TwoWay,
NotifyOnValidationError = true,
ValidatesOnDataErrors = true,
UpdateSourceTrigger = UpdateSourceTrigger.Explicit,
Source = helper
});
tbx.GetBindingExpression(Control.TagProperty).UpdateSource();
}
private void ClearError_Click(object sender, RoutedEventArgs e)
{
BindingExpression b = tbx.GetBindingExpression(Control.TagProperty);
if (b != null)
{
((ControlValidationHelper)b.DataItem).Message = null;
b.UpdateSource();
}
}
The 'Force error' button should make a validation error appear on the textbox, and the 'Clear error' button should make it go away.
One potential downside of this approach occurs if you are using a ValidationSummary. The ValidationSummary will list all validation errors against ValidationError instead of against the name of each property.
Although my answer wasn't regarded as preferable, I'm still sure that the MVVM pattern is the best choice to perform validation.
In my code you should use the model validator from this post about validation and any mvvm framework, for example MVVM Light.
It is much easier to add validation rules using the view model and model validator classes:
public class PersonViewModel : ViewModelBase, INotifyDataErrorInfo
{
private ModelValidator _validator = new ModelValidator();
public PersonViewModel()
{
this._validator.AddValidationFor(() => this.Age)
.Must(() => this.Age > 0)
.Show("Age must be greater than zero");
}
}
And you can validate the model if and only if a user explicitly clicks a button:
#region INotifyDataErrorInfo
public IEnumerable GetErrors(string propertyName)
{
return this._validator.GetErrors(propertyName);
}
public bool HasErrors
{
get { return this._validator.ErrorMessages.Count > 0; }
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged = delegate { };
protected void OnErrorsChanged(string propertyName)
{
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
this.RaisePropertyChanged("HasErrors");
}
#endregion
public bool Validate()
{
var result = this._validator.ValidateAll();
this._validator.PropertyNames.ForEach(OnErrorsChanged);
return result;
}
As everyone can see, there is nothing difficult here, just 20-30 lines of code.
Moreover, the MVVM approach is much more flexible and you can reuse some common validation scenaries among several view models.

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.

TextBox binding in WPF

I have a textbox in XAML file, On Changing the text in the textbox the prop setter is not getting called. I am able to get the value in ProjectOfficer textbox and not able to update it. I am using MVVM pattern
Below is my code XAML
TextBox Text="{Binding Path=Officer,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
x:Name="ProjectOfficer"/>
ViewModel.cs
public Staff Officer
{
get
{
return __con.PrimaryOfficer ;
}
set
{
_con.PrimaryOfficer = value;
_con.PrimaryOfficer.Update(true);
}
}
Staff.cs
public class Staff : EntityBase
{
public Staff();
public string Address { get; }
public string Code { get; set; }
public override void Update();
}
Thanks
You're binding a property of type string on the TextBox to a property of type Officer on your ViewModel. I expect the setter isn't being called because WPF can't do the conversion.
If you check the output window in visual studio, you'll probably see a binding error for this property.
Try something like:
TextBox text ="{Binding Path=Address,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
x:Name="ProjectOfficer"/>
Make sure the holder of the TextBox is linked to a Staff object. The textbox cannot bind directly to an object without telling the property to display (like Address in my example above).

Resources