public class ContainerControl : ScrollableControl, IContainerControl {
private Control active_control;
private Control unvalidated_control;
+ private ArrayList pending_validation_chain;
// This is an internal hack that allows some container controls
// to not auto select their child when they are activated
#if NET_2_0
private SizeF auto_scale_dimensions;
private AutoScaleMode auto_scale_mode;
+ private bool auto_scale_mode_set;
+ private bool auto_scale_pending;
+ private bool is_auto_scaling;
#endif
+ internal bool validation_failed; //track whether validation was cancelled by a validating control
+
#region Public Constructors
public ContainerControl() {
active_control = null;
ControlRemoved += new ControlEventHandler(OnControlRemoved);
#if NET_2_0
auto_scale_dimensions = SizeF.Empty;
- auto_scale_mode = AutoScaleMode.None;
+ auto_scale_mode = AutoScaleMode.Inherit;
#endif
}
#endregion // Public Constructors
}
set {
- if (value==null || (active_control == value)) {
+ if (value==null || (active_control == value && active_control.Focused)) {
return;
}
// Fire the enter and leave events if possible
Form form = FindForm ();
- if (form != null) {
- Control active = GetMostDeeplyNestedActiveControl (form);
- Control common_container = GetCommonContainer (active, value);
- ArrayList chain = new ArrayList ();
- ArrayList validation_chain = new ArrayList ();
- Control walk = active;
-
- // we split this up into three steps:
- // 1. walk up the tree (if we need to) to our root, firing leave events.
- // 2. validate.
- // 3. walk down the tree (if we need to), firing enter events.
-
- // "our root" is either the common container of the current active
- // control and the new active control, or the current active control,
- // or the new active control. That is, it's either one of these three
- // configurations:
-
- // (1) root (2) new (3) current
- // / \ / \ / \
- // ... ... ... ... ... ...
- // / \ / \
- // current new current new
-
-
- // note (3) doesn't require any upward walking, and no leave events are generated.
- // (2) doesn't require any downward walking, and no enter events are generated.
-
- // as we walk up the tree, we generate a list of all controls which cause
- // validation. After firing the leave events, we invoke (in order starting from
- // the most deeply nested) their Validating event. If one sets CancelEventArgs.Cancel
- // to true, we ignore the control the user wanted to set ActiveControl to, and use
- // the Validating control instead.
-
- bool fire_enter = true;
- Control root = common_container;
-
- // Generate the leave messages
- while (walk != common_container) {
- if (walk == value) {
- root = value;
- fire_enter = false;
- break;
- }
- walk.FireLeave ();
- /* clear our idea of the active control as we go back up */
- if (walk is ContainerControl)
- ((ContainerControl)walk).active_control = null;
+ Control active = GetMostDeeplyNestedActiveControl (form == null ? this : form);
+ Control common_ancestor = GetCommonContainer (active, value);
+ ArrayList chain = new ArrayList ();
+ ArrayList validation_chain = new ArrayList ();
+ Control walk = active;
+
+ // we split this up into three steps:
+ // 1. walk up the tree (if we need to) to our root, firing leave events.
+ // 2. validate.
+ // 3. walk down the tree (if we need to), firing enter events.
+
+ // "our root" is either the common ancestor of the current active
+ // control and the new active control, or the current active control,
+ // or the new active control. That is, it's either one of these three
+ // configurations:
+
+ // (1) root (2) new (3) current
+ // / \ / \ / \
+ // ... ... ... ... ... ...
+ // / \ / \
+ // current new current new
+
+
+ // note (3) doesn't require any upward walking, and no leave events are generated.
+ // (2) doesn't require any downward walking, and no enter events are generated.
+
+ // as we walk up the tree, we generate a list of all controls which cause
+ // validation. After firing the leave events, we invoke (in order starting from
+ // the most deeply nested) their Validating event. If one sets CancelEventArgs.Cancel
+ // to true, we ignore the control the user wanted to set ActiveControl to, and use
+ // the Validating control instead.
+
+ bool fire_enter = true;
+ Control root = common_ancestor;
+
+ active_control = value;
+
+ // Generate the leave messages
+ while (walk != common_ancestor && walk != null) {
+ if (walk == value) {
+ root = value;
+ fire_enter = false;
+ break;
+ }
+ walk.FireLeave ();
+ /* clear our idea of the active control as we go back up */
+ if (walk is ContainerControl)
+ ((ContainerControl)walk).active_control = null;
+
+ if (walk.CausesValidation)
+ validation_chain.Add (walk);
+ walk = walk.Parent;
+ }
+
+ // Validation can be postponed due to all the controls
+ // in the enter chain not causing validation. If we don't have any
+ // enter chain, it means that the selected control is a child and then
+ // we need to validate the controls anyway
+ bool postpone_validation;
+ Control topmost_under_root = null; // topmost under root, in the *enter* chain
+ if (value == root)
+ postpone_validation = false;
+ else {
+ postpone_validation = true;
+ walk = value;
+ while (walk != root && walk != null) {
if (walk.CausesValidation)
- validation_chain.Add (walk);
+ postpone_validation = false;
+ topmost_under_root = walk;
walk = walk.Parent;
}
+ }
- for (int i = 0; i < validation_chain.Count; i ++) {
- if (!ValidateControl ((Control)validation_chain[i])) {
- value = (Control)validation_chain[i];
- fire_enter = true;
- }
+ Control failed_validation_control = PerformValidation (form == null ? this : form, postpone_validation,
+ validation_chain, topmost_under_root);
+ if (failed_validation_control != null) {
+ active_control = value = failed_validation_control;
+ fire_enter = true;
+ }
+
+ if (fire_enter) {
+ walk = value;
+ while (walk != root && walk != null) {
+ chain.Add (walk);
+ walk = walk.Parent;
}
- if (fire_enter) {
- walk = value;
- while (walk != root) {
- chain.Add (walk);
- walk = walk.Parent;
- }
+ if (root != null && walk == root && !(root is ContainerControl))
+ chain.Add (walk);
- for (int i = chain.Count - 1; i >= 0; i--) {
- walk = (Control) chain [i];
- walk.FireEnter ();
- }
+ for (int i = chain.Count - 1; i >= 0; i--) {
+ walk = (Control) chain [i];
+ walk.FireEnter ();
}
+ }
- walk = this;
- while (walk != null) {
- if (walk.Parent is ContainerControl)
- ((ContainerControl)walk.Parent).active_control = walk;
- walk = walk.Parent;
+ walk = this;
+ Control ctl = this;
+ while (walk != null) {
+ if (walk.Parent is ContainerControl) {
+ ((ContainerControl) walk.Parent).active_control = ctl;
+ ctl = walk.Parent;
}
+ walk = walk.Parent;
}
- active_control = value;
-
if (this is Form)
CheckAcceptButton();
// Scroll control into view
ScrollControlIntoView(active_control);
-
+
+
+ walk = this;
+ ctl = this;
+ while (walk != null) {
+ if (walk.Parent is ContainerControl) {
+ ctl = walk.Parent;
+ }
+ walk = walk.Parent;
+ }
+
// Let the control know it's selected
- SendControlFocus (value);
+ if (ctl.InternalContainsFocus)
+ SendControlFocus (active_control);
+ }
+ }
+
+ // Return the control where validation failed, and null otherwise
+ // @topmost_under_root is the control under the root in the enter chain, if any
+ //
+ // The process of validation happens as described:
+ //
+ // 1. Iterate over the nodes in the enter chain (walk down), looking for any node
+ // causing validation. If we can't find any, don't validate the current validation chain, but postpone it,
+ // saving it in the top_container.pending_validation_chain field, since we need to keep track of it later.
+ // If we have a previous pending_validation_chain, add the new nodes, making sure they are not repeated
+ // (this is computed in ActiveControl and we receive if as the postpone_validation parameter).
+ //
+ // 2. If we found at least one node causing validation in the enter chain, try to validate the elements
+ // in pending_validation_chain, if any. Then continue with the ones receives as parameters.
+ //
+ // 3. Return null if all the validation performed successfully, and return the control where the validation
+ // failed otherwise.
+ //
+ private Control PerformValidation (ContainerControl top_container, bool postpone_validation, ArrayList validation_chain,
+ Control topmost_under_root)
+ {
+ validation_failed = false;
+
+ if (postpone_validation) {
+ AddValidationChain (top_container, validation_chain);
+ return null;
+ }
+
+ // if not null, pending chain has always one element or more
+ if (top_container.pending_validation_chain != null) {
+ // if the topmost node in the enter chain is exactly the topmost
+ // int the validation chain, remove it, as .net does
+ int last_idx = top_container.pending_validation_chain.Count - 1;
+ if (topmost_under_root == top_container.pending_validation_chain [last_idx])
+ top_container.pending_validation_chain.RemoveAt (last_idx);
+
+ AddValidationChain (top_container, validation_chain);
+ validation_chain = top_container.pending_validation_chain;
+ top_container.pending_validation_chain = null;
}
+
+ for (int i = 0; i < validation_chain.Count; i ++) {
+ if (!ValidateControl ((Control)validation_chain[i])) {
+ validation_failed = true;
+ return (Control)validation_chain[i];
+ }
+ }
+
+ return null;
}
+ // Add the elements in validation_chain to the pending validation chain stored in top_container
+ private void AddValidationChain (ContainerControl top_container, ArrayList validation_chain)
+ {
+ if (validation_chain.Count == 0)
+ return;
+
+ if (top_container.pending_validation_chain == null || top_container.pending_validation_chain.Count == 0) {
+ top_container.pending_validation_chain = validation_chain;
+ return;
+ }
+
+ foreach (Control c in validation_chain)
+ if (!top_container.pending_validation_chain.Contains (c))
+ top_container.pending_validation_chain.Add (c);
+ }
+
private bool ValidateControl (Control c)
{
CancelEventArgs e = new CancelEventArgs ();
}
set {
- auto_scale_dimensions = value;
+ if (auto_scale_dimensions != value) {
+ auto_scale_dimensions = value;
+
+ PerformAutoScale ();
+ }
}
}
protected SizeF AutoScaleFactor {
get {
- if (auto_scale_dimensions.IsEmpty) {
- return new SizeF(1f, 1f);
- }
+ if (auto_scale_dimensions.IsEmpty)
+ return new SizeF (1f, 1f);
+
return new SizeF(CurrentAutoScaleDimensions.Width / auto_scale_dimensions.Width,
CurrentAutoScaleDimensions.Height / auto_scale_dimensions.Height);
}
}
- [MonoTODO("Call scaling method")]
[Browsable (false)]
[EditorBrowsable (EditorBrowsableState.Advanced)]
[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
return auto_scale_mode;
}
set {
+ if (this is Form)
+ (this as Form).AutoScale = false;
+
if (auto_scale_mode != value) {
auto_scale_mode = value;
- // Trigger scaling
+ if (auto_scale_mode_set)
+ auto_scale_dimensions = SizeF.Empty;
+
+ auto_scale_mode_set = true;
+
+ PerformAutoScale ();
}
}
}
}
#if NET_2_0
- [MonoTODO("Revisit when System.Drawing.GDI.WindowsGraphics.GetTextMetrics is done or come up with other cross-plat avg. font width calc method")]
[Browsable (false)]
[EditorBrowsable (EditorBrowsableState.Advanced)]
public SizeF CurrentAutoScaleDimensions {
get {
switch(auto_scale_mode) {
- case AutoScaleMode.Dpi: {
- Bitmap bmp;
- Graphics g;
- SizeF size;
-
- bmp = new Bitmap(1, 1, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
- g = Graphics.FromImage(bmp);
- size = new SizeF(g.DpiX, g.DpiY);
- g.Dispose();
- bmp.Dispose();
- return size;
- }
-
- case AutoScaleMode.Font: {
- // http://msdn2.microsoft.com/en-us/library/system.windows.forms.containercontrol.currentautoscaledimensions(VS.80).aspx
- // Implement System.Drawing.GDI.WindowsGraphics.GetTextMetrics first...
- break;
- }
+ case AutoScaleMode.Dpi:
+ return TextRenderer.GetDpi ();
+
+ case AutoScaleMode.Font:
+ Size s = TextRenderer.MeasureText ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890", Font);
+ int width = (int)Math.Round ((float)s.Width / 62f);
+
+ return new SizeF (width, s.Height);
}
return auto_scale_dimensions;
#endregion // Public Instance Properties
#region Protected Instance Methods
+#if NET_2_0
+ protected override bool CanEnableIme {
+ get { return false; }
+ }
+#endif
protected override CreateParams CreateParams {
get {
return base.CreateParams;
#endregion // Public Instance Methods
#region Public Instance Methods
- [MonoTODO]
+#if NET_2_0
+ internal void PerformAutoScale (bool called_by_scale)
+ {
+ if ((AutoScaleMode == AutoScaleMode.Inherit) && !called_by_scale)
+ return;
+
+ if ((layout_suspended > 0) && !called_by_scale) {
+ auto_scale_pending = true;
+ return;
+ }
+ // Set this first so we don't get called again from
+ // PerformDelayedAutoScale after ResumeLayout
+ auto_scale_pending = false;
+
+ SizeF factor = AutoScaleFactor;
+ if (AutoScaleMode == AutoScaleMode.Inherit) {
+ ContainerControl cc = FindContainer (this.Parent);
+ if (cc != null)
+ factor = cc.AutoScaleFactor;
+ }
+ if (factor != new SizeF (1F, 1F)) {
+ is_auto_scaling = true;
+ SuspendLayout ();
+ Scale (factor);
+ ResumeLayout (false);
+ is_auto_scaling = false;
+ }
+
+ auto_scale_dimensions = CurrentAutoScaleDimensions;
+ }
+
+ public void PerformAutoScale ()
+ {
+ PerformAutoScale (false);
+ }
+
+ internal void PerformDelayedAutoScale ()
+ {
+ if (auto_scale_pending)
+ PerformAutoScale ();
+ }
+
+ internal bool IsAutoScaling {
+ get { return is_auto_scaling; }
+ }
+#endif
+
+ [MonoTODO ("Stub, not implemented")]
static bool ValidateWarned;
public bool Validate() {
//throw new NotImplementedException();
return true;
}
+#if NET_2_0
+ public bool Validate (bool checkAutoValidate)
+ {
+ if ((checkAutoValidate && (AutoValidate != AutoValidate.Disable)) || !checkAutoValidate)
+ return Validate ();
+
+ return true;
+ }
+
+ [Browsable (false)]
+ [EditorBrowsable (EditorBrowsableState.Never)]
+ public virtual bool ValidateChildren ()
+ {
+ return ValidateChildren (ValidationConstraints.Selectable);
+ }
+
+ [Browsable (false)]
+ [EditorBrowsable (EditorBrowsableState.Never)]
+ public virtual bool ValidateChildren (ValidationConstraints validationConstraints)
+ {
+ bool recurse = !((validationConstraints & ValidationConstraints.ImmediateChildren) == ValidationConstraints.ImmediateChildren);
+
+ foreach (Control control in Controls)
+ if (!ValidateNestedControls (control, validationConstraints, recurse))
+ return false;
+
+ return true;
+ }
+#endif
+
bool IContainerControl.ActivateControl(Control control) {
return Select(control);
}
OnBindingContextChanged (EventArgs.Empty);
}
+#if NET_2_0
+ protected override bool ProcessCmdKey (ref Message msg, Keys keyData)
+ {
+ if (ToolStripManager.ProcessCmdKey (ref msg, keyData) == true)
+ return true;
+
+ return base.ProcessCmdKey (ref msg, keyData);
+ }
+#endif
+
[EditorBrowsable (EditorBrowsableState.Advanced)]
protected override bool ProcessDialogChar(char charCode) {
if (GetTopLevel()) {
switch (key) {
case Keys.Tab: {
- if (ProcessTabKey((Control.ModifierKeys & Keys.Shift) == 0)) {
- return true;
+ if ((keyData & (Keys.Alt | Keys.Control)) == Keys.None) {
+ if (ProcessTabKey ((Control.ModifierKeys & Keys.Shift) == 0)) {
+ return true;
+ }
}
break;
}
c = GetNextControl(c, true);
if (c != null) {
// This is stupid. I want to be able to call c.ProcessMnemonic directly
- if (c.CanSelect && c.ProcessControlMnemonic(charCode)) {
+ if (c.ProcessControlMnemonic(charCode)) {
return(true);
}
continue;
protected override void WndProc(ref Message m) {
switch ((Msg) m.Msg) {
- case Msg.WM_LBUTTONDOWN:
- OnMouseDown (new MouseEventArgs (FromParamToMouseButtons ((int) m.WParam.ToInt32()),
- mouse_clicks, LowOrder ((int) m.LParam.ToInt32 ()),
- HighOrder ((int) m.LParam.ToInt32 ()), 0));
- break;
-
case Msg.WM_SETFOCUS:
if (active_control != null)
Select (active_control);
#endregion // Protected Instance Methods
#region Internal Methods
+ internal void ChildControlRemoved (Control control)
+ {
+ ContainerControl top_container = FindForm ();
+ if (top_container == null)
+ top_container = this;
+
+ // Remove controls -as well as any sub control- that was in the pending validation chain
+ ArrayList pending_validation_chain = top_container.pending_validation_chain;
+ if (pending_validation_chain != null) {
+ RemoveChildrenFromValidation (pending_validation_chain, control);
+
+ if (pending_validation_chain.Count == 0)
+ top_container.pending_validation_chain = null;
+ }
+
+ if (control == active_control || control.Contains (active_control)) {
+ SelectNextControl (this, true, true, true, true);
+ if (control == active_control || control.Contains (active_control)) {
+ active_control = null;
+ }
+ }
+ }
+
+ // Check that this control (or any child) is included in the pending validation chain
+ bool RemoveChildrenFromValidation (ArrayList validation_chain, Control c)
+ {
+ if (RemoveFromValidationChain (validation_chain, c))
+ return true;
+
+ foreach (Control child in c.Controls)
+ if (RemoveChildrenFromValidation (validation_chain, child))
+ return true;
+
+ return false;
+ }
+
+ // Remove the top most control in the pending validation chain, as well as any children there,
+ // taking advantage of the fact that the chain is in reverse order of the control's hierarchy
+ bool RemoveFromValidationChain (ArrayList validation_chain, Control c)
+ {
+ int idx = validation_chain.IndexOf (c);
+ if (idx > -1) {
+ pending_validation_chain.RemoveAt (idx--);
+ return true;
+ }
+
+ return false;
+ }
+
internal virtual void CheckAcceptButton()
{
// do nothing here, only called if it is a Form
}
+
+#if NET_2_0
+ private bool ValidateNestedControls (Control c, ValidationConstraints constraints, bool recurse)
+ {
+ bool validate_result = true;
+
+ if (!c.CausesValidation)
+ validate_result = true;
+ else if (!ValidateThisControl (c, constraints))
+ validate_result = true;
+ else if (!ValidateControl (c))
+ validate_result = false;
+
+ if (recurse)
+ foreach (Control control in c.Controls)
+ if (!ValidateNestedControls (control, constraints, recurse))
+ return false;
+
+ return validate_result;
+ }
+
+ private bool ValidateThisControl (Control c, ValidationConstraints constraints)
+ {
+ if (constraints == ValidationConstraints.None)
+ return true;
+
+ if ((constraints & ValidationConstraints.Enabled) == ValidationConstraints.Enabled && !c.Enabled)
+ return false;
+
+ if ((constraints & ValidationConstraints.Selectable) == ValidationConstraints.Selectable && !c.GetStyle (ControlStyles.Selectable))
+ return false;
+
+ if ((constraints & ValidationConstraints.TabStop) == ValidationConstraints.TabStop && !c.TabStop)
+ return false;
+
+ if ((constraints & ValidationConstraints.Visible) == ValidationConstraints.Visible && !c.Visible)
+ return false;
+
+ return true;
+ }
+#endif
#endregion // Internal Methods
#if NET_2_0
protected override void OnFontChanged (EventArgs e)
{
base.OnFontChanged (e);
+
+ if (AutoScaleMode == AutoScaleMode.Font)
+ PerformAutoScale ();
}
+ protected override void OnLayout (LayoutEventArgs e)
+ {
+ base.OnLayout (e);
+ }
+
AutoValidate auto_validate = AutoValidate.Inherit;
+
+ [Browsable (false)]
+ [AmbientValue (AutoValidate.Inherit)]
+ [EditorBrowsable (EditorBrowsableState.Never)]
public virtual AutoValidate AutoValidate {
get {
return auto_validate;
}
}
+ internal bool ShouldSerializeAutoValidate ()
+ {
+ return this.AutoValidate != AutoValidate.Inherit;
+ }
+
static object OnValidateChanged = new object ();
protected virtual void OnAutoValidateChanged (EventArgs e)
eh (this, e);
}
+ [Browsable (false)]
+ [EditorBrowsable (EditorBrowsableState.Never)]
public event EventHandler AutoValidateChanged {
add { Events.AddHandler (OnValidateChanged, value); }
remove { Events.RemoveHandler (OnValidateChanged, value); }