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 {
35 [ClassInterface (ClassInterfaceType.AutoDispatch)]
37 public class ContainerControl : ScrollableControl, IContainerControl {
38 private Control active_control;
39 private Control unvalidated_control;
40 private ArrayList pending_validation_chain;
42 // This is an internal hack that allows some container controls
43 // to not auto select their child when they are activated
44 internal bool auto_select_child = true;
45 private SizeF auto_scale_dimensions;
46 private AutoScaleMode auto_scale_mode;
47 private bool auto_scale_mode_set;
48 private bool auto_scale_pending;
49 private bool is_auto_scaling;
51 internal bool validation_failed; //track whether validation was cancelled by a validating control
53 #region Public Constructors
54 public ContainerControl() {
55 active_control = null;
56 unvalidated_control = null;
57 ControlRemoved += new ControlEventHandler(OnControlRemoved);
58 auto_scale_dimensions = SizeF.Empty;
59 auto_scale_mode = AutoScaleMode.Inherit;
61 #endregion // Public Constructors
63 #region Public Instance Properties
65 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
66 public Control ActiveControl {
68 return active_control;
72 if (value==null || (active_control == value && active_control.Focused)) {
76 if (!Contains(value)) {
77 throw new ArgumentException("Cannot activate invisible or disabled control.");
80 // Fire the enter and leave events if possible
81 Form form = FindForm ();
82 Control active = GetMostDeeplyNestedActiveControl (form == null ? this : form);
83 Control common_ancestor = GetCommonContainer (active, value);
84 ArrayList chain = new ArrayList ();
85 ArrayList validation_chain = new ArrayList ();
86 Control walk = active;
88 // we split this up into three steps:
89 // 1. walk up the tree (if we need to) to our root, firing leave events.
91 // 3. walk down the tree (if we need to), firing enter events.
93 // "our root" is either the common ancestor of the current active
94 // control and the new active control, or the current active control,
95 // or the new active control. That is, it's either one of these three
98 // (1) root (2) new (3) current
100 // ... ... ... ... ... ...
102 // current new current new
105 // note (3) doesn't require any upward walking, and no leave events are generated.
106 // (2) doesn't require any downward walking, and no enter events are generated.
108 // as we walk up the tree, we generate a list of all controls which cause
109 // validation. After firing the leave events, we invoke (in order starting from
110 // the most deeply nested) their Validating event. If one sets CancelEventArgs.Cancel
111 // to true, we ignore the control the user wanted to set ActiveControl to, and use
112 // the Validating control instead.
114 bool fire_enter = true;
115 Control root = common_ancestor;
117 active_control = value;
119 // Generate the leave messages
120 while (walk != common_ancestor && walk != null) {
127 /* clear our idea of the active control as we go back up */
128 if (walk is ContainerControl)
129 ((ContainerControl)walk).active_control = null;
131 if (walk.CausesValidation)
132 validation_chain.Add (walk);
137 // Validation can be postponed due to all the controls
138 // in the enter chain not causing validation. If we don't have any
139 // enter chain, it means that the selected control is a child and then
140 // we need to validate the controls anyway
141 bool postpone_validation;
142 Control topmost_under_root = null; // topmost under root, in the *enter* chain
144 postpone_validation = false;
146 postpone_validation = true;
148 while (walk != root && walk != null) {
149 if (walk.CausesValidation)
150 postpone_validation = false;
152 topmost_under_root = walk;
157 Control failed_validation_control = PerformValidation (form == null ? this : form, postpone_validation,
158 validation_chain, topmost_under_root);
159 if (failed_validation_control != null) {
160 active_control = value = failed_validation_control;
166 while (walk != root && walk != null) {
171 if (root != null && walk == root && !(root is ContainerControl))
174 for (int i = chain.Count - 1; i >= 0; i--) {
175 walk = (Control) chain [i];
182 while (walk != null) {
183 if (walk.Parent is ContainerControl) {
184 ((ContainerControl) walk.Parent).active_control = ctl;
193 // Scroll control into view
194 ScrollControlIntoView(active_control);
199 while (walk != null) {
200 if (walk.Parent is ContainerControl) {
206 // Let the control know it's selected
207 if (ctl.InternalContainsFocus)
208 SendControlFocus (active_control);
212 // Return the control where validation failed, and null otherwise
213 // @topmost_under_root is the control under the root in the enter chain, if any
215 // The process of validation happens as described:
217 // 1. Iterate over the nodes in the enter chain (walk down), looking for any node
218 // causing validation. If we can't find any, don't validate the current validation chain, but postpone it,
219 // saving it in the top_container.pending_validation_chain field, since we need to keep track of it later.
220 // If we have a previous pending_validation_chain, add the new nodes, making sure they are not repeated
221 // (this is computed in ActiveControl and we receive if as the postpone_validation parameter).
223 // 2. If we found at least one node causing validation in the enter chain, try to validate the elements
224 // in pending_validation_chain, if any. Then continue with the ones receives as parameters.
226 // 3. Return null if all the validation performed successfully, and return the control where the validation
229 private Control PerformValidation (ContainerControl top_container, bool postpone_validation, ArrayList validation_chain,
230 Control topmost_under_root)
232 validation_failed = false;
234 if (postpone_validation) {
235 AddValidationChain (top_container, validation_chain);
239 // if not null, pending chain has always one element or more
240 if (top_container.pending_validation_chain != null) {
241 // if the topmost node in the enter chain is exactly the topmost
242 // int the validation chain, remove it, as .net does
243 int last_idx = top_container.pending_validation_chain.Count - 1;
244 if (topmost_under_root == top_container.pending_validation_chain [last_idx])
245 top_container.pending_validation_chain.RemoveAt (last_idx);
247 AddValidationChain (top_container, validation_chain);
248 validation_chain = top_container.pending_validation_chain;
249 top_container.pending_validation_chain = null;
252 for (int i = 0; i < validation_chain.Count; i ++) {
253 if (!ValidateControl ((Control)validation_chain[i])) {
254 validation_failed = true;
255 return (Control)validation_chain[i];
262 // Add the elements in validation_chain to the pending validation chain stored in top_container
263 private void AddValidationChain (ContainerControl top_container, ArrayList validation_chain)
265 if (validation_chain.Count == 0)
268 if (top_container.pending_validation_chain == null || top_container.pending_validation_chain.Count == 0) {
269 top_container.pending_validation_chain = validation_chain;
273 foreach (Control c in validation_chain)
274 if (!top_container.pending_validation_chain.Contains (c))
275 top_container.pending_validation_chain.Add (c);
278 private bool ValidateControl (Control c)
280 CancelEventArgs e = new CancelEventArgs ();
282 c.FireValidating (e);
291 private Control GetMostDeeplyNestedActiveControl (ContainerControl container)
293 Control active = container.ActiveControl;
294 while (active is ContainerControl) {
295 if (((ContainerControl)active).ActiveControl == null)
297 active = ((ContainerControl)active).ActiveControl;
302 // Just in a separate method to make debugging a little easier,
303 // should eventually be rolled into ActiveControl setter
304 private Control GetCommonContainer (Control active_control, Control value)
306 Control new_container = null;
307 Control prev_container = active_control;
309 while (prev_container != null) {
310 new_container = value.Parent;
311 while (new_container != null) {
312 if (new_container == prev_container)
313 return new_container;
314 new_container = new_container.Parent;
317 prev_container = prev_container.Parent;
323 internal void SendControlFocus (Control c)
325 if (c.IsHandleCreated) {
326 XplatUI.SetFocus (c.window.Handle);
331 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
332 [EditorBrowsable (EditorBrowsableState.Advanced)]
334 public SizeF AutoScaleDimensions {
336 return auto_scale_dimensions;
340 if (auto_scale_dimensions != value) {
341 auto_scale_dimensions = value;
348 protected SizeF AutoScaleFactor {
350 if (auto_scale_dimensions.IsEmpty)
351 return new SizeF (1f, 1f);
353 return new SizeF(CurrentAutoScaleDimensions.Width / auto_scale_dimensions.Width,
354 CurrentAutoScaleDimensions.Height / auto_scale_dimensions.Height);
360 [EditorBrowsable (EditorBrowsableState.Advanced)]
361 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
362 public AutoScaleMode AutoScaleMode {
364 return auto_scale_mode;
368 (this as Form).AutoScale = false;
370 if (auto_scale_mode != value) {
371 auto_scale_mode = value;
373 if (auto_scale_mode_set)
374 auto_scale_dimensions = SizeF.Empty;
376 auto_scale_mode_set = true;
384 public override BindingContext BindingContext {
386 if (base.BindingContext == null) {
387 base.BindingContext = new BindingContext();
389 return base.BindingContext;
393 base.BindingContext = value;
398 [EditorBrowsable (EditorBrowsableState.Advanced)]
399 public SizeF CurrentAutoScaleDimensions {
401 switch(auto_scale_mode) {
402 case AutoScaleMode.Dpi:
403 return TextRenderer.GetDpi ();
405 case AutoScaleMode.Font:
406 Size s = TextRenderer.MeasureText ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890", Font);
407 int width = (int)Math.Round ((float)s.Width / 62f);
409 return new SizeF (width, s.Height);
412 return auto_scale_dimensions;
417 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
418 public Form ParentForm {
422 parent = this.Parent;
424 while (parent != null) {
425 if (parent is Form) {
428 parent = parent.Parent;
434 #endregion // Public Instance Properties
436 #region Protected Instance Methods
437 protected override bool CanEnableIme {
438 get { return false; }
440 protected override CreateParams CreateParams {
442 return base.CreateParams;
445 #endregion // Public Instance Methods
447 #region Public Instance Methods
448 internal void PerformAutoScale (bool called_by_scale)
450 if ((AutoScaleMode == AutoScaleMode.Inherit) && !called_by_scale)
453 if ((layout_suspended > 0) && !called_by_scale) {
454 auto_scale_pending = true;
457 // Set this first so we don't get called again from
458 // PerformDelayedAutoScale after ResumeLayout
459 auto_scale_pending = false;
461 SizeF factor = AutoScaleFactor;
462 if (AutoScaleMode == AutoScaleMode.Inherit) {
463 ContainerControl cc = FindContainer (this.Parent);
465 factor = cc.AutoScaleFactor;
467 if (factor != new SizeF (1F, 1F)) {
468 is_auto_scaling = true;
471 ResumeLayout (false);
472 is_auto_scaling = false;
475 auto_scale_dimensions = CurrentAutoScaleDimensions;
478 public void PerformAutoScale ()
480 PerformAutoScale (false);
483 internal void PerformDelayedAutoScale ()
485 if (auto_scale_pending)
489 internal bool IsAutoScaling {
490 get { return is_auto_scaling; }
493 [MonoTODO ("Stub, not implemented")]
494 static bool ValidateWarned;
495 public bool Validate() {
496 //throw new NotImplementedException();
497 if (!ValidateWarned) {
498 Console.WriteLine("ContainerControl.Validate is not yet implemented");
499 ValidateWarned = true;
504 public bool Validate (bool checkAutoValidate)
506 if ((checkAutoValidate && (AutoValidate != AutoValidate.Disable)) || !checkAutoValidate)
513 [EditorBrowsable (EditorBrowsableState.Never)]
514 public virtual bool ValidateChildren ()
516 return ValidateChildren (ValidationConstraints.Selectable);
520 [EditorBrowsable (EditorBrowsableState.Never)]
521 public virtual bool ValidateChildren (ValidationConstraints validationConstraints)
523 bool recurse = !((validationConstraints & ValidationConstraints.ImmediateChildren) == ValidationConstraints.ImmediateChildren);
525 foreach (Control control in Controls)
526 if (!ValidateNestedControls (control, validationConstraints, recurse))
532 bool IContainerControl.ActivateControl(Control control) {
533 return Select(control);
535 #endregion // Public Instance Methods
537 #region Protected Instance Methods
538 [EditorBrowsable (EditorBrowsableState.Advanced)]
539 protected override void AdjustFormScrollbars(bool displayScrollbars) {
540 base.AdjustFormScrollbars(displayScrollbars);
543 protected override void Dispose(bool disposing) {
544 base.Dispose(disposing);
547 // LAMESPEC This used to be documented, but it's not in code
548 // and no longer listed in MSDN2
549 // [EditorBrowsable (EditorBrowsableState.Advanced)]
550 // protected override void OnControlRemoved(ControlEventArgs e) {
551 private void OnControlRemoved(object sender, ControlEventArgs e) {
552 if (e.Control == this.unvalidated_control) {
553 this.unvalidated_control = null;
556 if (e.Control == this.active_control) {
557 this.unvalidated_control = null;
560 // base.OnControlRemoved(e);
563 protected override void OnCreateControl() {
564 base.OnCreateControl();
565 // MS seems to call this here, it gets the whole databinding process started
566 OnBindingContextChanged (EventArgs.Empty);
569 protected override bool ProcessCmdKey (ref Message msg, Keys keyData)
571 if (ToolStripManager.ProcessCmdKey (ref msg, keyData) == true)
574 return base.ProcessCmdKey (ref msg, keyData);
577 [EditorBrowsable (EditorBrowsableState.Advanced)]
578 protected override bool ProcessDialogChar(char charCode) {
580 if (ProcessMnemonic(charCode)) {
584 return base.ProcessDialogChar(charCode);
587 protected override bool ProcessDialogKey(Keys keyData) {
591 key = keyData & Keys.KeyCode;
596 if ((keyData & (Keys.Alt | Keys.Control)) == Keys.None) {
597 if (ProcessTabKey ((Control.ModifierKeys & Keys.Shift) == 0)) {
618 if (SelectNextControl(active_control, forward, false, false, true)) {
626 return base.ProcessDialogKey(keyData);
629 protected override bool ProcessMnemonic(char charCode) {
637 c = GetNextControl(c, true);
639 // This is stupid. I want to be able to call c.ProcessMnemonic directly
640 if (c.ProcessControlMnemonic(charCode)) {
650 } while (c != active_control);
655 protected virtual bool ProcessTabKey(bool forward) {
656 return SelectNextControl(active_control, forward, true, true, false);
659 protected override void Select(bool directed, bool forward)
661 if (Parent != null) {
662 IContainerControl parent = Parent.GetContainerControl ();
663 if (parent != null) {
664 parent.ActiveControl = this;
668 if (directed && auto_select_child) {
669 SelectNextControl (null, forward, true, true, false);
673 protected virtual void UpdateDefaultButton() {
677 [EditorBrowsable (EditorBrowsableState.Advanced)]
678 protected override void WndProc(ref Message m) {
679 switch ((Msg) m.Msg) {
681 case Msg.WM_SETFOCUS:
682 if (active_control != null)
683 Select (active_control);
685 base.WndProc (ref m);
693 #endregion // Protected Instance Methods
695 #region Internal Methods
696 internal void ChildControlRemoved (Control control)
698 ContainerControl top_container = FindForm ();
699 if (top_container == null)
700 top_container = this;
702 // Remove controls -as well as any sub control- that was in the pending validation chain
703 ArrayList pending_validation_chain = top_container.pending_validation_chain;
704 if (pending_validation_chain != null) {
705 RemoveChildrenFromValidation (pending_validation_chain, control);
707 if (pending_validation_chain.Count == 0)
708 top_container.pending_validation_chain = null;
711 if (control == active_control || control.Contains (active_control)) {
712 SelectNextControl (this, true, true, true, true);
713 if (control == active_control || control.Contains (active_control)) {
714 active_control = null;
719 // Check that this control (or any child) is included in the pending validation chain
720 bool RemoveChildrenFromValidation (ArrayList validation_chain, Control c)
722 if (RemoveFromValidationChain (validation_chain, c))
725 foreach (Control child in c.Controls)
726 if (RemoveChildrenFromValidation (validation_chain, child))
732 // Remove the top most control in the pending validation chain, as well as any children there,
733 // taking advantage of the fact that the chain is in reverse order of the control's hierarchy
734 bool RemoveFromValidationChain (ArrayList validation_chain, Control c)
736 int idx = validation_chain.IndexOf (c);
738 pending_validation_chain.RemoveAt (idx--);
745 internal virtual void CheckAcceptButton()
747 // do nothing here, only called if it is a Form
750 private bool ValidateNestedControls (Control c, ValidationConstraints constraints, bool recurse)
752 bool validate_result = true;
754 if (!c.CausesValidation)
755 validate_result = true;
756 else if (!ValidateThisControl (c, constraints))
757 validate_result = true;
758 else if (!ValidateControl (c))
759 validate_result = false;
762 foreach (Control control in c.Controls)
763 if (!ValidateNestedControls (control, constraints, recurse))
766 return validate_result;
769 private bool ValidateThisControl (Control c, ValidationConstraints constraints)
771 if (constraints == ValidationConstraints.None)
774 if ((constraints & ValidationConstraints.Enabled) == ValidationConstraints.Enabled && !c.Enabled)
777 if ((constraints & ValidationConstraints.Selectable) == ValidationConstraints.Selectable && !c.GetStyle (ControlStyles.Selectable))
780 if ((constraints & ValidationConstraints.TabStop) == ValidationConstraints.TabStop && !c.TabStop)
783 if ((constraints & ValidationConstraints.Visible) == ValidationConstraints.Visible && !c.Visible)
788 #endregion // Internal Methods
790 protected override void OnParentChanged (EventArgs e)
792 base.OnParentChanged (e);
795 [EditorBrowsable (EditorBrowsableState.Advanced)]
796 protected override void OnFontChanged (EventArgs e)
798 base.OnFontChanged (e);
800 if (AutoScaleMode == AutoScaleMode.Font)
804 protected override void OnLayout (LayoutEventArgs e)
809 AutoValidate auto_validate = AutoValidate.Inherit;
812 [AmbientValue (AutoValidate.Inherit)]
813 [EditorBrowsable (EditorBrowsableState.Never)]
814 public virtual AutoValidate AutoValidate {
816 return auto_validate;
819 [MonoTODO("Currently does nothing with the setting")]
821 if (auto_validate != value){
822 auto_validate = value;
823 OnAutoValidateChanged (new EventArgs ());
828 internal bool ShouldSerializeAutoValidate ()
830 return this.AutoValidate != AutoValidate.Inherit;
833 static object OnValidateChanged = new object ();
835 protected virtual void OnAutoValidateChanged (EventArgs e)
837 EventHandler eh = (EventHandler) (Events [OnValidateChanged]);
843 [EditorBrowsable (EditorBrowsableState.Never)]
844 public event EventHandler AutoValidateChanged {
845 add { Events.AddHandler (OnValidateChanged, value); }
846 remove { Events.RemoveHandler (OnValidateChanged, value); }