|
//------------------------------------------------------------------------------
// <copyright file="ErrorProvider.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Windows.Forms {
using System.Threading;
using System.Runtime.Remoting;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System;
using System.Collections;
using System.Globalization;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System.Windows.Forms.Internal;
using System.Security.Permissions;
using System.Runtime.Versioning;
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider"]/*' />
/// <devdoc>
/// ErrorProvider presents a simple user interface for indicating to the
/// user that a control on a form has an error associated with it. If a
/// error description string is specified for the control, then an icon
/// will appear next to the control, and when the mouse hovers over the
/// icon, a tooltip will appear showing the error description string.
/// </devdoc>
[
ProvideProperty("IconPadding", typeof(Control)),
ProvideProperty("IconAlignment", typeof(Control)),
ProvideProperty("Error", typeof(Control)),
ToolboxItemFilter("System.Windows.Forms"),
ComplexBindingProperties("DataSource", "DataMember"),
SRDescription(SR.DescriptionErrorProvider)
]
public class ErrorProvider : Component, IExtenderProvider, ISupportInitialize {
//
// FIELDS
//
Hashtable items = new Hashtable();
Hashtable windows = new Hashtable();
Icon icon = DefaultIcon;
IconRegion region;
int itemIdCounter;
int blinkRate;
ErrorBlinkStyle blinkStyle;
bool showIcon = true; // used for blinking
private bool inSetErrorManager = false;
private bool setErrorManagerOnEndInit = false;
private bool initializing = false;
[ThreadStatic]
static Icon defaultIcon = null;
const int defaultBlinkRate = 250;
const ErrorBlinkStyle defaultBlinkStyle = ErrorBlinkStyle.BlinkIfDifferentError;
const ErrorIconAlignment defaultIconAlignment = ErrorIconAlignment.MiddleRight;
// data binding
private ContainerControl parentControl;
private object dataSource = null;
private string dataMember = null;
private BindingManagerBase errorManager;
private EventHandler currentChanged;
// listen to the OnPropertyChanged event in the ContainerControl
private EventHandler propChangedEvent;
private EventHandler onRightToLeftChanged;
private bool rightToLeft = false;
private object userData;
//
// CONSTRUCTOR
//
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ErrorProvider"]/*' />
/// <devdoc>
/// Default constructor.
/// </devdoc>
public ErrorProvider() {
icon = DefaultIcon;
blinkRate = defaultBlinkRate;
blinkStyle = defaultBlinkStyle;
currentChanged = new EventHandler(ErrorManager_CurrentChanged);
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ErrorProvider1"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public ErrorProvider(ContainerControl parentControl) : this() {
this.parentControl = parentControl;
propChangedEvent = new EventHandler(ParentControl_BindingContextChanged);
parentControl.BindingContextChanged += propChangedEvent;
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ErrorProvider2"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public ErrorProvider(IContainer container) : this() {
if (container == null) {
throw new ArgumentNullException("container");
}
container.Add(this);
}
//
// PROPERTIES
//
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.Site"]/*' />
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;
}
}
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.BlinkStyle"]/*' />
/// <devdoc>
/// Returns or sets when the error icon flashes.
/// </devdoc>
[
SRCategory(SR.CatBehavior),
DefaultValue(defaultBlinkStyle),
SRDescription(SR.ErrorProviderBlinkStyleDescr)
]
public ErrorBlinkStyle BlinkStyle {
get {
if (blinkRate == 0) {
return ErrorBlinkStyle.NeverBlink;
}
return blinkStyle;
}
set {
//valid values are 0x0 to 0x2
if (!ClientUtils.IsEnumValid(value, (int)value, (int)ErrorBlinkStyle.BlinkIfDifferentError, (int)ErrorBlinkStyle.NeverBlink)){
throw new InvalidEnumArgumentException("value", (int)value, typeof(ErrorBlinkStyle));
}
// If the blinkRate == 0, then set blinkStyle = neverBlink
//
if (this.blinkRate == 0) {
value = ErrorBlinkStyle.NeverBlink;
}
if (this.blinkStyle == value) {
return;
}
if (value == ErrorBlinkStyle.AlwaysBlink) {
// we need to startBlinking on all the controlItems
// in our items hashTable.
this.showIcon = true;
this.blinkStyle = ErrorBlinkStyle.AlwaysBlink;
foreach (ErrorWindow w in windows.Values)
{
w.StartBlinking();
}
}
else if (blinkStyle == ErrorBlinkStyle.AlwaysBlink) {
// we need to stop blinking...
this.blinkStyle = value;
foreach (ErrorWindow w in windows.Values) {
w.StopBlinking();
}
}
else {
this.blinkStyle = value;
}
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ContainerControl"]/*' />
/// <devdoc>
/// Indicates what container control (usually the form) should be inspected for bindings.
/// A binding will reveal what control to place errors on for a
/// error in the current row in the DataSource/DataMember pair.
/// </devdoc>
[
DefaultValue(null),
SRCategory(SR.CatData),
SRDescription(SR.ErrorProviderContainerControlDescr)
]
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);
}
}
}
/// <include file='doc\DateTimePicker.uex' path='docs/doc[@for="DateTimePicker.RightToLeft"]/*' />
/// <devdoc>
/// This is used for international applications where the language
/// is written from RightToLeft. When this property is true,
// text will be from right to left.
/// </devdoc>
[
SRCategory(SR.CatAppearance),
Localizable(true),
DefaultValue(false),
SRDescription(SR.ControlRightToLeftDescr)
]
public virtual bool RightToLeft {
get {
return rightToLeft;
}
set {
if (value != rightToLeft) {
rightToLeft = value;
OnRightToLeftChanged(EventArgs.Empty);
}
}
}
/// <include file='doc\Form.uex' path='docs/doc[@for="Form.RightToLeftChanged"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
[SRCategory(SR.CatPropertyChanged), SRDescription(SR.ControlOnRightToLeftChangedDescr)]
public event EventHandler RightToLeftChanged {
add {
onRightToLeftChanged += value;
}
remove {
onRightToLeftChanged -= value;
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.Tag"]/*' />
/// <devdoc>
/// User defined data associated with the control.
/// </devdoc>
[
SRCategory(SR.CatData),
Localizable(false),
Bindable(true),
SRDescription(SR.ControlTagDescr),
DefaultValue(null),
TypeConverter(typeof(StringConverter)),
]
public object Tag {
get {
return userData;
}
set {
userData = 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) {
setErrorManagerOnEndInit = true;
}
else {
// unwire the errorManager:
//
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;
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.DataSource"]/*' />
/// <devdoc>
/// Indicates the source of data to bind errors against.
/// </devdoc>
[
DefaultValue(null),
SRCategory(SR.CatData),
AttributeProvider(typeof(IListSource)),
SRDescription(SR.ErrorProviderDataSourceDescr)
]
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);
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ShouldSerializeDataSource"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
private bool ShouldSerializeDataSource() {
return (dataSource != null);
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.DataMember"]/*' />
/// <devdoc>
/// Indicates the sub-list of data from the DataSource to bind errors against.
/// </devdoc>
[
DefaultValue(null),
SRCategory(SR.CatData),
Editor("System.Windows.Forms.Design.DataMemberListEditor, " + AssemblyRef.SystemDesign, typeof(System.Drawing.Design.UITypeEditor)),
SRDescription(SR.ErrorProviderDataMemberDescr)
]
public string DataMember {
get {
return dataMember;
}
set {
if (value == null) value = "";
Set_ErrorManager(this.DataSource, value, false);
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ShouldSerializeDataMember"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
private bool ShouldSerializeDataMember() {
return (dataMember != null && dataMember.Length != 0);
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.BindToDataAndErrors"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public void BindToDataAndErrors(object newDataSource, string newDataMember) {
Set_ErrorManager(newDataSource, newDataMember, false);
}
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 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 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_BindingsChanged(object sender, CollectionChangeEventArgs e) {
ErrorManager_CurrentChanged(errorManager, e);
}
private void ParentControl_BindingContextChanged(object sender, EventArgs e) {
Set_ErrorManager(this.DataSource, this.DataMember, true);
}
// Work around... we should figure out if errors changed automatically.
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.UpdateBinding"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public void UpdateBinding() {
ErrorManager_CurrentChanged(errorManager, EventArgs.Empty);
}
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 ErrorManager_CurrentChanged(object sender, EventArgs e) {
Debug.Assert(sender == errorManager, "who else can send us messages?");
// flush the old list
//
// items.Clear();
if (errorManager.Count == 0) {
return;
}
object value = errorManager.Current;
if ( !(value is IDataErrorInfo)) {
return;
}
BindingsCollection errBindings = errorManager.Bindings;
int bindingsCount = errBindings.Count;
// we need to delete the blinkPhases from each controlItem (suppose
// that the error that we get is the same error. then we want to
// show the error and not blink )
//
foreach (ControlItem ctl in items.Values) {
ctl.BlinkPhase = 0;
}
// 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;
}
BindToObject dataBinding = errBindings[j].BindToObject;
string error = ((IDataErrorInfo) value)[dataBinding.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);
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.BlinkRate"]/*' />
/// <devdoc>
/// Returns or set the rate in milliseconds at which the error icon flashes.
/// </devdoc>
[
SRCategory(SR.CatBehavior),
DefaultValue(defaultBlinkRate),
SRDescription(SR.ErrorProviderBlinkRateDescr),
RefreshProperties(RefreshProperties.Repaint)
]
public int BlinkRate {
get {
return blinkRate;
}
set {
if (value < 0) {
throw new ArgumentOutOfRangeException("BlinkRate", value, SR.GetString(SR.BlinkRateMustBeZeroOrMore));
}
blinkRate = value;
// If we set the blinkRate = 0 then set BlinkStyle = NeverBlink
if (blinkRate == 0)
BlinkStyle = ErrorBlinkStyle.NeverBlink;
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.DefaultIcon"]/*' />
/// <devdoc>
/// Demand load and cache the default icon.
/// </devdoc>
/// <internalonly/>
static Icon DefaultIcon {
get {
if (defaultIcon == null) {
lock (typeof(ErrorProvider)) {
if (defaultIcon == null) {
defaultIcon = new Icon(typeof(ErrorProvider), "Error.ico");
}
}
}
return defaultIcon;
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.Icon"]/*' />
/// <devdoc>
/// Returns or sets the Icon that displayed next to a control when an error
/// description string has been set for the control. For best results, an
/// icon containing a 16 by 16 icon should be used.
/// </devdoc>
[
Localizable(true),
SRCategory(SR.CatAppearance),
SRDescription(SR.ErrorProviderIconDescr)
]
public Icon Icon {
get {
return icon;
}
set {
if (value == null)
throw new ArgumentNullException("value");
icon = value;
DisposeRegion();
ErrorWindow[] array = new ErrorWindow[windows.Values.Count];
windows.Values.CopyTo(array, 0);
for (int i = 0; i < array.Length; i++)
array[i].Update(false /*timerCaused*/);
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.Region"]/*' />
/// <devdoc>
/// Create the icon region on demand.
/// </devdoc>
/// <internalonly/>
internal IconRegion Region {
[ResourceExposure(ResourceScope.Machine)]
[ResourceConsumption(ResourceScope.Machine)]
get {
if (region == null)
region = new IconRegion(Icon);
return region;
}
}
//
// METHODS
//
// Begin bulk member initialization - deferring binding to data source until EndInit is reached
void ISupportInitialize.BeginInit() {
initializing = true;
}
// End bulk member initialization by binding to data source
private void EndInitCore() {
initializing = false;
if (setErrorManagerOnEndInit) {
setErrorManagerOnEndInit = false;
Set_ErrorManager(this.DataSource, this.DataMember, true);
}
}
// Check to see if DataSource has completed its initialization, before ending our initialization.
// If DataSource is still initializing, hook its Initialized event and wait for it to signal completion.
// If DataSource is already initialized, just go ahead and complete our initialization now.
//
void ISupportInitialize.EndInit() {
ISupportInitializeNotification dsInit = (this.DataSource as ISupportInitializeNotification);
if (dsInit != null && !dsInit.IsInitialized) {
dsInit.Initialized += new EventHandler(DataSource_Initialized);
}
else {
EndInitCore();
}
}
// Respond to late completion of the DataSource's initialization, by completing our own initialization.
// This situation can arise if the call to the DataSource's EndInit() method comes after the call to the
// BindingSource's EndInit() method (since code-generated ordering of these calls is non-deterministic).
//
private void DataSource_Initialized(object sender, EventArgs e) {
ISupportInitializeNotification dsInit = (this.DataSource as ISupportInitializeNotification);
Debug.Assert(dsInit != null, "ErrorProvider: ISupportInitializeNotification.Initialized event received, but current DataSource does not support ISupportInitializeNotification!");
Debug.Assert(dsInit.IsInitialized, "ErrorProvider: DataSource sent ISupportInitializeNotification.Initialized event but before it had finished initializing.");
if (dsInit != null) {
dsInit.Initialized -= new EventHandler(DataSource_Initialized);
}
EndInitCore();
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.Clear"]/*' />
/// <devdoc>
/// Clears all errors being tracked by this error provider, ie. undoes all previous calls to SetError.
/// </devdoc>
public void Clear() {
ErrorWindow[] w = new ErrorWindow[windows.Values.Count];
windows.Values.CopyTo(w, 0);
for (int i = 0; i < w.Length; i++) {
w[i].Dispose();
}
windows.Clear();
foreach (ControlItem item in items.Values) {
if (item != null) {
item.Dispose();
}
}
items.Clear();
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.CanExtend"]/*' />
/// <devdoc>
/// Returns whether a control can be extended.
/// </devdoc>
public bool CanExtend(object extendee) {
return extendee is Control && !(extendee is Form) && !(extendee is ToolBar);
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.Dispose"]/*' />
/// <devdoc>
/// Release any resources that this component is using. After calling Dispose,
/// the component should no longer be used.
/// </devdoc>
protected override void Dispose(bool disposing) {
if (disposing) {
Clear();
DisposeRegion();
UnwireEvents(errorManager);
}
base.Dispose(disposing);
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.DisposeRegion"]/*' />
/// <devdoc>
/// Helper to dispose the cached icon region.
/// </devdoc>
/// <internalonly/>
void DisposeRegion() {
if (region != null) {
region.Dispose();
region = null;
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.EnsureControlItem"]/*' />
/// <devdoc>
/// Helper to make sure we have allocated a control item for this control.
/// </devdoc>
/// <internalonly/>
private ControlItem EnsureControlItem(Control control) {
if (control == null)
throw new ArgumentNullException("control");
ControlItem item = (ControlItem)items[control];
if (item == null) {
item = new ControlItem(this, control, (IntPtr)(++itemIdCounter));
items[control] = item;
}
return item;
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.EnsureErrorWindow"]/*' />
/// <devdoc>
/// Helper to make sure we have allocated an error window for this control.
/// </devdoc>
/// <internalonly/>
internal ErrorWindow EnsureErrorWindow(Control parent) {
ErrorWindow window = (ErrorWindow)windows[parent];
if (window == null) {
window = new ErrorWindow(this, parent);
windows[parent] = window;
}
return window;
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.GetError"]/*' />
/// <devdoc>
/// Returns the current error description string for the specified control.
/// </devdoc>
[
DefaultValue(""),
Localizable(true),
SRCategory(SR.CatAppearance),
SRDescription(SR.ErrorProviderErrorDescr)
]
public string GetError(Control control) {
return EnsureControlItem(control).Error;
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.GetIconAlignment"]/*' />
/// <devdoc>
/// Returns where the error icon should be placed relative to the control.
/// </devdoc>
[
DefaultValue(defaultIconAlignment),
Localizable(true),
SRCategory(SR.CatAppearance),
SRDescription(SR.ErrorProviderIconAlignmentDescr)
]
public ErrorIconAlignment GetIconAlignment(Control control) {
return EnsureControlItem(control).IconAlignment;
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.GetIconPadding"]/*' />
/// <devdoc>
/// Returns the amount of extra space to leave next to the error icon.
/// </devdoc>
[
DefaultValue(0),
Localizable(true),
SRCategory(SR.CatAppearance),
SRDescription(SR.ErrorProviderIconPaddingDescr)
]
public int GetIconPadding(Control control) {
return EnsureControlItem(control).IconPadding;
}
private void ResetIcon() {
Icon = DefaultIcon;
}
/// <include file='doc\Form.uex' path='docs/doc[@for="Form.OnRightToLeftChanged"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
[EditorBrowsable(EditorBrowsableState.Advanced)]
protected virtual void OnRightToLeftChanged(EventArgs e) {
foreach (ErrorWindow w in windows.Values)
{
w.Update(false);
}
if (onRightToLeftChanged != null) {
onRightToLeftChanged(this, e);
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.SetError"]/*' />
/// <devdoc>
/// Sets the error description string for the specified control.
/// </devdoc>
public void SetError(Control control, string value) {
EnsureControlItem(control).Error = value;
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.SetIconAlignment"]/*' />
/// <devdoc>
/// Sets where the error icon should be placed relative to the control.
/// </devdoc>
public void SetIconAlignment(Control control, ErrorIconAlignment value) {
EnsureControlItem(control).IconAlignment = value;
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.SetIconPadding"]/*' />
/// <devdoc>
/// Sets the amount of extra space to leave next to the error icon.
/// </devdoc>
public void SetIconPadding(Control control, int padding) {
EnsureControlItem(control).IconPadding = padding;
}
private bool ShouldSerializeIcon() {
return Icon != DefaultIcon;
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ErrorWindow"]/*' />
/// <devdoc>
/// There is one ErrorWindow for each control parent. It is parented to the
/// control parent. The window's region is made up of the regions from icons
/// of all child icons. The window's size is the enclosing rectangle for all
/// the regions. A tooltip window is created as a child of this window. The
/// rectangle associated with each error icon being displayed is added as a
/// tool to the tooltip window.
/// </devdoc>
/// <internalonly/>
internal class ErrorWindow : NativeWindow {
//
// FIELDS
//
ArrayList items = new ArrayList();
Control parent;
ErrorProvider provider;
Rectangle windowBounds = Rectangle.Empty;
System.Windows.Forms.Timer timer;
NativeWindow tipWindow;
// VSWhidbey #455702
DeviceContext mirrordc= null;
Size mirrordcExtent = Size.Empty;
Point mirrordcOrigin = Point.Empty;
DeviceContextMapMode mirrordcMode = DeviceContextMapMode.Text;
//
// CONSTRUCTORS
//
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ErrorWindow.ErrorWindow"]/*' />
/// <devdoc>
/// Construct an error window for this provider and control parent.
/// </devdoc>
public ErrorWindow(ErrorProvider provider, Control parent) {
this.provider = provider;
this.parent = parent;
}
//
// METHODS
//
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ErrorWindow.Add"]/*' />
/// <devdoc>
/// This is called when a control would like to show an error icon.
/// </devdoc>
public void Add(ControlItem item) {
items.Add(item);
if (!EnsureCreated())
{
return;
}
NativeMethods.TOOLINFO_T toolInfo = new NativeMethods.TOOLINFO_T();
toolInfo.cbSize = Marshal.SizeOf(toolInfo);
toolInfo.hwnd = Handle;
toolInfo.uId = item.Id;
toolInfo.lpszText = item.Error;
toolInfo.uFlags = NativeMethods.TTF_SUBCLASS;
UnsafeNativeMethods.SendMessage(new HandleRef(tipWindow, tipWindow.Handle), NativeMethods.TTM_ADDTOOL, 0, toolInfo);
Update(false /*timerCaused*/);
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ErrorWindow.Dispose"]/*' />
/// <devdoc>
/// Called to get rid of any resources the Object may have.
/// </devdoc>
public void Dispose() {
EnsureDestroyed();
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ErrorWindow.EnsureCreated"]/*' />
/// <devdoc>
/// Make sure the error window is created, and the tooltip window is created.
/// </devdoc>
bool EnsureCreated() {
if (Handle == IntPtr.Zero) {
if (!parent.IsHandleCreated)
{
return false;
}
CreateParams cparams = new CreateParams();
cparams.Caption = String.Empty;
cparams.Style = NativeMethods.WS_VISIBLE | NativeMethods.WS_CHILD;
cparams.ClassStyle = NativeMethods.CS_DBLCLKS;
cparams.X = 0;
cparams.Y = 0;
cparams.Width = 0;
cparams.Height = 0;
cparams.Parent = parent.Handle;
CreateHandle(cparams);
NativeMethods.INITCOMMONCONTROLSEX icc = new NativeMethods.INITCOMMONCONTROLSEX();
icc.dwICC = NativeMethods.ICC_TAB_CLASSES;
icc.dwSize = Marshal.SizeOf(icc);
SafeNativeMethods.InitCommonControlsEx(icc);
cparams = new CreateParams();
cparams.Parent = Handle;
cparams.ClassName = NativeMethods.TOOLTIPS_CLASS;
cparams.Style = NativeMethods.TTS_ALWAYSTIP;
tipWindow = new NativeWindow();
tipWindow.CreateHandle(cparams);
UnsafeNativeMethods.SendMessage(new HandleRef(tipWindow, tipWindow.Handle), NativeMethods.TTM_SETMAXTIPWIDTH, 0, SystemInformation.MaxWindowTrackSize.Width);
SafeNativeMethods.SetWindowPos(new HandleRef(tipWindow, tipWindow.Handle), NativeMethods.HWND_TOP, 0, 0, 0, 0, NativeMethods.SWP_NOSIZE | NativeMethods.SWP_NOMOVE | NativeMethods.SWP_NOACTIVATE);
UnsafeNativeMethods.SendMessage(new HandleRef(tipWindow, tipWindow.Handle), NativeMethods.TTM_SETDELAYTIME, NativeMethods.TTDT_INITIAL, 0);
}
return true;
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ErrorWindow.EnsureDestroyed"]/*' />
/// <devdoc>
/// Destroy the timer, toolwindow, and the error window itself.
/// </devdoc>
void EnsureDestroyed() {
if (timer != null) {
timer.Dispose();
timer = null;
}
if (tipWindow != null) {
tipWindow.DestroyHandle();
tipWindow = null;
}
// Hide the window and invalidate the parent to ensure
// that we leave no visual artifacts... given that we
// have a bizare region window, this is needed.
//
SafeNativeMethods.SetWindowPos(new HandleRef(this, Handle),
NativeMethods.HWND_TOP,
windowBounds.X,
windowBounds.Y,
windowBounds.Width,
windowBounds.Height,
NativeMethods.SWP_HIDEWINDOW
| NativeMethods.SWP_NOSIZE
| NativeMethods.SWP_NOMOVE);
if (parent != null) {
parent.Invalidate(true);
}
DestroyHandle();
Debug.Assert(mirrordc == null, "Why is mirrordc non-null?");
if (mirrordc != null) {
mirrordc.Dispose();
}
}
/// <devdoc>
///
/// VSWhidbey #455702.
///
/// Since we added mirroring to certain controls, we need to make sure the
/// error icons show up in the correct place. We cannot mirror the errorwindow
/// in EnsureCreated (although that would have been really easy), since we use
/// GDI+ for some of this code, and as we all know, GDI+ does not handle mirroring
/// at all.
///
/// To work around that we create our own mirrored dc when we need to.
///
/// </devdoc>
void CreateMirrorDC(IntPtr hdc, int originOffset) {
Debug.Assert(mirrordc == null, "Why is mirrordc non-null? Did you not call RestoreMirrorDC?");
mirrordc = DeviceContext.FromHdc(hdc);
if (parent.IsMirrored && mirrordc != null) {
mirrordc.SaveHdc();
mirrordcExtent = mirrordc.ViewportExtent;
mirrordcOrigin = mirrordc.ViewportOrigin;
mirrordcMode = mirrordc.SetMapMode(DeviceContextMapMode.Anisotropic);
mirrordc.ViewportExtent = new Size(-(mirrordcExtent.Width), mirrordcExtent.Height);
mirrordc.ViewportOrigin = new Point(mirrordcOrigin.X + originOffset, mirrordcOrigin.Y);
}
}
void RestoreMirrorDC() {
if (parent.IsMirrored && mirrordc != null) {
mirrordc.ViewportExtent = mirrordcExtent;
mirrordc.ViewportOrigin = mirrordcOrigin;
mirrordc.SetMapMode(mirrordcMode);
mirrordc.RestoreHdc();
mirrordc.Dispose();
}
mirrordc= null;
mirrordcExtent = Size.Empty;
mirrordcOrigin = Point.Empty;
mirrordcMode = DeviceContextMapMode.Text;
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ErrorWindow.OnPaint"]/*' />
/// <devdoc>
/// This is called when the error window needs to paint. We paint each icon at its
/// correct location.
/// </devdoc>
void OnPaint(ref Message m) {
NativeMethods.PAINTSTRUCT ps = new NativeMethods.PAINTSTRUCT();
IntPtr hdc = UnsafeNativeMethods.BeginPaint(new HandleRef(this, Handle), ref ps);
try {
CreateMirrorDC(hdc, windowBounds.Width - 1);
try {
for (int i = 0; i < items.Count; i++) {
ControlItem item = (ControlItem)items[i];
Rectangle bounds = item.GetIconBounds(provider.Region.Size);
SafeNativeMethods.DrawIconEx(new HandleRef(this, mirrordc.Hdc), bounds.X - windowBounds.X, bounds.Y - windowBounds.Y, new HandleRef(provider.Region, provider.Region.IconHandle), bounds.Width, bounds.Height, 0, NativeMethods.NullHandleRef, NativeMethods.DI_NORMAL);
}
}
finally {
RestoreMirrorDC();
}
}
finally {
UnsafeNativeMethods.EndPaint(new HandleRef(this, Handle), ref ps);
}
}
protected override void OnThreadException(Exception e) {
Application.OnThreadException(e);
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ErrorWindow.OnTimer"]/*' />
/// <devdoc>
/// This is called when an error icon is flashing, and the view needs to be updatd.
/// </devdoc>
void OnTimer(Object sender, EventArgs e) {
int blinkPhase = 0;
for (int i = 0; i < items.Count; i++) {
blinkPhase += ((ControlItem)items[i]).BlinkPhase;
}
if (blinkPhase == 0 && provider.BlinkStyle != ErrorBlinkStyle.AlwaysBlink) {
Debug.Assert(timer != null);
timer.Stop();
}
Update(true /*timerCaused*/);
}
private void OnToolTipVisibilityChanging(System.IntPtr id, bool toolTipShown) {
for (int i = 0; i < items.Count; i++) {
if (((ControlItem)items[i]).Id == id) {
((ControlItem)items[i]).ToolTipShown = toolTipShown;
}
}
#if DEBUG
int shownTooltips = 0;
for (int j = 0; j < items.Count; j++) {
if (((ControlItem)items[j]).ToolTipShown) {
shownTooltips++;
}
}
Debug.Assert(shownTooltips <= 1);
#endif
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ErrorWindow.Remove"]/*' />
/// <devdoc>
/// This is called when a control no longer needs to display an error icon.
/// </devdoc>
public void Remove(ControlItem item) {
items.Remove(item);
if (tipWindow != null) {
NativeMethods.TOOLINFO_T toolInfo = new NativeMethods.TOOLINFO_T();
toolInfo.cbSize = Marshal.SizeOf(toolInfo);
toolInfo.hwnd = Handle;
toolInfo.uId = item.Id;
UnsafeNativeMethods.SendMessage(new HandleRef(tipWindow, tipWindow.Handle), NativeMethods.TTM_DELTOOL, 0, toolInfo);
}
if (items.Count == 0) {
EnsureDestroyed();
}
else {
Update(false /*timerCaused*/);
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ErrorWindow.StartBlinking"]/*' />
/// <devdoc>
/// Start the blinking process. The timer will fire until there are no more
/// icons that need to blink.
/// </devdoc>
internal void StartBlinking() {
if (timer == null) {
timer = new System.Windows.Forms.Timer();
timer.Tick += new EventHandler(OnTimer);
}
timer.Interval = provider.BlinkRate;
timer.Start();
Update(false /*timerCaused*/);
}
internal void StopBlinking() {
if (timer != null) {
timer.Stop();
}
Update(false /*timerCaused*/);
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ErrorWindow.Update"]/*' />
/// <devdoc>
/// Move and size the error window, compute and set the window region,
/// set the tooltip rectangles and descriptions. This basically brings
/// the error window up to date with the internal data structures.
/// </devdoc>
public void Update(bool timerCaused) {
IconRegion iconRegion = provider.Region;
Size size = iconRegion.Size;
windowBounds = Rectangle.Empty;
for (int i = 0; i < items.Count; i++) {
ControlItem item = (ControlItem)items[i];
Rectangle iconBounds = item.GetIconBounds(size);
if (windowBounds.IsEmpty)
windowBounds = iconBounds;
else
windowBounds = Rectangle.Union(windowBounds, iconBounds);
}
Region windowRegion = new Region(new Rectangle(0, 0, 0, 0));
IntPtr windowRegionHandle = IntPtr.Zero;
try {
for (int i = 0; i < items.Count; i++) {
ControlItem item = (ControlItem)items[i];
Rectangle iconBounds = item.GetIconBounds(size);
iconBounds.X -= windowBounds.X;
iconBounds.Y -= windowBounds.Y;
bool showIcon = true;
if (!item.ToolTipShown) {
switch (provider.BlinkStyle) {
case ErrorBlinkStyle.NeverBlink:
// always show icon
break;
case ErrorBlinkStyle.BlinkIfDifferentError:
showIcon = (item.BlinkPhase == 0) || (item.BlinkPhase > 0 && (item.BlinkPhase & 1) == (i & 1));
break;
case ErrorBlinkStyle.AlwaysBlink:
showIcon = ((i & 1) == 0) == provider.showIcon;
break;
}
}
if (showIcon)
{
iconRegion.Region.Translate(iconBounds.X, iconBounds.Y);
windowRegion.Union(iconRegion.Region);
iconRegion.Region.Translate(-iconBounds.X, -iconBounds.Y);
}
if (tipWindow != null) {
NativeMethods.TOOLINFO_T toolInfo = new NativeMethods.TOOLINFO_T();
toolInfo.cbSize = Marshal.SizeOf(toolInfo);
toolInfo.hwnd = Handle;
toolInfo.uId = item.Id;
toolInfo.lpszText = item.Error;
toolInfo.rect = NativeMethods.RECT.FromXYWH(iconBounds.X, iconBounds.Y, iconBounds.Width, iconBounds.Height);
toolInfo.uFlags = NativeMethods.TTF_SUBCLASS;
if (provider.RightToLeft) {
toolInfo.uFlags |= NativeMethods.TTF_RTLREADING;
}
UnsafeNativeMethods.SendMessage(new HandleRef(tipWindow, tipWindow.Handle), NativeMethods.TTM_SETTOOLINFO, 0, toolInfo);
}
if (timerCaused && item.BlinkPhase > 0) {
item.BlinkPhase--;
}
}
if (timerCaused) {
provider.showIcon = !provider.showIcon;
}
DeviceContext dc = null;
dc = DeviceContext.FromHwnd(this.Handle);
try {
CreateMirrorDC(dc.Hdc, windowBounds.Width);
Graphics graphics = Graphics.FromHdcInternal(mirrordc.Hdc);
try {
windowRegionHandle = windowRegion.GetHrgn(graphics);
System.Internal.HandleCollector.Add(windowRegionHandle, NativeMethods.CommonHandles.GDI);
}
finally {
graphics.Dispose();
RestoreMirrorDC();
}
if (UnsafeNativeMethods.SetWindowRgn(new HandleRef(this, Handle), new HandleRef(windowRegion, windowRegionHandle), true) != 0) {
//The HWnd owns the region.
windowRegionHandle = IntPtr.Zero;
}
}
finally {
if (dc != null) {
dc.Dispose();
}
}
}
finally {
windowRegion.Dispose();
if (windowRegionHandle != IntPtr.Zero) {
SafeNativeMethods.DeleteObject(new HandleRef(null, windowRegionHandle));
}
}
SafeNativeMethods.SetWindowPos(new HandleRef(this, Handle), NativeMethods.HWND_TOP, windowBounds.X, windowBounds.Y,
windowBounds.Width, windowBounds.Height, NativeMethods.SWP_NOACTIVATE);
SafeNativeMethods.InvalidateRect(new HandleRef(this, Handle), null, false);
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ErrorWindow.WndProc"]/*' />
/// <devdoc>
/// Called when the error window gets a windows message.
/// </devdoc>
protected override void WndProc(ref Message m) {
switch (m.Msg) {
case NativeMethods.WM_NOTIFY:
NativeMethods.NMHDR nmhdr = (NativeMethods.NMHDR)m.GetLParam(typeof(NativeMethods.NMHDR));
if (nmhdr.code == NativeMethods.TTN_SHOW || nmhdr.code == NativeMethods.TTN_POP)
{
OnToolTipVisibilityChanging(nmhdr.idFrom, nmhdr.code == NativeMethods.TTN_SHOW);
}
break;
case NativeMethods.WM_ERASEBKGND:
break;
case NativeMethods.WM_PAINT:
OnPaint(ref m);
break;
default:
base.WndProc(ref m);
break;
}
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ControlItem"]/*' />
/// <devdoc>
/// There is one ControlItem for each control that the ErrorProvider is
/// is tracking state for. It contains the values of all the extender
/// properties.
/// </devdoc>
internal class ControlItem {
//
// FIELDS
//
string error;
Control control;
ErrorWindow window;
ErrorProvider provider;
int blinkPhase;
IntPtr id;
int iconPadding;
bool toolTipShown;
ErrorIconAlignment iconAlignment;
const int startingBlinkPhase = 10; // cause we want to blink 5 times
//
// CONSTRUCTORS
//
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ControlItem.ControlItem"]/*' />
/// <devdoc>
/// Construct the item with its associated control, provider, and
/// a unique ID. The ID is used for the tooltip ID.
/// </devdoc>
public ControlItem(ErrorProvider provider, Control control, IntPtr id) {
this.toolTipShown = false;
this.iconAlignment = defaultIconAlignment;
this.error = String.Empty;
this.id = id;
this.control = control;
this.provider = provider;
this.control.HandleCreated += new EventHandler(OnCreateHandle);
this.control.HandleDestroyed += new EventHandler(OnDestroyHandle);
this.control.LocationChanged += new EventHandler(OnBoundsChanged);
this.control.SizeChanged += new EventHandler(OnBoundsChanged);
this.control.VisibleChanged += new EventHandler(OnParentVisibleChanged);
this.control.ParentChanged += new EventHandler(OnParentVisibleChanged);
}
public void Dispose() {
if (control != null) {
control.HandleCreated -= new EventHandler(OnCreateHandle);
control.HandleDestroyed -= new EventHandler(OnDestroyHandle);
control.LocationChanged -= new EventHandler(OnBoundsChanged);
control.SizeChanged -= new EventHandler(OnBoundsChanged);
control.VisibleChanged -= new EventHandler(OnParentVisibleChanged);
control.ParentChanged -= new EventHandler(OnParentVisibleChanged);
}
error = string.Empty;
}
//
// PROPERTIES
//
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ControlItem.Id"]/*' />
/// <devdoc>
/// Returns the unique ID for this control. The ID used as the tooltip ID.
/// </devdoc>
public IntPtr Id {
get {
return id;
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ControlItem.BlinkPhase"]/*' />
/// <devdoc>
/// Returns or set the phase of blinking that this control is currently
/// in. If zero, the control is not blinking. If odd, then the control
/// is blinking, but invisible. If even, the control is blinking and
/// currently visible. Each time the blink timer fires, this value is
/// reduced by one (until zero), thus causing the error icon to appear
/// or disappear.
/// </devdoc>
public int BlinkPhase {
get {
return blinkPhase;
}
set {
blinkPhase = value;
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ControlItem.IconPadding"]/*' />
/// <devdoc>
/// Returns or sets the icon padding for the control.
/// </devdoc>
public int IconPadding {
get {
return iconPadding;
}
set {
if (iconPadding != value) {
iconPadding = value;
UpdateWindow();
}
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ControlItem.Error"]/*' />
/// <devdoc>
/// Returns or sets the error description string for the control.
/// </devdoc>
public string Error {
get {
return error;
}
set {
if (value == null) {
value = "";
}
// if the error is the same and the blinkStyle is not AlwaysBlink, then
// we should not add the error and not start blinking.
if (error.Equals(value) && provider.BlinkStyle != ErrorBlinkStyle.AlwaysBlink) {
return;
}
bool adding = error.Length == 0;
error = value;
if (value.Length == 0) {
RemoveFromWindow();
}
else {
if (adding) {
AddToWindow();
}
else {
if (provider.BlinkStyle != ErrorBlinkStyle.NeverBlink) {
StartBlinking();
}
else {
UpdateWindow();
}
}
}
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ControlItem.IconAlignment"]/*' />
/// <devdoc>
/// Returns or sets the location of the error icon for the control.
/// </devdoc>
public ErrorIconAlignment IconAlignment {
get {
return iconAlignment;
}
set {
if (iconAlignment != value) {
//valid values are 0x0 to 0x5
if (!ClientUtils.IsEnumValid(value, (int)value, (int)ErrorIconAlignment.TopLeft, (int)ErrorIconAlignment.BottomRight))
{
throw new InvalidEnumArgumentException("value", (int)value, typeof(ErrorIconAlignment));
}
iconAlignment = value;
UpdateWindow();
}
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ControlItem.ToolTipShown"]/*' />
/// <devdoc>
/// Returns true if the tooltip for this control item is currently shown.
/// </devdoc>
public bool ToolTipShown
{
get {
return this.toolTipShown;
}
set {
this.toolTipShown = value;
}
}
internal ErrorIconAlignment RTLTranslateIconAlignment(ErrorIconAlignment align) {
if (provider.RightToLeft) {
switch (align) {
case ErrorIconAlignment.TopLeft:
return ErrorIconAlignment.TopRight;
case ErrorIconAlignment.MiddleLeft:
return ErrorIconAlignment.MiddleRight;
case ErrorIconAlignment.BottomLeft:
return ErrorIconAlignment.BottomRight;
case ErrorIconAlignment.TopRight:
return ErrorIconAlignment.TopLeft;
case ErrorIconAlignment.MiddleRight:
return ErrorIconAlignment.MiddleLeft;
case ErrorIconAlignment.BottomRight:
return ErrorIconAlignment.BottomLeft;
default:
Debug.Fail("Unknown ErrorIconAlignment value");
return align;
}
}
else {
return align;
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ControlItem.GetIconBounds"]/*' />
/// <devdoc>
/// Returns the location of the icon in the same coordinate system as
/// the control being extended. The size passed in is the size of
/// the icon.
/// </devdoc>
internal Rectangle GetIconBounds(Size size) {
int x = 0;
int y = 0;
switch (RTLTranslateIconAlignment(IconAlignment)) {
case ErrorIconAlignment.TopLeft:
case ErrorIconAlignment.MiddleLeft:
case ErrorIconAlignment.BottomLeft:
x = control.Left - size.Width - iconPadding;
break;
case ErrorIconAlignment.TopRight:
case ErrorIconAlignment.MiddleRight:
case ErrorIconAlignment.BottomRight:
x = control.Right + iconPadding;
break;
}
switch (IconAlignment) {
case ErrorIconAlignment.TopLeft:
case ErrorIconAlignment.TopRight:
y = control.Top;
break;
case ErrorIconAlignment.MiddleLeft:
case ErrorIconAlignment.MiddleRight:
y = control.Top + (control.Height - size.Height) / 2;
break;
case ErrorIconAlignment.BottomLeft:
case ErrorIconAlignment.BottomRight:
y = control.Bottom - size.Height;
break;
}
return new Rectangle(x, y, size.Width, size.Height);
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ControlItem.UpdateWindow"]/*' />
/// <devdoc>
/// If this control's error icon has been added to the error
/// window, then update the window state because some property
/// has changed.
/// </devdoc>
void UpdateWindow() {
if (window != null) {
window.Update(false /*timerCaused*/);
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ControlItem.StartBlinking"]/*' />
/// <devdoc>
/// If this control's error icon has been added to the error
/// window, then start blinking the error window. The blink
/// count
/// </devdoc>
void StartBlinking() {
if (window != null) {
BlinkPhase = startingBlinkPhase;
window.StartBlinking();
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ControlItem.AddToWindow"]/*' />
/// <devdoc>
/// Add this control's error icon to the error window.
/// </devdoc>
void AddToWindow() {
// if we are recreating the control, then add the control.
if (window == null &&
(control.Created || control.RecreatingHandle) &&
control.Visible && control.ParentInternal != null &&
error.Length > 0) {
window = provider.EnsureErrorWindow(control.ParentInternal);
window.Add(this);
// Make sure that we blink if the style is set to AlwaysBlink or BlinkIfDifferrentError
if (provider.BlinkStyle != ErrorBlinkStyle.NeverBlink)
{
StartBlinking();
}
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ControlItem.RemoveFromWindow"]/*' />
/// <devdoc>
/// Remove this control's error icon from the error window.
/// </devdoc>
void RemoveFromWindow() {
if (window != null) {
window.Remove(this);
window = null;
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ControlItem.OnBoundsChanged"]/*' />
/// <devdoc>
/// This is called when a property on the control is changed.
/// </devdoc>
void OnBoundsChanged(Object sender, EventArgs e) {
UpdateWindow();
}
void OnParentVisibleChanged(Object sender, EventArgs e) {
this.BlinkPhase = 0;
RemoveFromWindow();
AddToWindow();
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ControlItem.OnCreateHandle"]/*' />
/// <devdoc>
/// This is called when the control's handle is created.
/// </devdoc>
void OnCreateHandle(Object sender, EventArgs e) {
AddToWindow();
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.ControlItem.OnDestroyHandle"]/*' />
/// <devdoc>
/// This is called when the control's handle is destroyed.
/// </devdoc>
void OnDestroyHandle(Object sender, EventArgs e) {
RemoveFromWindow();
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.IconRegion"]/*' />
/// <devdoc>
/// This represents the HRGN of icon. The region is calculate from the icon's mask.
/// </devdoc>
internal class IconRegion {
//
// FIELDS
//
Region region;
Icon icon;
//
// CONSTRUCTORS
//
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.IconRegion.IconRegion"]/*' />
/// <devdoc>
/// Constructor that takes an Icon and extracts its 16x16 version.
/// </devdoc>
[ResourceExposure(ResourceScope.Machine)]
[ResourceConsumption(ResourceScope.Machine)]
public IconRegion(Icon icon) {
this.icon = new Icon(icon, 16, 16);
}
//
// PROPERTIES
//
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.IconRegion.IconHandle"]/*' />
/// <devdoc>
/// Returns the handle of the icon.
/// </devdoc>
public IntPtr IconHandle {
get {
return icon.Handle;
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.IconRegion.Region"]/*' />
/// <devdoc>
/// Returns the handle of the region.
/// </devdoc>
public Region Region {
[ResourceExposure(ResourceScope.Process)]
[ResourceConsumption(ResourceScope.Process | ResourceScope.Machine, ResourceScope.Process | ResourceScope.Machine)]
get {
if (region == null) {
region = new Region(new Rectangle(0,0,0,0));
IntPtr mask = IntPtr.Zero;
try {
Size size = icon.Size;
Bitmap bitmap = icon.ToBitmap();
bitmap.MakeTransparent();
mask = ControlPaint.CreateHBitmapTransparencyMask(bitmap);
bitmap.Dispose();
// It is been observed that users can use non standard size icons (not a 16 bit multiples for width and height)
// and GetBitmapBits method allocate bytes in multiple of 16 bits for each row. Following calculation is to get right width in bytes.
int bitmapBitsAllocationSize = 16;
//if width is not multiple of 16, we need to allocate BitmapBitsAllocationSize for remaining bits.
int widthInBytes = 2 * ((size.Width +15) / bitmapBitsAllocationSize); // its in bytes.
byte[] bits = new byte[widthInBytes * size.Height];
SafeNativeMethods.GetBitmapBits(new HandleRef(null, mask), bits.Length, bits);
for (int y = 0; y < size.Height; y++) {
for (int x = 0; x < size.Width; x++) {
// see if bit is set in mask. bits in byte are reversed. 0 is black (set).
if ((bits[y * widthInBytes + x / 8] & (1 << (7 - (x % 8)))) == 0) {
region.Union(new Rectangle(x, y, 1, 1));
}
}
}
region.Intersect(new Rectangle(0, 0, size.Width, size.Height));
}
finally {
if (mask != IntPtr.Zero)
SafeNativeMethods.DeleteObject(new HandleRef(null, mask));
}
}
return region;
}
}
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.IconRegion.Size"]/*' />
/// <devdoc>
/// Return the size of the icon.
/// </devdoc>
public Size Size {
get {
return icon.Size;
}
}
//
// METHODS
//
/// <include file='doc\ErrorProvider.uex' path='docs/doc[@for="ErrorProvider.IconRegion.Dispose"]/*' />
/// <devdoc>
/// Release any resources held by this Object.
/// </devdoc>
public void Dispose() {
if (region != null) {
region.Dispose();
region = null;
}
icon.Dispose();
}
}
}
}
|