Binding and appendText result to a TextBox - wpf

Total beginner in WPF so bear with me.
I building a proof of concept application before I go ahead with it. I have decided to try building it using WPF first.
This is the scenario:
I have:
a View=CustomerView (userControl with a button "Buy Products")
a txtbox (append all the actions that occurs)
ViewModel=CustomerViewModel (talks to the service etc.. and gets results
Model =Customer (simple class with properties with InotifyPropertyChanged implemented
Given that I have a collection of products and for each product that I buy I need to APPEND TEXT TO the txtBox in the userControl printing all the actions that occurs, the textBox should look like
Start buying .....
Product 1 Sold
Product 2 Sold
Fineshed....
My problem is that despite I notify successfully after each item is sold I cannot seem to see how I can bind or make "Product 1 Sold,Product 2 appear in the textbox.
In windows forms I would have a userControl with a property called ProductStatus and whenever notified I would appendText to the textBox.
"I put beginInvoke as I was getting threadCross operation" when coming from a service. That is another problem that I will have to investigate
private ProductStatus _productStatus;
public ProductStatus Status
{
get { return _productStatus; }
set
{
_printStatus = value;
BeginInvoke((MethodInvoker)(() => txtProductResult.AppendText(string.Format("Product Sold {0}", Environment.NewLine))));
}
}
How do I append text to myTextBox for each item I sell?
=================Added current code===============================
void LogChanged(object sender, NotifyCollectionChangedEventArgs e)
{
foreach(object item in e.NewItems)
{
txtProduct.AppendText(item.ToString());
}
}
<TextBox Margin="12,41,12,59" Name="txtPrintResult" />
<TextBox Text="{Binding TicketPrintLog, Mode=OneWay}" Margin="12,41,12,12" />
ProductModel
=============
public string ProductLog{ get; set; }
ProductViewModel
==================
public string ProductLog
{
get { return _ProductModel.ProductLog; }
}
internal void AppendToProductLogText(string text)
{
_ProductModel.ProductLog += text + Environment.NewLine;
OnPropertyChanged("ProductLog");
}
void ProductSoldNotificationReceived(object sender, notificationEventArgs e)
{
//THIS FIRES WHENEVER I PRODUCT IS SOLD.
AppendToProductLogText(e.Message);
}
ProductView (userControl) XAML
================================
<TextBox Margin="12,41,12,59" Name="txtResult" Text="{Binding ProductLog, Mode=OneWay}" />
IN CODE BEHIND I DO
public void Action1()
{
txtResult.AppendText(_productViewModel.ProductLog);
}
public void SellProductAction()
{
txtResult.AppendText(_productViewModel.ProductLog);
}
The problem i have is that i still have todo this
txtResult.AppendText(_productViewModel.ProductLog);
which defeats the point on databinding also when executing SellproductAction I only get notified about the last item, it doesn't append Product1 sold, Product 2 sold etc…
Any ideas?

First off, you can still do this the Windows Forms way, by appending the new text to the existing text:
txtProductResult = txtProductResult + Environment.NewLine + "Product sold";
(If you do this from a background thread you will need to use txtProductResult.Dispatcher.BeginInvoke instead of txtProductResult.BeginInvoke.)
However, the idiomatic way to do this in WPF is with data binding. So you would create a ProductSalesLog property on your view model, and bind the TextBox to that property. Then, whenever you update the ProductSalesLog property in the view model, the TextBox will update automatically. Note that your view model must implement INotifyPropertyChanged and must raise the PropertyChanged event for the ProductSalesLog property. Thus:
public class CustomerViewModel : INotifyPropertyChanged
{
private string _productSalesLog = String.Empty;
public string ProductSalesLog
{
get { return _productSalesLog; }
}
internal void AppendSalesLogText(string text)
{
_productSalesLog += text + Environment.NewLine;
OnPropertyChanged("ProductSalesLog");
}
}
And in your view:
<TextBox Text="{Binding ProductSalesLog, Mode=OneWay}" />

As WPF is a data driven framework, you should have your view databinding to properties on your view model and model. Any changes in the viewmodel or model can then be passed to the view through the INotifyPropertyChanged interface (by raising a propertyChanged event).
This means for most purposes you should not need to access properties on your user control. This should be possible by having a product status on your viewmodel and your text box should bind to this property. You will of course have to make sure when the setter for product status is called that the PropertyChanged event is raised to notify the view to update

Related

Updating a dependency property based on changes in the view model

I'm having some problems with data binding in WPF. Here's the scenario: I have made a user control which simulates a Dial Pad (i.e., an array of 12 buttons with the digits from '0' to '9' plus the '#' and 'Clear' keys). The control lives inside a class library and it's been implemented following the MVVM pattern, mainly because I need the components in the class library to be easily unit tested.
The view model for the control is quite simple, it basically updates a public "DialedNumber" string (which is internally connected to the model) every time the user presses a dial pad key button. The binding is working correctly and, by using the debugger, I can confirm that the "DialedNumber" variable inside the viewmodel is getting updated as I press button in the dial pad.
This DialPad control is used by a separate XAML file (Panel.xaml), which laids out several controls that belong to my custom class library.
Now, I'd like to add a TextBlock inside my Panel file in order to display the "DialedNumber" string held inside the DialPad. This is the code snippet in Panel.xaml:
<PanelControls:DialPad x:Name="MyDialPad" DialedNumber="55325"/>
<TextBlock Text="{Binding ElementName=MyDialPad, Path=DialedNumber}" />
The result I'm getting is that the textblock displays the correct number on start (i.e., "55325"), but its content doesn't get updated as I press the dial pad keys (even though the DialPad's viewmodel gets updated as I press new keys, as I've checked with the debugger).
Here's the code behind for the DialPad view:
public partial class DialPad : UserControl
{
public DialPad()
{
InitializeComponent();
DataContext = new DialPadViewModel();
}
public void DialedNumberChanged(object sender, EventArgs e)
{
return;
}
public DialPadViewModel DialPadViewModel
{
get { return DataContext as DialPadViewModel; }
}
public string DialedNumber
{
get
{
var dialPadViewModel = Resources["DialPadVM"] as DialPadViewModel;
return (dialPadViewModel != null) ? dialPadViewModel.DialedNumber : "";
}
set
{
var dialPadViewModel = Resources["DialPadVM"] as DialPadViewModel;
if (dialPadViewModel != null)
{
dialPadViewModel.DialedNumber = value;
}
}
}
}
Here's the DialPad view model:
public class DialPadViewModel : ObservableObject
{
public DialPadViewModel()
{
_dialPadModel = new DialPadModel();
}
#region Fields
private readonly DialPadModel _dialPadModel;
private ICommand _dialPadKeyPressed;
#endregion
#region Public Properties/Command
public DialPadModel DialPadModel
{
get { return _dialPadModel; }
}
public ICommand DialPadKeyPressedCommand
{
get
{
if (_dialPadKeyPressed == null)
{
_dialPadKeyPressed = new RelayCommand(DialPadKeyPressedCmd);
}
return _dialPadKeyPressed;
}
}
public string DialedNumber
{
get { return _dialPadModel.DialedNumber; }
set
{
_dialPadModel.DialedNumber = value;
RaisePropertyChanged("DialedNumber");
}
}
#endregion
#region Private Helpers
private void DialPadKeyPressedCmd(object parameter)
{
string keyPressedString = parameter.ToString();
if (keyPressedString.Length > 0)
{
if (char.IsDigit(keyPressedString[0]))
{
DialedNumber += keyPressedString[0].ToString(CultureInfo.InvariantCulture);
}
else if (keyPressedString == "C" || keyPressedString == "Clr" || keyPressedString == "Clear")
{
DialedNumber = "";
}
}
}
#endregion
}
Let me restate my problem: the textblock in Panel.xaml displays the correct number (55325) on start, but its value never gets updated as I press the DialPadButtons. I've placed a breakpoint inside DialPadKeyPressedCmd and I can confirm that the method gets executed everytime I press a key in the dial pad.
DependencyProperties are meant to point to some other property to get their value. So you can either point it to your DialPadViewModel.DialedNumber, or you can point it to some other string when the UserControl is used (either a binding or a hardcoded value like "551"), but you can't do both.
In your case, when someone binds to the DialedNumber dependency property, they are replacing the current value (the binding to DialPadViewModel.DialedNumber) with a new value.
Depending on how your code looks and what you want to do, there are a few ways around it.
First, you could insist that people who want to use your control also use your ViewModel, and don't make DialedNumber a public dependency property.
So instead of being allowed to create a custom class with a property of SomeOtherDialedNumber and binding
<DialPad DialedNumber="{Binding SomeOtherDialedNumber}">
they are forced to use the DialPadViewModel in their code anytime they want to use the DialPad control. For this to work, you would need to remove the this.DataContext = new DialPadViewModel in your code-behind the UserControl since the user will be providing the DialPadViewModel to your UserControl, and you can use an implicit DataTemplate to tell WPF to always draw DialPadViewModel with your DialPad UserControl.
<DataTemplate DataType="{x:Type DialPadViewModel}">
<local:DialPad />
</DataTemplate>
The other alternative I can think of is to synchronize your DependencyProperty with your ViewModel property with some PropertyChange notifications.
You would need to update DialPadViewModel.DialedNumber anytime the DialedNumber dependency property changes (You may need to use DependencyPropertyDescriptor.AddValueChanged for property change notification), and you would also have to write something to update the source of the DialedNumber dependency property anytime DialPadViewModel.DialedNumber changes.
Personally, if my UserControl has a ViewModel then I use the first option. If not, I get rid of the ViewModel entirely and build the logic for my UserControl in the code-behind, without a ViewModel.
The reason for this is that WPF works with two layers: a UI layer and a data layer. The DataContext is the data layer, and a ViewModel is typically part of the data layer. By setting the data layer (DataContext) explicitly in the UserControl's constructor, you are combining your data layer with your UI layer, which goes against one of the biggest reasons for using MVVM: separation of concerns. A UserControl should really just be a pretty shell only, and you should be able to place it on top of any data layer you want.
If you place your DialPad in your View, you can create a DialPadViewModel-Property (public+global) in your ViewViewModel:
public DialPadViewModel DialPadViewModel = new DialPadViewModel();
Now set the DataContext-Binding of your View to the ViewViewModel and bind the DialPads DataContext also to it, like
<local:DialPad DataContext="{Binding}"/>
Now you can bind to the properties in your DialPadViewModel:
<TextBox Text="{Binding Path=DialPadViewModel.DialedNumber}"/>
Thats how you can Access your DialPadViewModel from your View and your DialPad.
EDIT:
Now try changing your DialedNumber Property in your DialPad.xaml.cs like this:
public string DialedNumber
{
get
{
return DialPadViewModel.DialedNumber;
}
set
{
DialPadViewModel.DialedNumber = value;
}
}
EDIT 2: I found the Problem:
In your DialPad.xaml all your Commands were bound to the DialPadViewModel from the resources, while the TextBloc was bound to the DialPads DataContext, which is another instance of the DialPadViewModel.
So everytime you hit a DialPad-Button you changed the value of the DialedNumber from the resources' DPVM-instance not the DialedNumber from the DataContext's DPVM-instance.
It sounds like you can add a TextBox to your view and bind it's Text property to your view-model's DialedNumber property.
<TextBox Text="{Binding Path=DialedNumber}"></TextBox>
Your view-model property can look something like this:
private string _dialedNumber;
[DefaultValue("551")]
public string DialedNumber
{
get { return _dialedNumber; }
set
{
if (value == _dialedNumber)
return;
_dialedNumber= value;
_yourModel.DialedNumber= _dialedNumber;
this.RaisePropertyChanged("DialedNumber");
}
}
Let me know if I misunderstood your question.

Bubbling up WPF Datagrid Item events

I've a datagrid with an ItemsSource of ObservableCollection (OC) of objects. When an item's property changes, I want to work on the OC itself .
E.g. I've an item which is approved for uploading to our database. However, I need to loop through the OC to check if other items exist in the collection which already fit the set criteria, so that I may actually not have to upload the selected item.
On the datagrid, when I tick the checkbox of an item, it will change the boolean value (e.g. "IsToUpload") of the item, and an event should trigger on the property change.
I'm assuming I will then need to 'bubble up' my event notifications to the datagrid/mainwindow class, where I can then work on the OC. How may I do this, and if this is not the correct way, what should I be doing?
I've followed Aran Mulholland's class structure to colour my rows dynamically: Coloring WPF DataGridRows one by one
So my class structure is roughly as follows:
MainWindow -> DataGrid
-> ObservableCollection<ItemObjectViewModel:NotificationObject>
ItemObject : INotifyPropertyChanged //this class is where I
//store my item variables. It is referenced through properties
//in the ItemObjectViewModel.
Event bubling \ routing etc works for dependency objects in a visual \ logical tree. Your NotificationObject is not a dependency object and neither is it hosted in the visual tree.... What we have in visual tree are the checkboxes (that are bound to your NotificationObject).
Non MVVM
In you DataGrid you would have to Tag your Checkboxes with some identification and then use ButtonBase.Click="" event at datagrid level which will be handled for any click event bubbled for any button based eleemnt (such as buttons, menuitems, togglebuttons, checkboxes, radioboxes, comboboxes) that gets clicked in the entire visual tree of the datagrid.
In the handler verify if the e.OriginalSource is a checkbox and that its Tag is same as the identification value we have set in the XAML of the datagrid. That way we know that the CheckBox is clicked.
E.g.
<DataGrid AutogenerateColumns="False"
ItemsSource="{Binding NotificationObjectCollection}"
ButtonBase.Clicked="OnNotificationCheckBoxClicked">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding IsClicked}"
Header="Click?">
<DataGridCheckBoxColumn.ElementStyle>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Tag" Value="IsClickCheckBox" />
</Style>
</DataGridCheckBoxColumn.ElementStyle>
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>
private void OnNotificationCheckBoxClicked
(object sender, RoutedEventArgs e)
{
if (e.OriginalSource is CheckBox)
{
if (((CheckBox)e.OriginalSource).Tag == "IsClickCheckBox")
{
var notificationObject
= ((CheckBox)e.OriginalSource).DataContext
as NotificationObject;
if (notificationObject.IsClicked) { }
else { }
}
}
}
MVVM
The only way MVVM can notify the ancestor object in the visual is by using Command execution as the underlying NotificationObject gets checked (setter is called) we execute the command supplied to the NotificationObject.
Use the weak reference based RelayCommand or DelegateCommand (as available on the internet) for this purpose.
Add a new NotificationObject constructor
private ICommand _isClickedCommand;
public NotificationObject(ICommand isClickedCommand)
{
_isClickedCommand = isClickedCommand;
}
private bool _isClicked;
public bool IsClicked
{
get
{
return _isClicked;
}
set
{
if (_isClicked != value)
{
_isClicked = value;
OnPropertyChanged("IsClicked");
isClickedCommand.Execute(this);
}
}
}
Using the notification object
public class ItemObjectViewModel
{
private DelegateCommand<NotificationObject>
_notificationObjectClickedCommand
= new DelegateCommand<NotificationObject>(
OnNotificationObjectCommandExecute);
....
private void PopulateCollection()
{
NotificationObjectCollection
= new ObservableCollection<NotificationObject>();
NotificationObjectCollection.Add(
new NotificationObject(_notificationObjectClickedCommand));
}
private void OnNotificationObjectCommandExecute(
NotificationObject notificationObject)
{
if (notificationObject.IsClicked) { }
else { }
}
}
You can also achieve the ICommand based behavior in non MVVM scenario by using 'RoutedCommand'
Let me know if this helps...

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.

Sending data from view to viewmodel with command binding

Question:
How do send data to a view model when using command binding? So that, for example, when i click a button, it sends the "currently selected index" of a list so that it can perform an operation on that item of the list
Further Information:
I'm working on a program where i have a list of shipments, and each shipment has a list of pallets. I want to make a button that will allow me to add a new pallet to the currently selected shipment. >Edit> And to through another wrench into the works, each pallet has a list of products. so not only do i need to know what shipment i'm on, but i also need to know what pallet of what shipment I'm on.
When I do a command binding, I have no idea how to send the data to the ViewModel. I would like to keep this pure MVVM so i don't want to have the ViewModel checking the view for anything.
~N
Edits:
11/04/09 - I removed the section of the question about the instantiation of the ViewModel. I'll ask that again in a different question as this one is well on track for solving the other question. And I made a few other edits to the question to clearify in the direction i want. as well as changed some grammatical bits so that it wasn't talking about two questions when there is only one.
I usually expose a CollectionView from the view model and set the IsSynchronizedWithCurrentItem property on the ItemsControl displaying the list in the view. Then when the command is executed, I can inspect the CollectionView.CurrrentItem propety to see what is currently selected.
EDIT: This answer addresses the first question in your, um, question. Rather than your view sending the currently selected item to the ViewModel, the ViewModel keeps track of the currently selected item. So using this technique you don't need to work out how to send that information.
Something like this in your view model:
class ApplicationViewModel
{
// Exposes a list of ShipmentViewModels.
public CollectionView Shipments { get; private set; }
// A DelegateCommand or similar, that when executed calls AddPallet().
public ICommand AddPalletCommand { get; private set; }
void AddPallet()
{
ShipmentViewModel shipment = (ShipmentViewModel)Shipments.CurrentItem;
shipment.Pallets.Add(new PalletViewModel(...));
}
}
And then this in your xaml:
<ListBox ItemsSource="{Binding Shipments}" IsSynchronizedWithCurrentItem="True"/>
<Button Command="{Binding AddPalletCommand}>Add Pallet</Button>
This way you can also track the selection of the Shipments collection from your ViewModel and update the command's CanExecute state.
Does that help any?
For keeping track of the currently selected item I do something similar to Groky, maybe this example make a little more sense.
In your ViewModel that contains the collection that your list is bound to (I'm using a ListBox in this example) expose a property that relates to the selected item.
// Assuming your using the MVVM template from Microsoft
public class PalletListViewModel : ViewModelBase
{
// The collection our list is bound to
private ObservableCollection<Pallet> _palletList;
// The current selected item
private Pallet _selectedPallet;
// Our command bound to the button
private DelegateCommand _processCommand;
public ObservableCollection<Pallet> PalletList
{
get { return _palletList; }
}
public Pallet SelectedPallet
{
get { return _selectedPallet; }
set
{
if(value == _selectedPallet)
return;
_selectedPallet = value;
// INotifyPropertyChanged Method for updating the binding
OnPropertyChanged("SelectedPallet");
}
}
public ICommand ProcessCommand
{
get
{
if(_processCommand == null)
_processCommand = new DelegateCommand(Process);
return _processCommand;
}
}
private void Process()
{
// Process the SelectedPallet
}
}
<Window ...>
<Grid x:Name="LayoutRoot">
<Button Content="Process Pallet" Command="{Binding ProcessCommand}" />
<ListBox ItemsSource="{Binding PalletList}" SelectedItem="{Binding SelectedPallet}">
...
</ListBox>
</Grid>
</Window>
Hopefully this is what your looking for.

Resources