Is there any way to manually/dynamically add errors to the Validation.Errors collection?
from http://www.wpftutorial.net/ValidationErrorByCode.html
ValidationError validationError = new ValidationError(regexValidationRule,
textBox.GetBindingExpression(TextBox.TextProperty));
validationError.ErrorContent = "This is not a valid e-mail address";
Validation.MarkInvalid(
textBox.GetBindingExpression(TextBox.TextProperty),
validationError);
jrwren's answer guided me in the right direction, but wasn't very clear as to what regexValidationRule was nor how to clear the validation error. Here's the end result I came up with.
I chose to use Tag since I was using this manual validation in a situation where I wasn't actually using a viewmodel or bindings. This gave something I could bind to without worrying about affecting the view.
Adding a binding in code behind:
private void AddValidationAbility(FrameworkElement uiElement)
{
var binding = new Binding("TagProperty");
binding.Source = this;
uiElement.SetBinding(FrameworkElement.TagProperty, binding);
}
and trigging a validation error on it without using IDataError:
using System.Windows;
using System.Windows.Controls;
private void UpdateValidation(FrameworkElement control, string error)
{
var bindingExpression = control.GetBindingExpression(FrameworkElement.TagProperty);
if (error == null)
{
Validation.ClearInvalid(bindingExpression);
}
else
{
var validationError = new ValidationError(new DataErrorValidationRule(), bindingExpression);
validationError.ErrorContent = error;
Validation.MarkInvalid(bindingExpression, validationError);
}
}
Related
I've got a custom TextBox to handle measurements (feet, inches, mm) that has a couple Dependency Properties that determine what the formatting of the box should be when the box loses focus. I got all the conversions happening in the OnLostFocus function because conversions mid-input would not work. In OnLostFocus, I convert the value to a number depending on some other DP properties and set a Measurement property. All this works great.
My question is, how do I handle validation? When someone inputs an invalid value, I want the textbox to go red, much like you could do with a Binding that has ValidatesOnExceptions=true. I tried something like below in the catch block of OnLostfocus
protected override void OnLostFocus(RoutedEventArgs e)
{
try
{
if (string.IsNullOrWhiteSpace(Text))
{
Text = "0";
}
if (IsMetric)
{
var measurement = convertStuffHere();
Text = measurement.Text;
Measurement = measurement.Value;
}
else
{
var measurement = convertOtherStuffHere();
// convert and formatting stuff here...
Text = measurement.Text;
Measurement = measurement.Value;
}
var binding = this.GetBindingExpression(TextBox.TextProperty);
if (binding != null)
Validation.ClearInvalid(this.GetBindingExpression(TextBox.TextProperty));
}
catch (Exception)
{
var rule = new DataErrorValidationRule();
var binding = this.GetBindingExpression(TextBox.TextProperty);
if (binding != null)
{
ValidationError validationError = new ValidationError(rule, this.GetBindingExpression(TextBox.TextProperty));
validationError.ErrorContent = "This is not a valid input";
Validation.MarkInvalid(this.GetBindingExpression(TextBox.TextProperty), validationError);
}
}
finally
{
base.OnLostFocus(e);
}
}
This almost works, but the validation error shows up late. I have to lose focus, get focus, and lose focus again before a red box shows around the textbox.
I'm using it like <myns:MeasurementTextBox Text="{Binding MeasurementText1, ValidatesOnExceptions=True}" Margin="10" IsMetric="True"></myns:MeasurementTextBox>
You can use TextBoxBase.TextChanged event instead of UIElement.LostFocus.
I'm using the SimpleMVVM Toolkit.
I have a view (manage_view) with multiple buttons that will navigate (set a frame's source) to new views (manage_import_view, manage_scanners_view, etc). Each view has it's own VM.
For each of the views I set the datacontext to the VM by using a locator. The locator injects a ServiceAgent into the VM.
The problem is that when I navigate to the other views, the state of the previous view is lost. For instance I'd do an import on the Manage_Import_View and bind to properties on the VM. When I navigate to Manage_Scanners_View and then back to the Manage_Import_View, the properties that I bound to is lost.
I understand what is happening but Im not sure how to resolve it. How do I keep the state of the views when switching between them?
Looking forward to your thoughts on this.
(I've searched Switching between views according to state but it's not exactly what I need.)
Edit
My locator
public ImportViewModel ImportViewModel
{
get
{
IIntegrationServiceAgent sa = new IntegrationServiceAgent();
return new ImportViewModel(sa);
}
}
In my view's XAML I set the datacontext
DataContext="{Binding Source={StaticResource Locator}, Path=ImportViewModel}"
Navigation is like so
private void Navigate(string pageName)
{
Uri pageUri = new Uri("/Views/" + pageName + ".xaml", UriKind.Relative);
this.SelectedPage = pageUri;
this.SelectedPageName = pageName;
}
I have a completion callback once import is complete. This sets the props that my view binds to - these are the ones that are reset after switching views.
private void ImportCompleted(IntegrationResult intresult, Exception error)
{
if (error == null)
{
_errorCount = intresult.Errors.Count;
ErrorList = intresult.Errors;
ResultMessage = intresult.Message;
ErrorMessage = (errorList.Count == 1 ? "1 error" : errorList.Count.ToString() + " errors");
Notify(ImportCompleteNotice, null); // Tell the view we're done
ShowErrorDialog(importType);
}
else
NotifyError(error.Message, error);
IsImportBusy = false;
}
This seems clunky to me. I am not entirely sure why this is happening but I can guess... You are loading the SelectedPage from Uris each time they are requested, this will set and parse the XAML each time they are loaded which will effect your bindings. Here's what I would do:
First on the application start-up, load all of the Views into a view list
private Dictionary<string, Uri> viewUriDict;
private List<string> viewNameList = new List<string>()
{
"ViewA",
"ViewB"
};
// The main View Model constructor.
public MainViewModel()
{
viewUriDict = new Dictionary<string, Uri>();
foreach (string s in viewNameList)
viewUriDict.Add(s, new Uri("/Views/" + s + ".xaml", UriKind.Relative);
this.SelectedPageName = viewNameList[0];
}
private string selectedPageName;
public string SelectedPageName
{
get { return this.selectedPageName; }
set
{
if (this.selectedPageName == value)
return;
this.selectedPageName = value;
this.SelectedPage = this.viewUriDict[this.selectedPageName];
OnPropertyChanged("SelectedPageName"); // For INotifyPropertyChanged.
}
}
private Uri selectedPage;
private Uri selectedPageName
{
get { return this.selectedPage; }
set
{
if (this.selectedPage == value)
return;
this.selectedPage = value;
OnPropertyChanged("SelectedPage"); // For INotifyPropertyChanged.
}
}
So now the Uri list is cached in your main window/app. Navigation would then become
private void Navigate(string pageName)
{
this.SelectedPageName = pageName;
}
or by merely setting this.SelectedPageName = "PageX".
The second thing I would do is lazily instantiate the InportViewModel service agent. I am not sure how this is called, but I would not re-create the service agent on every call...
private IIntegrationServiceAgent sa;
public ImportViewModel ImportViewModel
{
get
{
if (sa == null)
sa = new IntegrationServiceAgent();
return new ImportViewModel(sa);
}
}
This may resolve your issue, or might not. Either way I hope it is of some value. If I were you, I would look at using Prism to do this type of thing, although it might be overkill if this is a small project.
I hope this helps.
For anyone who've experience the same puzzle, I've found the answer here and here .
tonysneed's reply on the second link explains it.
I have a simple set up like this.
ContentControl in XAML
Add AdornerY to the adorner layer in the code-behind to the ContentControl
AdornerY's template is set to my custom ControlTemplate AdornerTemplateY
I'm quite sure 1 and 2 shall not give any problems so I'm posting the code here for the 3.
Visual Studio Express is not very friendly with errors and it merely says:
A first chance exception of type 'System.ArgumentException' occurred
in PresentationFramework.dll
What's wrong with the code? Thanks a bunch.
class AdornerTemplateY : ControlTemplate
{
FrameworkElementFactory Chrome;
public AdornerTemplateY(ContentControl designerItem)
: base(typeof(AdornerY))
{
Chrome = new FrameworkElementFactory(typeof(Rectangle));
Chrome.SetValue(Rectangle.NameProperty, "INTERNAL_CHROME");
Chrome.SetValue(Rectangle.FillProperty, Brushes.PowderBlue);
Chrome.SetValue(Rectangle.StrokeProperty, Brushes.Black);
Chrome.SetValue(Rectangle.DataContextProperty, designerItem);
Chrome.SetValue(Rectangle.IsHitTestVisibleProperty, true);
this.VisualTree = this.Chrome;
this.Triggers.Add(CreateTrigger());
}
private Trigger CreateTrigger()
{
Trigger TriggerFocus = new Trigger
{
Property = AdornerY.IsMouseOverProperty,
Value = true,
Setters =
{
new Setter
{
Property = AdornerY.VisibilityProperty,
Value = Visibility.Collapsed
},
}
};
return TriggerFocus;
}
}
I have a tabControl on a WPF form.
In one of the Tab Items I have a User Control that contains a DataGrid which has CanUserAddRows="True". The user can type data in the column and when they press [enter], a new row is created.
The problem is when I type data into the new row and then change tabs I get this exception: "WPF datagrid 'newitemplaceholderposition' is not allowed during a transaction begun by 'Addnew' "
Any suggestions how to avoid it?
I have tried to put dg.CommitEdit() on usercontrol.Unloaded(). I don't get the exception, but I also don't get the new row.
I ran into the same problem...here are some snippets describing how I solved it. Note that in my case I wanted to reject the changes to avoid the error. If you want to commit the changes, this may lead you in the right direction.
1a) Use the InitializingNewItem event on the datagrid to capture the adding row.
private void mydatagrid_InitializingNewItem(object sender, InitializingNewItemEventArgs e)
{
_viewmodel.NewRowDefaults((DataRowView)e.NewItem);
}
1b) In this case, I'm calling a method in my view model to populate row defaults and save a reference to the row.
private DataRowView _drvAddingRow { get; set; }
public void NewRowDefaults(DataRowView drv)
{
_drvAddingRow = drv;
...
}
2) Then when you need to reject the change (before notifying property changes or whatever your case is), use the CancelEdit method on the captured datarowview.
_drvAddingRow.CancelEdit();
I just ran into the same problem. Found two possible workarounds:
1/ Trigger the CommitEdit event of the DataGrid, then call CommitEdit. I'm not sure why this last step is needed, you may not have to call CommitEdit in your case.
DataGrid.CommitEditCommand.Execute(this.DataGridWorkItems, this.DataGridWorkItems);
yourDataGrid.CommitEdit(DataGridEditingUnit.Row, false);
2/ Simulate a stroke on the 'Return' key of the keyboard:
var keyEventArgs = new KeyEventArgs(InputManager.Current.PrimaryKeyboardDevice,PresentationSource.FromDependencyObject(yourDataGrid), System.Environment.ProcessorCount, Key.Return);
keyEventArgs.RoutedEvent = UIElement.KeyDownEvent;
yourDataGrid.RaiseEvent(keyEventArgs);
I settled for the last solution, since I had a few fishy side effects with the first one.
Unfortunately, the other answers only solve the problem in some cases. For instance, if one of the cells has a validation error when switching tabs, the other solutions fail.
The problem is that when IsEnabled is changed, CanUserAddRows gets changed and that triggers NewItemPlaceholderPosition to be reset. To work around this bug, I inherited the DataGrid class and added some logic to the CoerceValueCallback of the CanUserAddRowsProperty.
namespace CustomControls
{
using System;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using Utilities;
public class FixedDataGrid : DataGrid
{
static FixedDataGrid()
{
var originalPropertyChangedCallback = CanUserAddRowsProperty.GetMetadata(typeof(DataGrid)).PropertyChangedCallback;
var originalCoerceValueCallback = CanUserAddRowsProperty.GetMetadata(typeof(DataGrid)).CoerceValueCallback;
CanUserAddRowsProperty.OverrideMetadata(typeof(FixedDataGrid), new FrameworkPropertyMetadata(true,
originalPropertyChangedCallback,
(d, e) =>
{
var ths = ((FixedDataGrid) d);
// Fixes System.InvalidOperationException: 'NewItemPlaceholderPosition' is not allowed during a transaction begun by 'AddNew'.
if (ths.IsEnabled) return originalCoerceValueCallback(d, e);
if (!((IEditableCollectionViewAddNewItem) ths.Items).CanAddNewItem &&
!((IEditableCollectionViewAddNewItem) ths.Items).CanCancelEdit)
return originalCoerceValueCallback(d, e);
ths.CancelEdit();
ReflectionUtils.InvokeMethod(ths, "CancelRowItem");
ReflectionUtils.InvokeMethod(ths, "UpdateNewItemPlaceholder", false);
ReflectionUtils.SetProperty(ths, "HasCellValidationError", false);
CommandManager.InvalidateRequerySuggested();
return originalCoerceValueCallback(d, e);
}));
}
}
}
namespace Utilities
{
using System;
using System.Reflection;
public class ReflectionUtils
{
public static void InvokeMethod(object obj, string name, params object[] args)
{
InvokeMethod(obj, obj.GetType(), name, args);
}
public static void InvokeMethod(object obj, Type type, string name, params object[] args)
{
var method = type.GetMethod(name, BindingFlags.NonPublic | BindingFlags.Instance);
if (method == null)
{
if (type.BaseType == null)
throw new MissingMethodException($"Couldn't find method {name} in {type}");
InvokeMethod(obj, type.BaseType, name, args);
return;
}
method.Invoke(obj, args);
}
public static T InvokeMethod<T>(object obj, string name, params object[] args)
{
return InvokeMethod<T>(obj, obj.GetType(), name, args);
}
public static T InvokeMethod<T>(object obj, Type type, string name, params object[] args)
{
var method = type.GetMethod(name, BindingFlags.NonPublic | BindingFlags.Instance);
if (method == null)
{
if (type.BaseType == null)
throw new MissingMethodException($"Couldn't find method {name} in {type}");
return InvokeMethod<T>(obj, type.BaseType, name, args);
}
return (T) method.Invoke(obj, args);
}
public static T GetProperty<T>(object obj, string name)
{
return GetProperty<T>(obj, obj.GetType(), name);
}
public static T GetProperty<T>(object obj, Type type, string name)
{
var prop = type
.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Instance);
if (prop == null)
{
if (type.BaseType == null)
throw new MissingMethodException($"Couldn't find property {name} in {type}");
return GetProperty<T>(obj, type.BaseType, name);
}
return (T) prop
.GetGetMethod(nonPublic: true).Invoke(obj, new object[] { });
}
public static void SetProperty<T>(object obj, string name, T val)
{
SetProperty(obj, obj.GetType(), name, val);
}
public static void SetProperty<T>(object obj, Type type, string name, T value)
{
var prop = type
.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Instance);
if (prop == null)
{
if (type.BaseType == null)
throw new MissingMethodException($"Couldn't find property {name} in {type}");
SetProperty(obj, type.BaseType, name, value);
return;
}
prop.GetSetMethod(nonPublic: true).Invoke(obj, new object[] {value});
}
}
}
The way this code works is that when IsEnabled is updated, CanUserAddRows is changed and that triggers the setter of NewItemPlaceholderPosition. By calling CancelRowItem and UpdateNewItemPlaceholder before NewItemPlaceholderPosition is set, we cancel the transaction immediately (it is insufficient to call CancelEdit). Setting HasCellValidationError to false also helps recover from some corner cases that arise when you have validation errors.
I have used holmes answer but didn't work for me properly. So I changed little bit.
Here is my solution:
First of all, because of I'm using MVVM, I added this codes to the datagrid:
<i:Interaction.Triggers>
<i:EventTrigger EventName="InitializingNewItem">
<ei:CallMethodAction TargetObject="{Binding}" MethodName="OnDataGridInitializingNewItem"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Namespaces are these:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
Then, I added this code to the ViewModel and set the DataGrid:
private DataGrid _dg { get; set; }
public void OnDataGridInitializingNewItem(object sender, InitializingNewItemEventArgs e)
{
if (_dg == null)
_dg = (DataGrid)sender;
}
After all, when needed, I ran this code:
_dg.CommitEdit();
Finally it works very well :)
PS:
First, I had tried CancelEdit method instead of CommitEdit. It worked and I went to another view that opened like pop-up. When I finished what to do and return to the view, last added row was gone. But it was committed to the db. After re-open the view, it was there.
I had such a problem, but in my case the Grid was wrapped in an AdornerDecorator, removing it, everything worked
I have an Image control with it's source bound to a property on an object(string url to an image). After making a service call, i update the data object with a new URL. The exception is thrown after it leaves my code, after invoking the PropertyChanged event.
The data structure and the service logic are all done in a core dll that has no knowledge of the UI. How do I sync up with the UI thread when i cant access a Dispatcher?
PS: Accessing Application.Current.RootVisual in order to get at a Dispatcher is not a solution because the root visual is on a different thread(causing the exact exception i need to prevent).
PPS: This only is a problem with the image control, binding to any other ui element, the cross thread issue is handled for you.
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => {...});
Also look here.
Have you tried implementing INotifyPropertyChanged?
The property getter for RootVisual on the Application class has a thread check which causes that exception. I got around this by storing the root visual's dispatcher in my own property in my App.xaml.cs:
public static Dispatcher RootVisualDispatcher { get; set; }
private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = new Page();
RootVisualDispatcher = RootVisual.Dispatcher;
}
If you then call BeginInvoke on App.RootVisualDispatcher rather than Application.Current.RootVisual.Dispatcher you shouldn't get this exception.
I ran into a similar issue to this, but this was in windows forms:
I have a class that has it's own thread, updating statistics about another process, there is a control in my UI that is databound to this object. I was running into cross-thread call issues, here is how I resolved it:
Form m_MainWindow; //Reference to the main window of my application
protected virtual void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
if(m_MainWindow.InvokeRequired)
m_MainWindow.Invoke(
PropertyChanged, this, new PropertyChangedEventArgs(propertyName);
else
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
This seems to work great, if anyone has suggestions, please let me know.
When ever we want to update UI related items that action should happen in the UI thread else you will get an invalid cross thread access exception
Deployment.Current.Dispatcher.BeginInvoke( () =>
{
UpdateUI(); // DO the actions in the function Update UI
});
public void UpdateUI()
{
//to do :Update UI elements here
}
The INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed.
For example, consider a Person object with a property called FirstName. To provide generic property-change notification, the Person type implements the INotifyPropertyChanged interface and raises a PropertyChanged event when FirstName is changed.
For change notification to occur in a binding between a bound client and a data source, your bound type should either:
Implement the INotifyPropertyChanged interface (preferred).
Provide a change event for each property of the bound type.
Do not do both.
Example:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
// Change the namespace to the project name.
namespace TestNotifyPropertyChangedCS
{
// This form demonstrates using a BindingSource to bind
// a list to a DataGridView control. The list does not
// raise change notifications. However the DemoCustomer type
// in the list does.
public partial class Form1 : Form
{
// This button causes the value of a list element to be changed.
private Button changeItemBtn = new Button();
// This DataGridView control displays the contents of the list.
private DataGridView customersDataGridView = new DataGridView();
// This BindingSource binds the list to the DataGridView control.
private BindingSource customersBindingSource = new BindingSource();
public Form1()
{
InitializeComponent();
// Set up the "Change Item" button.
this.changeItemBtn.Text = "Change Item";
this.changeItemBtn.Dock = DockStyle.Bottom;
this.changeItemBtn.Click +=
new EventHandler(changeItemBtn_Click);
this.Controls.Add(this.changeItemBtn);
// Set up the DataGridView.
customersDataGridView.Dock = DockStyle.Top;
this.Controls.Add(customersDataGridView);
this.Size = new Size(400, 200);
}
private void Form1_Load(object sender, EventArgs e)
{
// Create and populate the list of DemoCustomer objects
// which will supply data to the DataGridView.
BindingList<DemoCustomer> customerList = new BindingList<DemoCustomer>();
customerList.Add(DemoCustomer.CreateNewCustomer());
customerList.Add(DemoCustomer.CreateNewCustomer());
customerList.Add(DemoCustomer.CreateNewCustomer());
// Bind the list to the BindingSource.
this.customersBindingSource.DataSource = customerList;
// Attach the BindingSource to the DataGridView.
this.customersDataGridView.DataSource =
this.customersBindingSource;
}
// Change the value of the CompanyName property for the first
// item in the list when the "Change Item" button is clicked.
void changeItemBtn_Click(object sender, EventArgs e)
{
// Get a reference to the list from the BindingSource.
BindingList<DemoCustomer> customerList =
this.customersBindingSource.DataSource as BindingList<DemoCustomer>;
// Change the value of the CompanyName property for the
// first item in the list.
customerList[0].CustomerName = "Tailspin Toys";
customerList[0].PhoneNumber = "(708)555-0150";
}
}
// This is a simple customer class that
// implements the IPropertyChange interface.
public class DemoCustomer : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private Guid idValue = Guid.NewGuid();
private string customerNameValue = String.Empty;
private string phoneNumberValue = String.Empty;
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// The constructor is private to enforce the factory pattern.
private DemoCustomer()
{
customerNameValue = "Customer";
phoneNumberValue = "(312)555-0100";
}
// This is the public factory method.
public static DemoCustomer CreateNewCustomer()
{
return new DemoCustomer();
}
// This property represents an ID, suitable
// for use as a primary key in a database.
public Guid ID
{
get
{
return this.idValue;
}
}
public string CustomerName
{
get
{
return this.customerNameValue;
}
set
{
if (value != this.customerNameValue)
{
this.customerNameValue = value;
NotifyPropertyChanged();
}
}
}
public string PhoneNumber
{
get
{
return this.phoneNumberValue;
}
set
{
if (value != this.phoneNumberValue)
{
this.phoneNumberValue = value;
NotifyPropertyChanged();
}
}
}
}
}