I'm stuck with some legacy code that I want to upgrade a bit. I want to change the way the ErrorProvider
shows the error status on a Control. Default behavior is the Icon, and a ToolTip
if you hover on the icon.
I would like to change this behavior to be more similar to what we use in our WPF controls. Which is a red back-color(Salmon pink) and the tool-tip on the control itself.
Any tips, links or some way forward
EDIT.
See my answer below, on what i ended up with.
ErrorProvider
component doesn't support this feature and if you need it you can create it yourself.
You can subscribe to BindingComplete
event of a BindingManagerBase
and then you can use the event arg which is of type BindingCompleteEventArgs
that contains some useful properties:
ErrorText
to determine if there is an error in data-binding
Binding.Control
to determine the control which is bounded to
These are enough for us to implement our solution.
Code
Here is a sample code which shows how can you handle BindingComplete
event and use it to change BackColor
and tool-tip of a control based on it's valid or invalid state.
Suppose you have a binding source, myBindingSource
which is bound to a SampleModel
class which is implemented IDataErrorInfo
. You can subscribe to BindingComplete
event of this.BindingContext[this.myBindingSource]
:
private void Form1_Load(object sender, EventArgs e)
{
this.myBindingSource.DataSource = new SampleModel();
var bindingManager = this.BindingContext[this.myBindingSource];
bindingManager.BindingComplete += bindingManager_BindingComplete;
}
Dictionary<Control, Color> Items = new Dictionary<Control, Color>();
private void bindingManager_BindingComplete(object sender, BindingCompleteEventArgs e)
{
var control = e.Binding.Control;
//Store Original BackColor
if (!Items.ContainsKey(control))
Items[control] = control.BackColor;
//Check If there is an error
if (!string.IsNullOrEmpty(e.ErrorText))
{
control.BackColor = Color.Salmon;
this.errorToolTip.SetToolTip(control, e.ErrorText);
}
else
{
e.Binding.Control.BackColor = Items[e.Binding.Control];
this.errorToolTip.SetToolTip(control, null);
}
}
Thank you Reza Aghaei. This is what i came up with based on your comment and some additional searching... Some of this code comes from msdn resource
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Linq;
using System.Security.Permissions;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ErrorProvider
{
class BackgroundColorErrorProvider: Component, IExtenderProvider, ISupportInitialize
{
public BackgroundColorErrorProvider()
{
currentChanged = new EventHandler(ErrorManager_CurrentChanged);
}
public BackgroundColorErrorProvider(ContainerControl parentControl)
: this()
{
this.parentControl = parentControl;
propChangedEvent = new EventHandler(ParentControl_BindingContextChanged);
parentControl.BindingContextChanged += propChangedEvent;
}
public BackgroundColorErrorProvider(IContainer container)
: this()
{
if (container == null) {
throw new ArgumentNullException("container");
}
container.Add(this);
}
public bool CanExtend(object extendee)
{
return extendee is Control && !(extendee is Form) && !(extendee is ToolBar);
}
private bool inSetErrorManager = false;
private object dataSource;
private string dataMember = null;
private ContainerControl parentControl;
private BindingManagerBase errorManager;
private bool initializing;
private EventHandler currentChanged;
private EventHandler propChangedEvent;
private Dictionary<Control, Color> originalColor = new Dictionary<Control, Color>();
private Color errorBackgroundColor;
public ContainerControl ContainerControl
{
[UIPermission(SecurityAction.LinkDemand, Window = UIPermissionWindow.AllWindows)]
[UIPermission(SecurityAction.InheritanceDemand, Window = UIPermissionWindow.AllWindows)]
get
{
return parentControl;
}
set
{
if (parentControl != value)
{
if (parentControl != null)
parentControl.BindingContextChanged -= propChangedEvent;
parentControl = value;
if (parentControl != null)
parentControl.BindingContextChanged += propChangedEvent;
Set_ErrorManager(this.DataSource, this.DataMember, true);
}
}
}
public string DataMember
{
get { return dataMember; }
set
{
if (value == null) value = "";
Set_ErrorManager(this.DataSource, value, false);
}
}
public object DataSource
{
get { return dataSource; }
set
{
if ( parentControl != null && value != null && String.IsNullOrEmpty(this.dataMember))
{
// Let's check if the datamember exists in the new data source
try
{
errorManager = parentControl.BindingContext[value, this.dataMember];
}
catch (ArgumentException)
{
// The data member doesn't exist in the data source, so set it to null
this.dataMember = "";
}
}
Set_ErrorManager(value, this.DataMember, false);
}
}
public override ISite Site
{
set
{
base.Site = value;
if (value == null)
return;
IDesignerHost host = value.GetService(typeof(IDesignerHost)) as IDesignerHost;
if (host != null)
{
IComponent baseComp = host.RootComponent;
if (baseComp is ContainerControl)
{
this.ContainerControl = (ContainerControl)baseComp;
}
}
}
}
private ToolTip toolTip;
public ToolTip ToolTip
{
get { return toolTip; }
set { toolTip = value; }
}
public Color ErrorBackgroundColor
{
get { return errorBackgroundColor; }
set { errorBackgroundColor = value; }
}
private void Set_ErrorManager(object newDataSource, string newDataMember, bool force)
{
if (inSetErrorManager)
return;
inSetErrorManager = true;
try
{
bool dataSourceChanged = this.DataSource != newDataSource;
bool dataMemberChanged = this.DataMember != newDataMember;
//if nothing changed, then do not do any work
//
if (!dataSourceChanged && !dataMemberChanged && !force)
{
return;
}
// set the dataSource and the dataMember
//
this.dataSource = newDataSource;
this.dataMember = newDataMember;
if (!initializing)
{
UnwireEvents(errorManager);
// get the new errorManager
//
if (parentControl != null && this.dataSource != null && parentControl.BindingContext != null)
{
errorManager = parentControl.BindingContext[this.dataSource, this.dataMember];
}
else
{
errorManager = null;
}
// wire the events
//
WireEvents(errorManager);
// see if there are errors at the current
// item in the list, w/o waiting for the position to change
if (errorManager != null)
UpdateBinding();
}
}
finally
{
inSetErrorManager = false;
}
}
public void UpdateBinding()
{
ErrorManager_CurrentChanged(errorManager, EventArgs.Empty);
}
private void UnwireEvents(BindingManagerBase listManager)
{
if (listManager != null)
{
listManager.CurrentChanged -= currentChanged;
listManager.BindingComplete -= new BindingCompleteEventHandler(this.ErrorManager_BindingComplete);
CurrencyManager currManager = listManager as CurrencyManager;
if (currManager != null)
{
currManager.ItemChanged -= new ItemChangedEventHandler(this.ErrorManager_ItemChanged);
currManager.Bindings.CollectionChanged -= new CollectionChangeEventHandler(this.ErrorManager_BindingsChanged);
}
}
}
private void WireEvents(BindingManagerBase listManager)
{
if (listManager != null)
{
listManager.CurrentChanged += currentChanged;
listManager.BindingComplete += new BindingCompleteEventHandler(this.ErrorManager_BindingComplete);
CurrencyManager currManager = listManager as CurrencyManager;
if (currManager != null)
{
currManager.ItemChanged += new ItemChangedEventHandler(this.ErrorManager_ItemChanged);
currManager.Bindings.CollectionChanged += new CollectionChangeEventHandler(this.ErrorManager_BindingsChanged);
}
}
}
private void ErrorManager_BindingsChanged(object sender, CollectionChangeEventArgs e)
{
ErrorManager_CurrentChanged(errorManager, e);
}
private void ParentControl_BindingContextChanged(object sender, EventArgs e)
{
Set_ErrorManager(this.DataSource, this.DataMember, true);
}
private void ErrorManager_ItemChanged(object sender, ItemChangedEventArgs e)
{
BindingsCollection errBindings = errorManager.Bindings;
int bindingsCount = errBindings.Count;
// If the list became empty then reset the errors
if (e.Index == -1 && errorManager.Count == 0)
{
for (int j = 0; j < bindingsCount; j++)
{
if ((errBindings[j].Control != null))
{
// ...ignore everything but bindings to Controls
SetError(errBindings[j].Control, "");
}
}
}
else
{
ErrorManager_CurrentChanged(sender, e);
}
}
private void SetError(Control control, string p)
{
if (String.IsNullOrEmpty(p))
{
if (originalColor.ContainsKey(control))
control.BackColor = originalColor[control];
toolTip.SetToolTip(control, null);
}
else
{
control.BackColor = ErrorBackgroundColor;
toolTip.SetToolTip(control, p);
}
}
private void ErrorManager_BindingComplete(object sender, BindingCompleteEventArgs e)
{
Binding binding = e.Binding;
if (binding != null && binding.Control != null)
{
SetError(binding.Control, (e.ErrorText == null ? String.Empty : e.ErrorText));
}
}
private void ErrorManager_CurrentChanged(object sender, EventArgs e)
{
if (errorManager.Count == 0)
{
return;
}
object value = errorManager.Current;
if (!(value is IDataErrorInfo))
{
return;
}
BindingsCollection errBindings = errorManager.Bindings;
int bindingsCount = errBindings.Count;
// We can only show one error per control, so we will build up a string...
//
Hashtable controlError = new Hashtable(bindingsCount);
for (int j = 0; j < bindingsCount; j++)
{
// Ignore everything but bindings to Controls
if (errBindings[j].Control == null)
{
continue;
}
string error = ((IDataErrorInfo)value)[errBindings[j].BindingMemberInfo.BindingField];
if (error == null)
{
error = "";
}
string outputError = "";
if (controlError.Contains(errBindings[j].Control))
outputError = (string)controlError[errBindings[j].Control];
// VSWhidbey 106890: Utilize the error string without including the field name.
if (String.IsNullOrEmpty(outputError))
{
outputError = error;
}
else
{
outputError = string.Concat(outputError, "\r\n", error);
}
controlError[errBindings[j].Control] = outputError;
}
IEnumerator enumerator = controlError.GetEnumerator();
while (enumerator.MoveNext())
{
DictionaryEntry entry = (DictionaryEntry)enumerator.Current;
SetError((Control)entry.Key, (string)entry.Value);
}
}
public void BeginInit()
{
initializing = true;
}
public void EndInit()
{
initializing = false;
Set_ErrorManager(this.DataSource, this.DataMember, true);
}
}
}