1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2004 Novell, Inc.
23 // Peter Bartok pbartok@novell.com
28 using System.Collections;
29 using System.ComponentModel;
30 using System.ComponentModel.Design;
32 using System.Runtime.InteropServices;
34 namespace System.Windows.Forms {
36 [ClassInterface (ClassInterfaceType.AutoDispatch)]
39 public class ContainerControl : ScrollableControl, IContainerControl {
40 private Control active_control;
41 private Control unvalidated_control;
42 private ArrayList pending_validation_chain;
44 // This is an internal hack that allows some container controls
45 // to not auto select their child when they are activated
46 internal bool auto_select_child = true;
48 private SizeF auto_scale_dimensions;
49 private AutoScaleMode auto_scale_mode;
50 private bool auto_scale_mode_set;
51 private bool auto_scale_pending;
52 private bool is_auto_scaling;
55 internal bool validation_failed; //track whether validation was cancelled by a validating control
57 #region Public Constructors
58 public ContainerControl() {
59 active_control = null;
60 unvalidated_control = null;
61 ControlRemoved += new ControlEventHandler(OnControlRemoved);
63 auto_scale_dimensions = SizeF.Empty;
64 auto_scale_mode = AutoScaleMode.Inherit;
67 #endregion // Public Constructors
69 #region Public Instance Properties
71 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
72 public Control ActiveControl {
74 return active_control;
78 if (value==null || (active_control == value && active_control.Focused)) {
82 if (!Contains(value)) {
83 throw new ArgumentException("Cannot activate invisible or disabled control.");
86 // Fire the enter and leave events if possible
87 Form form = FindForm ();
88 Control active = GetMostDeeplyNestedActiveControl (form == null ? this : form);
89 Control common_ancestor = GetCommonContainer (active, value);
90 ArrayList chain = new ArrayList ();
91 ArrayList validation_chain = new ArrayList ();
92 Control walk = active;
94 // we split this up into three steps:
95 // 1. walk up the tree (if we need to) to our root, firing leave events.
97 // 3. walk down the tree (if we need to), firing enter events.
99 // "our root" is either the common ancestor of the current active
100 // control and the new active control, or the current active control,
101 // or the new active control. That is, it's either one of these three
104 // (1) root (2) new (3) current
106 // ... ... ... ... ... ...
108 // current new current new
111 // note (3) doesn't require any upward walking, and no leave events are generated.
112 // (2) doesn't require any downward walking, and no enter events are generated.
114 // as we walk up the tree, we generate a list of all controls which cause
115 // validation. After firing the leave events, we invoke (in order starting from
116 // the most deeply nested) their Validating event. If one sets CancelEventArgs.Cancel
117 // to true, we ignore the control the user wanted to set ActiveControl to, and use
118 // the Validating control instead.
120 bool fire_enter = true;
121 Control root = common_ancestor;
123 active_control = value;
125 // Generate the leave messages
126 while (walk != common_ancestor && walk != null) {
133 /* clear our idea of the active control as we go back up */
134 if (walk is ContainerControl)
135 ((ContainerControl)walk).active_control = null;
137 if (walk.CausesValidation)
138 validation_chain.Add (walk);
143 // Validation can be postponed due to all the controls
144 // in the enter chain not causing validation. If we don't have any
145 // enter chain, it means that the selected control is a child and then
146 // we need to validate the controls anyway
147 bool postpone_validation;
148 Control topmost_under_root = null; // topmost under root, in the *enter* chain
150 postpone_validation = false;
152 postpone_validation = true;
154 while (walk != root && walk != null) {
155 if (walk.CausesValidation)
156 postpone_validation = false;
158 topmost_under_root = walk;
163 Control failed_validation_control = PerformValidation (form == null ? this : form, postpone_validation,
164 validation_chain, topmost_under_root);
165 if (failed_validation_control != null) {
166 active_control = value = failed_validation_control;
172 while (walk != root && walk != null) {
177 if (root != null && walk == root && !(root is ContainerControl))
180 for (int i = chain.Count - 1; i >= 0; i--) {
181 walk = (Control) chain [i];
188 while (walk != null) {
189 if (walk.Parent is ContainerControl) {
190 ((ContainerControl) walk.Parent).active_control = ctl;
199 // Scroll control into view
200 ScrollControlIntoView(active_control);
205 while (walk != null) {
206 if (walk.Parent is ContainerControl) {
212 // Let the control know it's selected
213 if (ctl.InternalContainsFocus)
214 SendControlFocus (active_control);
218 // Return the control where validation failed, and null otherwise
219 // @topmost_under_root is the control under the root in the enter chain, if any
221 // The process of validation happens as described:
223 // 1. Iterate over the nodes in the enter chain (walk down), looking for any node
224 // causing validation. If we can't find any, don't validate the current validation chain, but postpone it,
225 // saving it in the top_container.pending_validation_chain field, since we need to keep track of it later.
226 // If we have a previous pending_validation_chain, add the new nodes, making sure they are not repeated
227 // (this is computed in ActiveControl and we receive if as the postpone_validation parameter).
229 // 2. If we found at least one node causing validation in the enter chain, try to validate the elements
230 // in pending_validation_chain, if any. Then continue with the ones receives as parameters.
232 // 3. Return null if all the validation performed successfully, and return the control where the validation
235 private Control PerformValidation (ContainerControl top_container, bool postpone_validation, ArrayList validation_chain,
236 Control topmost_under_root)
238 validation_failed = false;
240 if (postpone_validation) {
241 AddValidationChain (top_container, validation_chain);
245 // if not null, pending chain has always one element or more
246 if (top_container.pending_validation_chain != null) {
247 // if the topmost node in the enter chain is exactly the topmost
248 // int the validation chain, remove it, as .net does
249 int last_idx = top_container.pending_validation_chain.Count - 1;
250 if (topmost_under_root == top_container.pending_validation_chain [last_idx])
251 top_container.pending_validation_chain.RemoveAt (last_idx);
253 AddValidationChain (top_container, validation_chain);
254 validation_chain = top_container.pending_validation_chain;
255 top_container.pending_validation_chain = null;
258 for (int i = 0; i < validation_chain.Count; i ++) {
259 if (!ValidateControl ((Control)validation_chain[i])) {
260 validation_failed = true;
261 return (Control)validation_chain[i];
268 // Add the elements in validation_chain to the pending validation chain stored in top_container
269 private void AddValidationChain (ContainerControl top_container, ArrayList validation_chain)
271 if (validation_chain.Count == 0)
274 if (top_container.pending_validation_chain == null || top_container.pending_validation_chain.Count == 0) {
275 top_container.pending_validation_chain = validation_chain;
279 foreach (Control c in validation_chain)
280 if (!top_container.pending_validation_chain.Contains (c))
281 top_container.pending_validation_chain.Add (c);
284 private bool ValidateControl (Control c)
286 CancelEventArgs e = new CancelEventArgs ();
288 c.FireValidating (e);
297 private Control GetMostDeeplyNestedActiveControl (ContainerControl container)
299 Control active = container.ActiveControl;
300 while (active is ContainerControl) {
301 if (((ContainerControl)active).ActiveControl == null)
303 active = ((ContainerControl)active).ActiveControl;
308 // Just in a separate method to make debugging a little easier,
309 // should eventually be rolled into ActiveControl setter
310 private Control GetCommonContainer (Control active_control, Control value)
312 Control new_container = null;
313 Control prev_container = active_control;
315 while (prev_container != null) {
316 new_container = value.Parent;
317 while (new_container != null) {
318 if (new_container == prev_container)
319 return new_container;
320 new_container = new_container.Parent;
323 prev_container = prev_container.Parent;
329 internal void SendControlFocus (Control c)
331 if (c.IsHandleCreated) {
332 XplatUI.SetFocus (c.window.Handle);
338 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
339 [EditorBrowsable (EditorBrowsableState.Advanced)]
341 public SizeF AutoScaleDimensions {
343 return auto_scale_dimensions;
347 if (auto_scale_dimensions != value) {
348 auto_scale_dimensions = value;
355 protected SizeF AutoScaleFactor {
357 if (auto_scale_dimensions.IsEmpty)
358 return new SizeF (1f, 1f);
360 return new SizeF(CurrentAutoScaleDimensions.Width / auto_scale_dimensions.Width,
361 CurrentAutoScaleDimensions.Height / auto_scale_dimensions.Height);
367 [EditorBrowsable (EditorBrowsableState.Advanced)]
368 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
369 public AutoScaleMode AutoScaleMode {
371 return auto_scale_mode;
375 (this as Form).AutoScale = false;
377 if (auto_scale_mode != value) {
378 auto_scale_mode = value;
380 if (auto_scale_mode_set)
381 auto_scale_dimensions = SizeF.Empty;
383 auto_scale_mode_set = true;
392 public override BindingContext BindingContext {
394 if (base.BindingContext == null) {
395 base.BindingContext = new BindingContext();
397 return base.BindingContext;
401 base.BindingContext = value;
407 [EditorBrowsable (EditorBrowsableState.Advanced)]
408 public SizeF CurrentAutoScaleDimensions {
410 switch(auto_scale_mode) {
411 case AutoScaleMode.Dpi:
412 return TextRenderer.GetDpi ();
414 case AutoScaleMode.Font:
415 Size s = TextRenderer.MeasureText ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890", Font);
416 int width = (int)Math.Round ((float)s.Width / 62f);
418 return new SizeF (width, s.Height);
421 return auto_scale_dimensions;
427 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
428 public Form ParentForm {
432 parent = this.Parent;
434 while (parent != null) {
435 if (parent is Form) {
438 parent = parent.Parent;
444 #endregion // Public Instance Properties
446 #region Protected Instance Methods
448 protected override bool CanEnableIme {
449 get { return false; }
452 protected override CreateParams CreateParams {
454 return base.CreateParams;
457 #endregion // Public Instance Methods
459 #region Public Instance Methods
461 internal void PerformAutoScale (bool called_by_scale)
463 if ((AutoScaleMode == AutoScaleMode.Inherit) && !called_by_scale)
466 if ((layout_suspended > 0) && !called_by_scale) {
467 auto_scale_pending = true;
470 // Set this first so we don't get called again from
471 // PerformDelayedAutoScale after ResumeLayout
472 auto_scale_pending = false;
474 SizeF factor = AutoScaleFactor;
475 if (AutoScaleMode == AutoScaleMode.Inherit) {
476 ContainerControl cc = FindContainer (this.Parent);
478 factor = cc.AutoScaleFactor;
480 if (factor != new SizeF (1F, 1F)) {
481 is_auto_scaling = true;
484 ResumeLayout (false);
485 is_auto_scaling = false;
488 auto_scale_dimensions = CurrentAutoScaleDimensions;
491 public void PerformAutoScale ()
493 PerformAutoScale (false);
496 internal void PerformDelayedAutoScale ()
498 if (auto_scale_pending)
502 internal bool IsAutoScaling {
503 get { return is_auto_scaling; }
507 [MonoTODO ("Stub, not implemented")]
508 static bool ValidateWarned;
509 public bool Validate() {
510 //throw new NotImplementedException();
511 if (!ValidateWarned) {
512 Console.WriteLine("ContainerControl.Validate is not yet implemented");
513 ValidateWarned = true;
519 public bool Validate (bool checkAutoValidate)
521 if ((checkAutoValidate && (AutoValidate != AutoValidate.Disable)) || !checkAutoValidate)
528 [EditorBrowsable (EditorBrowsableState.Never)]
529 public virtual bool ValidateChildren ()
531 return ValidateChildren (ValidationConstraints.Selectable);
535 [EditorBrowsable (EditorBrowsableState.Never)]
536 public virtual bool ValidateChildren (ValidationConstraints validationConstraints)
538 bool recurse = !((validationConstraints & ValidationConstraints.ImmediateChildren) == ValidationConstraints.ImmediateChildren);
540 foreach (Control control in Controls)
541 if (!ValidateNestedControls (control, validationConstraints, recurse))
548 bool IContainerControl.ActivateControl(Control control) {
549 return Select(control);
551 #endregion // Public Instance Methods
553 #region Protected Instance Methods
554 [EditorBrowsable (EditorBrowsableState.Advanced)]
555 protected override void AdjustFormScrollbars(bool displayScrollbars) {
556 base.AdjustFormScrollbars(displayScrollbars);
559 protected override void Dispose(bool disposing) {
560 base.Dispose(disposing);
563 // LAMESPEC This used to be documented, but it's not in code
564 // and no longer listed in MSDN2
565 // [EditorBrowsable (EditorBrowsableState.Advanced)]
566 // protected override void OnControlRemoved(ControlEventArgs e) {
567 private void OnControlRemoved(object sender, ControlEventArgs e) {
568 if (e.Control == this.unvalidated_control) {
569 this.unvalidated_control = null;
572 if (e.Control == this.active_control) {
573 this.unvalidated_control = null;
576 // base.OnControlRemoved(e);
579 protected override void OnCreateControl() {
580 base.OnCreateControl();
581 // MS seems to call this here, it gets the whole databinding process started
582 OnBindingContextChanged (EventArgs.Empty);
586 protected override bool ProcessCmdKey (ref Message msg, Keys keyData)
588 if (ToolStripManager.ProcessCmdKey (ref msg, keyData) == true)
591 return base.ProcessCmdKey (ref msg, keyData);
595 [EditorBrowsable (EditorBrowsableState.Advanced)]
596 protected override bool ProcessDialogChar(char charCode) {
598 if (ProcessMnemonic(charCode)) {
602 return base.ProcessDialogChar(charCode);
605 protected override bool ProcessDialogKey(Keys keyData) {
609 key = keyData & Keys.KeyCode;
614 if ((keyData & (Keys.Alt | Keys.Control)) == Keys.None) {
615 if (ProcessTabKey ((Control.ModifierKeys & Keys.Shift) == 0)) {
636 if (SelectNextControl(active_control, forward, false, false, true)) {
644 return base.ProcessDialogKey(keyData);
647 protected override bool ProcessMnemonic(char charCode) {
655 System.Collections.Generic.List<MenuStrip> strips = new System.Collections.Generic.List<MenuStrip> ();
659 c = GetNextControl(c, true);
662 strips.Add ((MenuStrip)c);
665 // This is stupid. I want to be able to call c.ProcessMnemonic directly
666 if (c.ProcessControlMnemonic(charCode)) {
676 } while (c != active_control);
679 // No one has an explicit mnemonic for this key.
680 // Let MenuStrips have a chance at implicit mnemonics.
681 foreach (MenuStrip ms in strips)
682 if (ms.ProcessImplicitMnemonic (charCode))
689 protected virtual bool ProcessTabKey(bool forward) {
690 return SelectNextControl(active_control, forward, true, true, false);
693 protected override void Select(bool directed, bool forward)
695 if (Parent != null) {
696 IContainerControl parent = Parent.GetContainerControl ();
697 if (parent != null) {
698 parent.ActiveControl = this;
702 if (directed && auto_select_child) {
703 SelectNextControl (null, forward, true, true, false);
707 protected virtual void UpdateDefaultButton() {
711 [EditorBrowsable (EditorBrowsableState.Advanced)]
712 protected override void WndProc(ref Message m) {
713 switch ((Msg) m.Msg) {
715 case Msg.WM_SETFOCUS:
716 if (active_control != null)
717 Select (active_control);
719 base.WndProc (ref m);
722 SelectNextControl (null, true, true, true, false);
731 #endregion // Protected Instance Methods
733 #region Internal Methods
734 internal void ChildControlRemoved (Control control)
736 ContainerControl top_container = FindForm ();
737 if (top_container == null)
738 top_container = this;
740 // Remove controls -as well as any sub control- that was in the pending validation chain
741 ArrayList pending_validation_chain = top_container.pending_validation_chain;
742 if (pending_validation_chain != null) {
743 RemoveChildrenFromValidation (pending_validation_chain, control);
745 if (pending_validation_chain.Count == 0)
746 top_container.pending_validation_chain = null;
749 if (control == active_control || control.Contains (active_control)) {
750 SelectNextControl (this, true, true, true, true);
751 if (control == active_control || control.Contains (active_control)) {
752 active_control = null;
757 // Check that this control (or any child) is included in the pending validation chain
758 bool RemoveChildrenFromValidation (ArrayList validation_chain, Control c)
760 if (RemoveFromValidationChain (validation_chain, c))
763 foreach (Control child in c.Controls)
764 if (RemoveChildrenFromValidation (validation_chain, child))
770 // Remove the top most control in the pending validation chain, as well as any children there,
771 // taking advantage of the fact that the chain is in reverse order of the control's hierarchy
772 bool RemoveFromValidationChain (ArrayList validation_chain, Control c)
774 int idx = validation_chain.IndexOf (c);
776 pending_validation_chain.RemoveAt (idx--);
783 internal virtual void CheckAcceptButton()
785 // do nothing here, only called if it is a Form
789 private bool ValidateNestedControls (Control c, ValidationConstraints constraints, bool recurse)
791 bool validate_result = true;
793 if (!c.CausesValidation)
794 validate_result = true;
795 else if (!ValidateThisControl (c, constraints))
796 validate_result = true;
797 else if (!ValidateControl (c))
798 validate_result = false;
801 foreach (Control control in c.Controls)
802 if (!ValidateNestedControls (control, constraints, recurse))
805 return validate_result;
808 private bool ValidateThisControl (Control c, ValidationConstraints constraints)
810 if (constraints == ValidationConstraints.None)
813 if ((constraints & ValidationConstraints.Enabled) == ValidationConstraints.Enabled && !c.Enabled)
816 if ((constraints & ValidationConstraints.Selectable) == ValidationConstraints.Selectable && !c.GetStyle (ControlStyles.Selectable))
819 if ((constraints & ValidationConstraints.TabStop) == ValidationConstraints.TabStop && !c.TabStop)
822 if ((constraints & ValidationConstraints.Visible) == ValidationConstraints.Visible && !c.Visible)
828 #endregion // Internal Methods
831 protected override void OnParentChanged (EventArgs e)
833 base.OnParentChanged (e);
836 [EditorBrowsable (EditorBrowsableState.Advanced)]
837 protected override void OnFontChanged (EventArgs e)
839 base.OnFontChanged (e);
841 if (AutoScaleMode == AutoScaleMode.Font)
845 protected override void OnLayout (LayoutEventArgs e)
850 AutoValidate auto_validate = AutoValidate.Inherit;
853 [AmbientValue (AutoValidate.Inherit)]
854 [EditorBrowsable (EditorBrowsableState.Never)]
855 public virtual AutoValidate AutoValidate {
857 return auto_validate;
860 [MonoTODO("Currently does nothing with the setting")]
862 if (auto_validate != value){
863 auto_validate = value;
864 OnAutoValidateChanged (new EventArgs ());
869 internal bool ShouldSerializeAutoValidate ()
871 return this.AutoValidate != AutoValidate.Inherit;
874 static object OnValidateChanged = new object ();
876 protected virtual void OnAutoValidateChanged (EventArgs e)
878 EventHandler eh = (EventHandler) (Events [OnValidateChanged]);
884 [EditorBrowsable (EditorBrowsableState.Never)]
885 public event EventHandler AutoValidateChanged {
886 add { Events.AddHandler (OnValidateChanged, value); }
887 remove { Events.RemoveHandler (OnValidateChanged, value); }