Code:
/ DotNET / DotNET / 8.0 / untmp / whidbey / REDBITS / ndp / fx / src / Designer / WinForms / System / WinForms / Design / SelectionUIService.cs / 1 / SelectionUIService.cs
//------------------------------------------------------------------------------ //// Copyright (c) Microsoft Corporation. All rights reserved. // //----------------------------------------------------------------------------- /* */ namespace System.Windows.Forms.Design { using System; using System.Design; using System.Collections; using System.ComponentModel; using System.ComponentModel.Design; using Microsoft.Win32; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Design; using System.Globalization; using System.Runtime.InteropServices; using System.Runtime.Serialization.Formatters; using System.Windows.Forms; ////// /// The selection manager handles selection within a form. There is one selection /// manager for each form or top level designer. /// /// A selection consists of an array of components. One component is designated /// the "primary" selection and is displayed with different grab handles. /// /// An individual selection may or may not have UI associated with it. If the /// selection manager can find a suitable designer that is representing the /// selection, it will highlight the designer's border. If the merged property /// set has a location property, the selection's rules will allow movement. Also, /// if the property set has a size property, the selection's rules will allow /// for sizing. Grab handles may be drawn around the designer and user /// interactions involving the selection frame and grab handles are initiated /// here, but the actual movement of the objects is done in a designer object /// that implements the ISelectionHandler interface. /// @author [....] /// internal sealed class SelectionUIService : Control, ISelectionUIService { private static readonly Point InvalidPoint = new Point(int.MinValue, int.MinValue); private const int HITTEST_CONTAINER_SELECTOR = 0x0001; private const int HITTEST_NORMAL_SELECTION = 0x0002; private const int HITTEST_DEFAULT = HITTEST_CONTAINER_SELECTOR | HITTEST_NORMAL_SELECTION; // These are used during a drag operation, either through our own handle drag or through // ISelectionUIService // private ISelectionUIHandler dragHandler; // the current drag handler private object [] dragComponents; // the controls being dragged private SelectionRules dragRules; // movement constraints for the drag private bool dragMoved = false; private object containerDrag; // object being dragged during a container drag // These are used during a drag of a selection grab handle // private bool ignoreCaptureChanged = false; private bool mouseDown; // is our mouse button actually down right now? private int mouseDragHitTest; // where the hit occurred that caused the drag private Point mouseDragAnchor = InvalidPoint; // anchor point of the drag private Rectangle mouseDragOffset = Rectangle.Empty; // current drag offset private Point lastMoveScreenCoord = Point.Empty; private bool ctrlSelect = false; // was the CTRL key down when the drag began private bool mouseDragging = false; // Are we actually doing a drag? private ContainerSelectorActiveEventHandler containerSelectorActive; // the event we fire when user interacts with container selector private Hashtable selectionItems; private Hashtable selectionHandlers; // Component UI handlers private bool savedVisible; // we stash this when we mess with visibility ourselves. private bool batchMode; private bool batchChanged; private bool batchSync; private ISelectionService selSvc; private IDesignerHost host; private DesignerTransaction dragTransaction; ////// /// Creates a new selection manager object. The selection manager manages all /// selection of all designers under the current form file. /// [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] public SelectionUIService(IDesignerHost host) : base() { this.SetStyle(ControlStyles.StandardClick | ControlStyles.Opaque | ControlStyles.OptimizedDoubleBuffer, true); this.host = host; this.dragHandler = null; this.dragComponents = null; this.selectionItems = new Hashtable(); this.selectionHandlers = new Hashtable(); this.AllowDrop = true; // Not really any reason for this, except that it can be handy when // using Spy++ // Text = "SelectionUIOverlay"; this.selSvc = (ISelectionService)host.GetService(typeof(ISelectionService)); if (selSvc != null) { selSvc.SelectionChanged += new EventHandler(this.OnSelectionChanged); } // And configure the events we want to listen to. // host.TransactionOpened += new EventHandler(this.OnTransactionOpened); host.TransactionClosed += new DesignerTransactionCloseEventHandler(this.OnTransactionClosed); if (host.InTransaction) { OnTransactionOpened(host, EventArgs.Empty); } IComponentChangeService cs = (IComponentChangeService)host.GetService(typeof(IComponentChangeService)); if (cs != null) { cs.ComponentRemoved += new ComponentEventHandler(this.OnComponentRemove); cs.ComponentChanged += new ComponentChangedEventHandler(this.OnComponentChanged); } // Listen to the SystemEvents so that we can resync selection based on display settings etc. SystemEvents.DisplaySettingsChanged += new EventHandler(this.OnSystemSettingChanged); SystemEvents.InstalledFontsChanged += new EventHandler(this.OnSystemSettingChanged); SystemEvents.UserPreferenceChanged += new UserPreferenceChangedEventHandler(this.OnUserPreferenceChanged); } ////// /// override of control. /// protected override CreateParams CreateParams { get { CreateParams cp = base.CreateParams; cp.Style &= ~(NativeMethods.WS_CLIPSIBLINGS | NativeMethods.WS_CLIPCHILDREN); return cp; } } ////// /// Called to initiate a mouse drag on the selection overlay. We cache some /// state here. /// private void BeginMouseDrag(Point anchor, int hitTest) { Capture = true; ignoreCaptureChanged = false; mouseDragAnchor = anchor; mouseDragging = true; mouseDragHitTest = hitTest; mouseDragOffset = new Rectangle(); savedVisible = Visible; } ////// /// Displays the given exception to the user. /// private void DisplayError(Exception e) { IUIService uis = (IUIService)host.GetService(typeof(IUIService)); if (uis != null) { uis.ShowError(e); } else { string message = e.Message; if (message == null || message.Length == 0) { message = e.ToString(); } RTLAwareMessageBox.Show(null, message, null, MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1, 0); } } ////// /// Disposes the entire selection UI manager. /// protected override void Dispose(bool disposing) { if (disposing) { if (selSvc != null) { selSvc.SelectionChanged -= new EventHandler(this.OnSelectionChanged); } if (host != null) { host.TransactionOpened -= new EventHandler(this.OnTransactionOpened); host.TransactionClosed -= new DesignerTransactionCloseEventHandler(this.OnTransactionClosed); if (host.InTransaction) { OnTransactionClosed(host, new DesignerTransactionCloseEventArgs(true, true)); } IComponentChangeService cs = (IComponentChangeService)host.GetService(typeof(IComponentChangeService)); if (cs != null) { cs.ComponentRemoved -= new ComponentEventHandler(this.OnComponentRemove); cs.ComponentChanged -= new ComponentChangedEventHandler(this.OnComponentChanged); } } foreach(SelectionUIItem s in selectionItems.Values) { s.Dispose(); } selectionHandlers.Clear(); selectionItems.Clear(); // Listen to the SystemEvents so that we can resync selection based on display settings etc. SystemEvents.DisplaySettingsChanged -= new EventHandler(this.OnSystemSettingChanged); SystemEvents.InstalledFontsChanged -= new EventHandler(this.OnSystemSettingChanged); SystemEvents.UserPreferenceChanged -= new UserPreferenceChangedEventHandler(this.OnUserPreferenceChanged); } base.Dispose(disposing); } ////// /// Called when we want to finish a mouse drag and clean up our variables. We call this /// from multiple places, depending on the state of the finish. This does NOT end /// the drag -- for that must call EndDrag. This just cleans up the state of the /// mouse. /// private void EndMouseDrag(Point position) { // it's possible for us to be destroyed in a drag -- // e.g. if this is the tray's selectionuiservice // and the last item is dragged out, so // check diposed first if (this.IsDisposed) { return; } ignoreCaptureChanged = true; Capture = false; mouseDragAnchor = InvalidPoint; mouseDragOffset = Rectangle.Empty; mouseDragHitTest = 0; dragMoved = false; SetSelectionCursor(position); mouseDragging = ctrlSelect = false; } ////// /// Determines the selection hit test at the given point. The point should be in screen /// coordinates. /// private HitTestInfo GetHitTest(Point value, int flags) { Point pt = PointToClient(value); foreach(SelectionUIItem item in selectionItems.Values) { if ((flags & HITTEST_CONTAINER_SELECTOR) != 0) { if (item is ContainerSelectionUIItem && (item.GetRules() & SelectionRules.Visible) != SelectionRules.None) { int hitTest = item.GetHitTest(pt); if ((hitTest & SelectionUIItem.CONTAINER_SELECTOR) != 0) { return new HitTestInfo(hitTest, item, true); } } } if ((flags & HITTEST_NORMAL_SELECTION) != 0) { if (!(item is ContainerSelectionUIItem) && (item.GetRules() & SelectionRules.Visible) != SelectionRules.None) { int hitTest = item.GetHitTest(pt); if (hitTest != SelectionUIItem.NOHIT) { if (hitTest != 0) { return new HitTestInfo(hitTest, item); } else { return new HitTestInfo(SelectionUIItem.NOHIT, item); } } } } } return new HitTestInfo(SelectionUIItem.NOHIT, null); } private ISelectionUIHandler GetHandler(object component) { return (ISelectionUIHandler)selectionHandlers[component]; } ////// /// This method returns a well-formed name for a drag transaction based on /// the rules it is given. /// public static string GetTransactionName(SelectionRules rules, object[] objects) { // Determine a nice name for the drag operation // string transactionName; if ((int)(rules & SelectionRules.Moveable) != 0) { if (objects.Length > 1) { transactionName = SR.GetString(SR.DragDropMoveComponents, objects.Length); } else { string name = string.Empty; if (objects.Length > 0) { IComponent comp = objects[0] as IComponent; if (comp != null && comp.Site != null) { name = comp.Site.Name; } else { name = objects[0].GetType().Name; } } transactionName = SR.GetString(SR.DragDropMoveComponent, name); } } else if ((int)(rules & SelectionRules.AllSizeable) != 0) { if (objects.Length > 1) { transactionName = SR.GetString(SR.DragDropSizeComponents, objects.Length); } else { string name = string.Empty; if (objects.Length > 0) { IComponent comp = objects[0] as IComponent; if (comp != null && comp.Site != null) { name = comp.Site.Name; } else { name = objects[0].GetType().Name; } } transactionName = SR.GetString(SR.DragDropSizeComponent, name); } } else { transactionName = SR.GetString(SR.DragDropDragComponents, objects.Length); } return transactionName; } ////// /// Called by the designer host when it is entering or leaving a batch /// operation. Here we queue up selection notification and we turn off /// our UI. /// private void OnTransactionClosed(object sender, DesignerTransactionCloseEventArgs e) { if (e.LastTransaction) { batchMode = false; if (batchChanged) { batchChanged = false; ((ISelectionUIService)this).SyncSelection(); } if (batchSync) { batchSync = false; ((ISelectionUIService)this).SyncComponent(null); } } } ////// /// Called by the designer host when it is entering or leaving a batch /// operation. Here we queue up selection notification and we turn off /// our UI. /// private void OnTransactionOpened(object sender, EventArgs e) { batchMode = true; } ////// /// update our window region on first create. We shouldn't do this before the handle /// is created or else we will force creation. /// protected override void OnHandleCreated(EventArgs e) { Debug.Assert(!RecreatingHandle, "Perf hit: we are recreating the docwin handle"); base.OnHandleCreated(e); // Default the shape of the control to be empty, so that // if nothing is initially selected that our window surface doesn't // interfere. // UpdateWindowRegion(); } ////// /// Called whenever a component changes. Here we update our selection information /// so that the selection rectangles are all up to date. /// private void OnComponentChanged(object sender, ComponentChangedEventArgs ccevent) { if (!batchMode) { ((ISelectionUIService)this).SyncSelection(); } else { batchChanged = true; } } ////// /// called by the formcore when someone has removed a component. This will /// remove any selection on the component without disturbing the rest of /// the selection /// private void OnComponentRemove(object sender, ComponentEventArgs ce) { selectionHandlers.Remove(ce.Component); selectionItems.Remove(ce.Component); ((ISelectionUIService)this).SyncComponent(ce.Component); } ////// /// Called to invoke the container active event, if a designer /// has bound to it. /// private void OnContainerSelectorActive(ContainerSelectorActiveEventArgs e) { if (containerSelectorActive != null) { containerSelectorActive(this, e); } } ////// /// Called when the selection changes. We sync up the UI with /// the selection at this point. /// private void OnSelectionChanged(object sender, EventArgs e) { ICollection selection = selSvc.GetSelectedComponents(); Hashtable newSelection = new Hashtable(selection.Count); bool shapeChanged = false; foreach(object comp in selection ) { object existingItem = selectionItems[comp]; bool create = true; if (existingItem != null) { ContainerSelectionUIItem item = existingItem as ContainerSelectionUIItem; if (item != null) { item.Dispose(); shapeChanged = true; } else { newSelection[comp] = existingItem; create = false; } } if (create) { shapeChanged = true; newSelection[comp] = new SelectionUIItem(this, comp); } } if (!shapeChanged) { shapeChanged = selectionItems.Keys.Count != newSelection.Keys.Count; } selectionItems = newSelection; if (shapeChanged) { UpdateWindowRegion(); } Invalidate(); Update(); } ////// /// User setting requires that we repaint. /// private void OnSystemSettingChanged(object sender, EventArgs e) { Invalidate(); } ////// /// User setting requires that we repaint. /// private void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e) { Invalidate(); } ////// /// Inheriting classes should override this method to handle this event. /// Call super.onDragEnter to send this event to any registered event listeners. /// protected override void OnDragEnter(DragEventArgs devent) { base.OnDragEnter(devent); if (dragHandler != null) { dragHandler.OleDragEnter(devent); } } ////// /// Inheriting classes should override this method to handle this event. /// Call super.onDragOver to send this event to any registered event listeners. /// protected override void OnDragOver(DragEventArgs devent) { base.OnDragOver(devent); if (dragHandler != null) { dragHandler.OleDragOver(devent); } } ////// /// Inheriting classes should override this method to handle this event. /// Call super.onDragLeave to send this event to any registered event listeners. /// protected override void OnDragLeave(EventArgs e) { base.OnDragLeave(e); if (dragHandler != null) { dragHandler.OleDragLeave(); } } ////// /// Inheriting classes should override this method to handle this event. /// Call super.onDragDrop to send this event to any registered event listeners. /// protected override void OnDragDrop(DragEventArgs devent) { base.OnDragDrop(devent); if (dragHandler != null) { dragHandler.OleDragDrop(devent); } } ////// /// Inheriting classes should override this method to handle this event. /// Call base.OnDoiubleClick to send this event to any registered event listeners. /// protected override void OnDoubleClick(EventArgs devent) { base.OnDoubleClick(devent); if (selSvc != null) { object selComp = selSvc.PrimarySelection; Debug.Assert(selComp != null, "Illegal selection on double-click"); if (selComp != null) { ISelectionUIHandler handler = GetHandler(selComp); if (handler != null) { handler.OnSelectionDoubleClick((IComponent)selComp); } } } } ////// /// Overrides Control to handle our selection grab handles. /// // Standard 'catch all - rethrow critical' exception pattern [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] [SuppressMessage("Microsoft.Security", "CA2102:CatchNonClsCompliantExceptionsInGeneralHandlers")] protected override void OnMouseDown(MouseEventArgs me) { if (dragHandler == null && selSvc != null) { try { mouseDown = true; // First, did the user step on anything? // Point anchor = PointToScreen(new Point(me.X, me.Y)); HitTestInfo hti = GetHitTest(anchor, HITTEST_DEFAULT); int hitTest = hti.hitTest; if ((hitTest & SelectionUIItem.CONTAINER_SELECTOR) != 0) { selSvc.SetSelectedComponents(new object[] {hti.selectionUIHit.component}, SelectionTypes.Auto); // Then do a drag... // SelectionRules rules = SelectionRules.Moveable; if (((ISelectionUIService)this).BeginDrag(rules, anchor.X, anchor.Y)) { Visible = false; containerDrag = hti.selectionUIHit.component; BeginMouseDrag(anchor, hitTest); } } else if (hitTest != SelectionUIItem.NOHIT && me.Button == MouseButtons.Left) { SelectionRules rules = SelectionRules.None; // If the CTRL key isn't down, select this component, // otherwise, we wait until the mouse up // // Make sure the component is selected // ctrlSelect = (Control.ModifierKeys & Keys.Control) != Keys.None; if (!ctrlSelect) { selSvc.SetSelectedComponents(new object[] {hti.selectionUIHit.component}, SelectionTypes.Primary); } if ((hitTest & SelectionUIItem.MOVE_MASK) != 0) { rules |= SelectionRules.Moveable; } if ((hitTest & SelectionUIItem.SIZE_MASK) != 0) { if ((hitTest & (SelectionUIItem.SIZE_X | SelectionUIItem.POS_RIGHT)) == (SelectionUIItem.SIZE_X | SelectionUIItem.POS_RIGHT)) { rules |= SelectionRules.RightSizeable; } if ((hitTest & (SelectionUIItem.SIZE_X | SelectionUIItem.POS_LEFT)) == (SelectionUIItem.SIZE_X | SelectionUIItem.POS_LEFT)) { rules |= SelectionRules.LeftSizeable; } if ((hitTest & (SelectionUIItem.SIZE_Y | SelectionUIItem.POS_TOP)) == (SelectionUIItem.SIZE_Y | SelectionUIItem.POS_TOP)) { rules |= SelectionRules.TopSizeable; } if ((hitTest & (SelectionUIItem.SIZE_Y | SelectionUIItem.POS_BOTTOM)) == (SelectionUIItem.SIZE_Y | SelectionUIItem.POS_BOTTOM)) { rules |= SelectionRules.BottomSizeable; } if (((ISelectionUIService)this).BeginDrag(rules, anchor.X, anchor.Y)) { BeginMouseDrag(anchor, hitTest); } } else { // Our mouse is in drag mode. We defer the actual move until the user moves the // mouse. // dragRules = rules; BeginMouseDrag(anchor, hitTest); } } else if (hitTest == SelectionUIItem.NOHIT) { dragRules = SelectionRules.None; mouseDragAnchor = InvalidPoint; return; } } catch(Exception e) { if (ClientUtils.IsCriticalException(e)) { throw; } else if (e != CheckoutException.Canceled) { DisplayError(e); } } } } ////// /// Overrides Control to handle our selection grab handles. /// protected override void OnMouseMove(MouseEventArgs me) { base.OnMouseMove(me); Point screenCoord = PointToScreen(new Point(me.X, me.Y)); HitTestInfo hti = GetHitTest(screenCoord, HITTEST_CONTAINER_SELECTOR); int hitTest = hti.hitTest; if (hitTest != SelectionUIItem.CONTAINER_SELECTOR && hti.selectionUIHit != null) { OnContainerSelectorActive(new ContainerSelectorActiveEventArgs(hti.selectionUIHit.component)); } if (lastMoveScreenCoord == screenCoord) { return; } // If we're not dragging then set the cursor correctly. // if (!mouseDragging) { SetSelectionCursor(screenCoord); } else { // we have to make sure the mouse moved farther than // the minimum drag distance before we actually start // the drag // if (!((ISelectionUIService)this).Dragging && (mouseDragHitTest & SelectionUIItem.MOVE_MASK) != 0) { Size minDragSize = SystemInformation.DragSize; if ( Math.Abs(screenCoord.X - mouseDragAnchor.X) < minDragSize.Width && Math.Abs(screenCoord.Y - mouseDragAnchor.Y) < minDragSize.Height) { return; } else { ignoreCaptureChanged = true; if (((ISelectionUIService)this).BeginDrag(dragRules, mouseDragAnchor.X, mouseDragAnchor.Y)) { // we're moving, so we // don't care about the ctrl key any more ctrlSelect = false; } else { EndMouseDrag(MousePosition); return; } } } Rectangle old = mouseDragOffset; if ((mouseDragHitTest & SelectionUIItem.MOVE_X) != 0) { mouseDragOffset.X = screenCoord.X - mouseDragAnchor.X; } if ((mouseDragHitTest & SelectionUIItem.MOVE_Y) != 0) { mouseDragOffset.Y = screenCoord.Y - mouseDragAnchor.Y; } if ((mouseDragHitTest & SelectionUIItem.SIZE_X) != 0) { if ((mouseDragHitTest & SelectionUIItem.POS_LEFT) != 0) { mouseDragOffset.X = screenCoord.X - mouseDragAnchor.X; mouseDragOffset.Width = mouseDragAnchor.X - screenCoord.X; } else { mouseDragOffset.Width = screenCoord.X - mouseDragAnchor.X; } } if ((mouseDragHitTest & SelectionUIItem.SIZE_Y) != 0) { if ((mouseDragHitTest & SelectionUIItem.POS_TOP) != 0) { mouseDragOffset.Y = screenCoord.Y - mouseDragAnchor.Y; mouseDragOffset.Height = mouseDragAnchor.Y - screenCoord.Y; } else { mouseDragOffset.Height = screenCoord.Y - mouseDragAnchor.Y; } } if (!old.Equals(mouseDragOffset)) { Rectangle delta = mouseDragOffset; delta.X -= old.X; delta.Y -= old.Y; delta.Width -= old.Width; delta.Height -= old.Height; if (delta.X != 0 || delta.Y != 0 || delta.Width != 0 || delta.Height != 0) { // Go to default cursor for moves... // if ((mouseDragHitTest & SelectionUIItem.MOVE_X) != 0 || (mouseDragHitTest & SelectionUIItem.MOVE_Y) != 0) { Cursor = Cursors.Default; } ((ISelectionUIService)this).DragMoved(delta); } } } } ////// /// Overrides Control to handle our selection grab handles. /// // Standard 'catch all - rethrow critical' exception pattern [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] [SuppressMessage("Microsoft.Security", "CA2102:CatchNonClsCompliantExceptionsInGeneralHandlers")] protected override void OnMouseUp(MouseEventArgs me) { try { bool wasDown = mouseDown; mouseDown = false; Point screenCoord = PointToScreen(new Point(me.X, me.Y)); if (ctrlSelect && !mouseDragging && selSvc != null) { HitTestInfo hti = GetHitTest(screenCoord, HITTEST_DEFAULT); selSvc.SetSelectedComponents(new object[] {hti.selectionUIHit.component}, SelectionTypes.Primary); } if (mouseDragging) { object oldContainerDrag = containerDrag; bool oldDragMoved = dragMoved; EndMouseDrag(screenCoord); if (((ISelectionUIService)this).Dragging) { ((ISelectionUIService)this).EndDrag(false); } if (me.Button == MouseButtons.Right && oldContainerDrag != null && !oldDragMoved) { OnContainerSelectorActive(new ContainerSelectorActiveEventArgs(oldContainerDrag, ContainerSelectorActiveEventArgsType.Contextmenu)); } } } catch(Exception e) { if (ClientUtils.IsCriticalException(e)) { throw; } else if (e != CheckoutException.Canceled) { DisplayError(e); } } } ////// /// If the selection manager move, this indicates that the form has autoscolling /// enabled and has been scrolled. We have to invalidate here because we may /// get moved before the rest of the components so we may draw the selection in /// the wrong spot. /// protected override void OnMove(EventArgs e) { base.OnMove(e); Invalidate(); } ////// /// overrides control.onPaint. here we paint the selection handles. The window's /// region was setup earlier. /// protected override void OnPaint(PaintEventArgs e) { // Paint the regular selection items first, and then the // container selectors last so they draw over the // top. // foreach(SelectionUIItem item in selectionItems.Values) { if (item is ContainerSelectionUIItem) { continue; } item.DoPaint(e.Graphics); } foreach(SelectionUIItem item in selectionItems.Values) { if (item is ContainerSelectionUIItem) { item.DoPaint(e.Graphics); } } } ////// /// Sets the appropriate selection cursor at the given point. /// private void SetSelectionCursor(Point pt) { Point clientCoords = PointToClient(pt); // We render the cursor in the same order we paint. // foreach(SelectionUIItem item in selectionItems.Values) { if (item is ContainerSelectionUIItem) { continue; } Cursor cursor = item.GetCursorAtPoint(clientCoords); if (cursor != null) { if (cursor == Cursors.Default) { Cursor = null; } else { Cursor = cursor; } return; } } foreach(SelectionUIItem item in selectionItems.Values) { if (item is ContainerSelectionUIItem) { Cursor cursor = item.GetCursorAtPoint(clientCoords); if (cursor != null) { if (cursor == Cursors.Default) { Cursor = null; } else { Cursor = cursor; } return; } } } // Don't know what to set; just use the default. // Cursor = null; } ////// /// called when the overlay region is invalid and should be updated /// private void UpdateWindowRegion() { Region region = new Region(new Rectangle(0, 0, 0, 0)); foreach(SelectionUIItem item in selectionItems.Values) { region.Union(item.GetRegion()); } Region = region; } ////// /// Override of our control's WNDPROC. We diddle with capture a bit, /// and it's important to turn this off if the capture changes. /// protected override void WndProc(ref Message m) { switch (m.Msg) { case NativeMethods.WM_LBUTTONUP: case NativeMethods.WM_RBUTTONUP: if (mouseDragAnchor != InvalidPoint) { ignoreCaptureChanged = true; } break; case NativeMethods.WM_CAPTURECHANGED: if (!ignoreCaptureChanged && mouseDragAnchor != InvalidPoint) { EndMouseDrag(MousePosition); if (((ISelectionUIService)this).Dragging) { ((ISelectionUIService)this).EndDrag(true); } } ignoreCaptureChanged = false; break; } base.WndProc(ref m); } ////// /// This can be used to determine if the user is in the middle of a drag operation. /// bool ISelectionUIService.Dragging { get { return dragHandler != null; } } ////// /// Determines if the selection UI is shown or not. /// /// bool ISelectionUIService.Visible { get { return Visible; } set { Visible = value; } } ////// /// Adds an event handler to the ContainerSelectorActive event. /// This event is fired whenever the user interacts with the container /// selector in a manor that would indicate that the selector should /// continued to be displayed. Since the container selector normally /// will vanish after a timeout, designers should listen to this event /// and reset the timeout when this event occurs. /// event ContainerSelectorActiveEventHandler ISelectionUIService.ContainerSelectorActive { add { containerSelectorActive += value; } remove { containerSelectorActive -= value; } } ////// /// Assigns a selection UI handler to a given component. The handler will be /// called when the UI service needs information about the component. A single /// selection UI handler can be assigned to multiple components. /// /// When multiple components are dragged, only a single handler may control the /// drag. Because of this, only components that are assigned the same handler /// as the primary selection are included in drag operations. /// /// A selection UI handler is automatically unassigned when the component is removed /// from the container or disposed. /// void ISelectionUIService.AssignSelectionUIHandler(object component, ISelectionUIHandler handler) { ISelectionUIHandler oldHandler = (ISelectionUIHandler)selectionHandlers[component]; if (oldHandler != null) { // ASURT #44582: The collection editors do not dispose objects from the // collection before setting a new collection. This causes items that are // common to the old and new collections to come through this code path // again, causing the exception to fire. So, we check to see if the SelectionUIHandler // is same, and bail out in that case. // if (handler == oldHandler) { return; } Debug.Fail("A component may have only one selection UI handler."); throw new InvalidOperationException(); } selectionHandlers[component] = handler; // If this component is selected, create a new UI handler for it. // if (selSvc != null && selSvc.GetComponentSelected(component)) { SelectionUIItem item = new SelectionUIItem(this, component); selectionItems[component] = item; UpdateWindowRegion(); item.Invalidate(); } } void ISelectionUIService.ClearSelectionUIHandler(object component, ISelectionUIHandler handler) { ISelectionUIHandler oldHandler = (ISelectionUIHandler)selectionHandlers[component]; if (oldHandler == handler) { selectionHandlers[component] = null; } } ////// /// This can be called by an outside party to begin a drag of the currently selected /// set of components. /// // Standard 'catch all - rethrow critical' exception pattern [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] [SuppressMessage("Microsoft.Security", "CA2102:CatchNonClsCompliantExceptionsInGeneralHandlers")] bool ISelectionUIService.BeginDrag(SelectionRules rules, int initialX, int initialY) { if (dragHandler != null) { Debug.Fail("Caller is starting a drag, but there is already one in progress -- we cannot nest these!"); return false; } if (rules == SelectionRules.None) { Debug.Fail("Caller is starting requesting a drag with no drag rules."); return false; } if (selSvc == null) { return false; } savedVisible = Visible; // First, get the list of controls // ICollection col = selSvc.GetSelectedComponents(); object[] objects = new object[col.Count]; col.CopyTo(objects, 0); objects = ((ISelectionUIService)this).FilterSelection(objects, rules); if (objects.Length == 0) { return false; // nothing selected } // We allow all components with the same UI handler as the primary selection // to participate in the drag. // ISelectionUIHandler primaryHandler = null; object primary = selSvc.PrimarySelection; if (primary != null) { primaryHandler = GetHandler(primary); } if (primaryHandler == null) { return false; // no UI handler for selection } // Now within the given selection, add those items that have the same // UI handler and that have the proper rule constraints. // ArrayList list = new ArrayList(); for (int i = 0; i < objects.Length; i++) { if (GetHandler(objects[i]) == primaryHandler) { SelectionRules compRules = primaryHandler.GetComponentRules(objects[i]); if ((compRules & rules) == rules) { list.Add(objects[i]); } } } if (list.Count == 0) { return false; // nothing matching the given constraints } objects = list.ToArray(); bool dragging = false; // We must setup state before calling QueryBeginDrag. It is possible // that QueryBeginDrag will cancel a drag (if it places a modal dialog, for // example), so we must have the drag data all setup before it cancels. Then, // we will check again after QueryBeginDrag to see if a cancel happened. // dragComponents = objects; dragRules = rules; dragHandler = primaryHandler; string transactionName = GetTransactionName(rules, objects); dragTransaction = host.CreateTransaction(transactionName); try { if (primaryHandler.QueryBeginDrag(objects, rules, initialX, initialY)) { if (dragHandler != null) { try { dragging = primaryHandler.BeginDrag(objects, rules, initialX, initialY); } catch (Exception e) { Debug.Fail("Drag handler threw during BeginDrag -- bad handler!", e.ToString()); dragging = false; } } } } finally { if (!dragging) { dragComponents = null; dragRules = 0; dragHandler = null; // Always commit this -- BeginDrag returns false for our drags because it is a // complete operation. if (dragTransaction != null) { dragTransaction.Commit(); dragTransaction = null; } } } return dragging; } ////// /// Called by an outside party to update drag information. This can only be called /// after a successful call to beginDrag. /// void ISelectionUIService.DragMoved(Rectangle offset) { Rectangle newOffset = Rectangle.Empty; if (dragHandler == null) { throw new Exception(SR.GetString(SR.DesignerBeginDragNotCalled)); } Debug.Assert(dragComponents != null, "We should have a set of drag controls here"); if ((dragRules & SelectionRules.Moveable) == SelectionRules.None && (dragRules & (SelectionRules.TopSizeable | SelectionRules.LeftSizeable)) == SelectionRules.None) { newOffset = new Rectangle(0, 0, offset.Width, offset.Height); } if ((dragRules & SelectionRules.AllSizeable) == SelectionRules.None) { if (newOffset.IsEmpty) { newOffset = new Rectangle(offset.X, offset.Y, 0, 0); } else { newOffset.Width = newOffset.Height = 0; } } if (!newOffset.IsEmpty) { offset = newOffset; } Visible = false; dragMoved = true; dragHandler.DragMoved(dragComponents, offset); } ////// /// Called by an outside party to finish a drag operation. This can only be called /// after a successful call to beginDrag. /// // Standard 'catch all - rethrow critical' exception pattern [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] [SuppressMessage("Microsoft.Security", "CA2102:CatchNonClsCompliantExceptionsInGeneralHandlers")] void ISelectionUIService.EndDrag(bool cancel) { containerDrag = null; ISelectionUIHandler handler = dragHandler; object[] components = dragComponents; // Clean these out so that even if we throw an exception we don't die. // dragHandler = null; dragComponents = null; dragRules = SelectionRules.None; if (handler == null) { throw new InvalidOperationException(); } // Typically, the handler will be changing a bunch of component properties here. // Optimize this by enclosing it within a batch call. // DesignerTransaction trans = null; try { IComponent comp = components[0] as IComponent; if (components.Length > 1 || (components.Length == 1 && comp != null && comp.Site == null)) { trans = host.CreateTransaction(SR.GetString(SR.DragDropMoveComponents, components.Length)); } else if (components.Length == 1) { if (comp != null) { trans = host.CreateTransaction(SR.GetString(SR.DragDropMoveComponent, comp.Site.Name)); } } try { handler.EndDrag(components, cancel); } catch (Exception e) { Debug.Fail(e.ToString()); } } finally { if (trans != null) trans.Commit(); // Reset the selection. This will re-display our selection. // Visible = savedVisible; ((ISelectionUIService)this).SyncSelection(); if (dragTransaction != null) { dragTransaction.Commit(); dragTransaction = null; } // In case this drag was initiated by us, ensure that our mouse state is correct // EndMouseDrag(MousePosition); } } ////// /// Filters the set of selected components. The selection service will retrieve all /// components that are currently selected. This method allows you to filter this /// set down to components that match your criteria. The selectionRules parameter /// must contain one or more flags from the SelectionRules class. These flags /// allow you to constrain the set of selected objects to visible, movable, /// sizeable or all objects. /// object[] ISelectionUIService.FilterSelection(object [] components, SelectionRules selectionRules) { object[] selection = null; if (components == null) return new object[0]; // Mask off any selection object that doesn't adhere to the given ruleset. // We can ignore this if the ruleset is zero, as all components would be accepted. // if (selectionRules != SelectionRules.None) { ArrayList list = new ArrayList(); foreach(object comp in components) { SelectionUIItem item = (SelectionUIItem)selectionItems[comp]; if (item != null && !(item is ContainerSelectionUIItem)) { if ((item.GetRules() & selectionRules) == selectionRules) { list.Add(comp); } } } selection = (object[])list.ToArray(); } return selection == null ? new object[0] : selection; } ////// /// Retrieves the width and height of a selection border grab handle. /// Designers may need this to properly position their user interfaces. /// Size ISelectionUIService.GetAdornmentDimensions(AdornmentType adornmentType) { switch (adornmentType) { case AdornmentType.GrabHandle: return new Size(SelectionUIItem.GRABHANDLE_WIDTH, SelectionUIItem.GRABHANDLE_HEIGHT); case AdornmentType.ContainerSelector: case AdornmentType.Maximum: return new Size(ContainerSelectionUIItem.CONTAINER_WIDTH, ContainerSelectionUIItem.CONTAINER_HEIGHT); } return new Size(0, 0); } ////// /// Tests to determine if the given screen coordinate is over an adornment /// for the specified component. This will only return true if the /// adornment, and selection UI, is visible. /// bool ISelectionUIService.GetAdornmentHitTest(object component, Point value) { if (GetHitTest(value, HITTEST_DEFAULT).hitTest != SelectionUIItem.NOHIT) { return true; } return false; } ////// /// Determines if the component is currently "container" selected. Container /// selection is a visual aid for selecting containers. It doesn't affect /// the normal "component" selection. /// bool ISelectionUIService.GetContainerSelected(object component) { return (component != null && selectionItems[component] is ContainerSelectionUIItem); } ////// /// Retrieves a set of flags that define rules for the selection. Selection /// rules indicate if the given component can be moved or sized, for example. /// SelectionRules ISelectionUIService.GetSelectionRules(object component) { SelectionUIItem sel = (SelectionUIItem)selectionItems[component]; if (sel == null) { Debug.Fail("The component is not currently selected."); throw new InvalidOperationException(); } return sel.GetRules(); } ////// /// Allows you to configure the style of the selection frame that a /// component uses. This is useful if your component supports different /// modes of operation (such as an in-place editing mode and a static /// design mode). Where possible, you should leave the selection style /// as is and use the design-time hit testing feature of the IDesigner /// interface to provide features at design time. The value of style /// must be one of the SelectionStyle enum values. /// /// The selection style is only valid for the duration that the component is /// selected. /// SelectionStyles ISelectionUIService.GetSelectionStyle(object component) { SelectionUIItem s = (SelectionUIItem)selectionItems[component]; if (s == null) { return SelectionStyles.None; } return s.Style; } ////// /// Changes the container selection status of the given component. /// Container selection is a visual aid for selecting containers. It /// doesn't affect the normal "component" selection. /// void ISelectionUIService.SetContainerSelected(object component, bool selected) { if (selected) { SelectionUIItem existingItem = (SelectionUIItem)selectionItems[component]; if (!(existingItem is ContainerSelectionUIItem)) { if (existingItem != null) { existingItem.Dispose(); } SelectionUIItem item = new ContainerSelectionUIItem(this, component); selectionItems[component] = item; // Now update our region and invalidate // UpdateWindowRegion(); if (existingItem != null) { existingItem.Invalidate(); } item.Invalidate(); } } else { SelectionUIItem existingItem = (SelectionUIItem)selectionItems[component]; if (existingItem == null || existingItem is ContainerSelectionUIItem) { selectionItems.Remove(component); if (existingItem != null) { existingItem.Dispose(); } UpdateWindowRegion(); existingItem.Invalidate(); } } } ////// /// Allows you to configure the style of the selection frame that a /// component uses. This is useful if your component supports different /// modes of operation (such as an in-place editing mode and a static /// design mode). Where possible, you should leave the selection style /// as is and use the design-time hit testing feature of the IDesigner /// interface to provide features at design time. The value of style /// must be one of the SelectionStyle enum values. /// /// The selection style is only valid for the duration that the component is /// selected. /// void ISelectionUIService.SetSelectionStyle(object component, SelectionStyles style) { SelectionUIItem selUI = (SelectionUIItem)selectionItems[component]; if (selSvc != null && selSvc.GetComponentSelected(component)) { selUI = new SelectionUIItem(this, component); selectionItems[component] = selUI; } if (selUI != null) { selUI.Style = style; UpdateWindowRegion(); selUI.Invalidate(); } } ////// /// This should be called when a component has been moved, sized or re-parented, /// but the change was not the result of a property change. All property /// changes are monitored by the selection UI service, so this is automatic most /// of the time. There are times, however, when a component may be moved without /// a property change notification occurring. Scrolling an auto scroll Win32 /// form is an example of this. /// /// This method simply re-queries all currently selected components for their /// bounds and udpates the selection handles for any that have changed. /// void ISelectionUIService.SyncSelection() { if (batchMode) { batchChanged = true; } else { if (IsHandleCreated) { bool updateRegion = false; foreach(SelectionUIItem item in selectionItems.Values) { updateRegion |= item.UpdateSize(); item.UpdateRules(); } if (updateRegion) { UpdateWindowRegion(); Update(); } } } } ////// /// This should be called when a component's property changed, that the designer /// thinks should result in a selection UI change. /// /// This method simply re-queries all currently selected components for their /// bounds and udpates the selection handles for any that have changed. /// void ISelectionUIService.SyncComponent(object component) { if (batchMode) { batchSync = true; } else { if (IsHandleCreated) { foreach(SelectionUIItem item in selectionItems.Values) { item.UpdateRules(); item.Dispose(); } UpdateWindowRegion(); Invalidate(); Update(); } } } ////// /// This class represents a single selected object. /// private class SelectionUIItem { // Flags describing how a given selection point may be sized // public const int SIZE_X = 0x0001; public const int SIZE_Y = 0x0002; public const int SIZE_MASK = 0x0003; // Flags describing how a given selection point may be moved // public const int MOVE_X = 0x0004; public const int MOVE_Y = 0x0008; public const int MOVE_MASK = 0x000C; // Flags describing where a given selection point is located on an object // public const int POS_LEFT = 0x0010; public const int POS_TOP = 0x0020; public const int POS_RIGHT = 0x0040; public const int POS_BOTTOM = 0x0080; public const int POS_MASK = 0x00F0; // This is returned if the given selection point is not within // the selection // public const int NOHIT = 0x0100; // This is returned if the given selection point on the "container selector" // public const int CONTAINER_SELECTOR = 0x0200; public const int GRABHANDLE_WIDTH = 7; public const int GRABHANDLE_HEIGHT = 7; // tables we use to determine how things can move and size // internal static readonly int[] activeSizeArray = new int[] { SIZE_X | SIZE_Y | POS_LEFT | POS_TOP, SIZE_Y | POS_TOP, SIZE_X | SIZE_Y | POS_TOP | POS_RIGHT, SIZE_X | POS_LEFT, SIZE_X | POS_RIGHT, SIZE_X | SIZE_Y | POS_LEFT | POS_BOTTOM, SIZE_Y | POS_BOTTOM, SIZE_X | SIZE_Y | POS_RIGHT | POS_BOTTOM }; internal static readonly Cursor[] activeCursorArrays = new Cursor[] { Cursors.SizeNWSE, Cursors.SizeNS, Cursors.SizeNESW, Cursors.SizeWE, Cursors.SizeWE, Cursors.SizeNESW, Cursors.SizeNS, Cursors.SizeNWSE }; internal static readonly int[] inactiveSizeArray = new int[] {0, 0, 0, 0, 0, 0, 0, 0}; internal static readonly Cursor[] inactiveCursorArray = new Cursor[] { Cursors.Arrow, Cursors.Arrow, Cursors.Arrow, Cursors.Arrow, Cursors.Arrow, Cursors.Arrow, Cursors.Arrow, Cursors.Arrow }; internal int[] sizes; // array of sizing rules for this selection internal Cursor[] cursors; // array of cursors for each grab location internal SelectionUIService selUIsvc; internal Rectangle innerRect = Rectangle.Empty; // inner part of selection (== control bounds) internal Rectangle outerRect = Rectangle.Empty; // outer part of selection (inner + border size) internal Region region; // region object that defines the shape internal object component; // the component we're rendering private Control control; private SelectionStyles selectionStyle; // how do we draw this thing? private SelectionRules selectionRules; private ISelectionUIHandler handler; // the components selection UI handler (can be null) ////// /// constructor /// /// Its ok to call virtual method as this is a private class. [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] public SelectionUIItem(SelectionUIService selUIsvc, object component) { this.selUIsvc = selUIsvc; this.component = component; selectionStyle = SelectionStyles.Selected; // By default, a component isn't visible. We must establish what // it can do through it's UI handler. // handler = selUIsvc.GetHandler(component); sizes = inactiveSizeArray; cursors = inactiveCursorArray; IComponent comp = component as IComponent; if (comp != null) { ControlDesigner cd = selUIsvc.host.GetDesigner(comp) as ControlDesigner; if (cd != null) { control = cd.Control; } } UpdateRules(); UpdateGrabSettings(); UpdateSize(); } ////// /// Retrieves the style of the selection frame for this selection. /// public virtual SelectionStyles Style { get { return selectionStyle;} set { if (value != selectionStyle) { selectionStyle = value; if (region != null) { region.Dispose(); region = null; } } } } ////// /// paints the selection /// public virtual void DoPaint(Graphics gr) { // If we're not visible, then there's nothing to do... // if ((GetRules() & SelectionRules.Visible) == SelectionRules.None) return; bool fActive = false; if (selUIsvc.selSvc != null) { fActive = component == selUIsvc.selSvc.PrimarySelection; // Office rules: If this is a multi-select, reverse the colors for active / inactive. // fActive = (fActive == (selUIsvc.selSvc.SelectionCount <= 1)); } Rectangle r = new Rectangle(outerRect.X, outerRect.Y, GRABHANDLE_WIDTH, GRABHANDLE_HEIGHT); Rectangle inner = innerRect; Rectangle outer = outerRect; Region oldClip = gr.Clip; Color borderColor = SystemColors.Control; if (control != null && control.Parent != null) { Control parent = control.Parent; borderColor = parent.BackColor; } Brush brush = new SolidBrush(borderColor); gr.ExcludeClip(inner); gr.FillRectangle(brush, outer); gr.Clip = oldClip; ControlPaint.DrawSelectionFrame( gr, false, outer, inner, borderColor ); //if it's not locked & it is sizeable... if (((GetRules() & SelectionRules.Locked) == SelectionRules.None) && (GetRules() & SelectionRules.AllSizeable) != SelectionRules.None) { // upper left ControlPaint.DrawGrabHandle(gr, r, fActive, (sizes[0] != 0)); // upper right r.X = inner.X + inner.Width; ControlPaint.DrawGrabHandle(gr, r, fActive, sizes[2] != 0); // lower right r.Y = inner.Y + inner.Height; ControlPaint.DrawGrabHandle(gr, r, fActive, sizes[7] != 0); // lower left r.X = outer.X; ControlPaint.DrawGrabHandle(gr, r, fActive, sizes[5] != 0); // lower middle r.X += (outer.Width - GRABHANDLE_WIDTH) / 2; ControlPaint.DrawGrabHandle(gr, r, fActive, sizes[6] != 0); // upper middle r.Y = outer.Y; ControlPaint.DrawGrabHandle(gr, r, fActive, sizes[1] != 0); // left middle r.X = outer.X; r.Y = inner.Y + (inner.Height - GRABHANDLE_HEIGHT) / 2; ControlPaint.DrawGrabHandle(gr, r, fActive, sizes[3] != 0); // right middle r.X = inner.X + inner.Width; ControlPaint.DrawGrabHandle(gr, r, fActive, sizes[4] != 0); } else { ControlPaint.DrawLockedFrame(gr, outer, fActive); } } ////// /// Retrieves an appropriate cursor at the given point. If there is no appropriate /// cursor here (ie, the point lies outside the selection rectangle), then this /// will return null. /// public virtual Cursor GetCursorAtPoint(Point pt) { Cursor cursor = null; if (PointWithinSelection(pt)) { int nOffset = -1; if ((GetRules() & SelectionRules.AllSizeable) != SelectionRules.None) { nOffset = GetHandleIndexOfPoint(pt); } if (-1 == nOffset) { if ((GetRules() & SelectionRules.Moveable) == SelectionRules.None) { cursor = Cursors.Default; } else { cursor = Cursors.SizeAll; } } else { cursor = cursors[nOffset]; } } return cursor; } ////// /// returns the hit test code of the given point. This may be one of: /// public virtual int GetHitTest(Point pt) { // Is it within our rects? // if (!PointWithinSelection(pt)) { return NOHIT; } // Which index in the array is this? // int nOffset = GetHandleIndexOfPoint(pt); // If no index, the user has picked on the hatch // if (-1 == nOffset || sizes[nOffset] == 0) { return((GetRules() & SelectionRules.Moveable) == SelectionRules.None ? 0 : MOVE_X | MOVE_Y); } return sizes[nOffset]; } ////// /// gets the array offset of the handle at the given point /// private int GetHandleIndexOfPoint(Point pt) { if (pt.X >= outerRect.X && pt.X <= innerRect.X) { // Something on the left side. if (pt.Y >= outerRect.Y && pt.Y <= innerRect.Y) return 0; // top left if (pt.Y >= innerRect.Y + innerRect.Height && pt.Y <= outerRect.Y + outerRect.Height) return 5; // bottom left if (pt.Y >= outerRect.Y + (outerRect.Height - GRABHANDLE_HEIGHT) / 2 && pt.Y <= outerRect.Y + (outerRect.Height + GRABHANDLE_HEIGHT) / 2) return 3; // middle left return -1; // unknown hit } if (pt.Y >= outerRect.Y && pt.Y <= innerRect.Y) { // something on the top Debug.Assert(!(pt.X >= outerRect.X && pt.X <= innerRect.X), "Should be handled by left top check"); if (pt.X >= innerRect.X + innerRect.Width && pt.X <= outerRect.X + outerRect.Width) return 2; // top right if (pt.X >= outerRect.X + (outerRect.Width - GRABHANDLE_WIDTH) / 2 && pt.X <= outerRect.X + (outerRect.Width + GRABHANDLE_WIDTH) / 2) return 1; // top middle return -1; // unknown hit } if (pt.X >= innerRect.X + innerRect.Width && pt.X <= outerRect.X + outerRect.Width) { // something on the right side Debug.Assert(!(pt.Y >= outerRect.Y && pt.Y <= innerRect.Y), "Should be handled by top right check"); if (pt.Y >= innerRect.Y + innerRect.Height && pt.Y <= outerRect.Y + outerRect.Height) return 7; // bottom right if (pt.Y >= outerRect.Y + (outerRect.Height - GRABHANDLE_HEIGHT) / 2 && pt.Y <= outerRect.Y + (outerRect.Height + GRABHANDLE_HEIGHT) / 2) return 4; // middle right return -1; // unknown hit } if (pt.Y >= innerRect.Y + innerRect.Height && pt.Y <= outerRect.Y + outerRect.Height) { // something on the bottom Debug.Assert(!(pt.X >= outerRect.X && pt.X <= innerRect.X), "Should be handled by left bottom check"); Debug.Assert(!(pt.X >= innerRect.X + innerRect.Width && pt.X <= outerRect.X + outerRect.Width), "Should be handled by right bottom check"); if (pt.X >= outerRect.X + (outerRect.Width - GRABHANDLE_WIDTH) / 2 && pt.X <= outerRect.X + (outerRect.Width + GRABHANDLE_WIDTH) / 2) return 6; // bottom middle return -1; // unknown hit } return -1; // unknown hit } ////// /// returns a region handle that defines this selection. This is used to piece /// together a paint region for the surface that we draw our selection handles on /// public virtual Region GetRegion() { if (region == null) { if ((GetRules() & SelectionRules.Visible) != SelectionRules.None && !outerRect.IsEmpty) { region = new Region(outerRect); region.Exclude(innerRect); } else { region = new Region(new Rectangle(0, 0, 0, 0)); } if (handler != null) { Rectangle handlerClip = handler.GetSelectionClipRect(component); if (!handlerClip.IsEmpty) { region.Intersect(selUIsvc.RectangleToClient(handlerClip)); } } } return region; } ////// /// Retrieves the rules associated with this selection. /// public SelectionRules GetRules() { return selectionRules; } public void Dispose() { if (region != null) { region.Dispose(); region = null; } } ////// /// Invalidates the region for this selection glyph. /// public void Invalidate() { if (!outerRect.IsEmpty && !selUIsvc.Disposing) { selUIsvc.Invalidate(outerRect); } } ////// /// Part of our hit testing logic; determines if the point is somewhere /// within our selection. /// protected bool PointWithinSelection(Point pt) { // This is only supported for visible selections // if ((GetRules() & SelectionRules.Visible) == SelectionRules.None || outerRect.IsEmpty || innerRect.IsEmpty) { return false; } if (pt.X < outerRect.X || pt.X > outerRect.X + outerRect.Width) { return false; } if (pt.Y < outerRect.Y || pt.Y > outerRect.Y + outerRect.Height) { return false; } if (pt.X > innerRect.X && pt.X < innerRect.X + innerRect.Width && pt.Y > innerRect.Y && pt.Y < innerRect.Y + innerRect.Height) { return false; } return true; } ////// /// Updates the available grab handle settings based on the current rules. /// private void UpdateGrabSettings() { SelectionRules rules = GetRules(); if ((rules & SelectionRules.AllSizeable) == SelectionRules.None) { sizes = inactiveSizeArray; cursors = inactiveCursorArray; } else { sizes = new int[8]; cursors = new Cursor[8]; Array.Copy(activeCursorArrays, cursors, cursors.Length); Array.Copy(activeSizeArray, sizes, sizes.Length); if ((rules & SelectionRules.TopSizeable) != SelectionRules.TopSizeable) { sizes[0] = 0; sizes[1] = 0; sizes[2] = 0; cursors[0] = Cursors.Arrow; cursors[1] = Cursors.Arrow; cursors[2] = Cursors.Arrow; } if ((rules & SelectionRules.LeftSizeable) != SelectionRules.LeftSizeable) { sizes[0] = 0; sizes[3] = 0; sizes[5] = 0; cursors[0] = Cursors.Arrow; cursors[3] = Cursors.Arrow; cursors[5] = Cursors.Arrow; } if ((rules & SelectionRules.BottomSizeable) != SelectionRules.BottomSizeable) { sizes[5] = 0; sizes[6] = 0; sizes[7] = 0; cursors[5] = Cursors.Arrow; cursors[6] = Cursors.Arrow; cursors[7] = Cursors.Arrow; } if ((rules & SelectionRules.RightSizeable) != SelectionRules.RightSizeable) { sizes[2] = 0; sizes[4] = 0; sizes[7] = 0; cursors[2] = Cursors.Arrow; cursors[4] = Cursors.Arrow; cursors[7] = Cursors.Arrow; } } } ////// /// Updates our cached selection rules based on current /// handler values. /// public void UpdateRules() { if (handler == null) { selectionRules = SelectionRules.None; } else { SelectionRules oldRules = selectionRules; selectionRules = handler.GetComponentRules(component); if (selectionRules != oldRules) { UpdateGrabSettings(); Invalidate(); } } } ////// /// rebuilds the inner and outer rectangles based on the current /// selItem.component dimensions. We could calcuate this every time, but that /// would be expensive for functions like getHitTest that are called a lot /// (like on every mouse move) /// public virtual bool UpdateSize() { bool sizeChanged = false; // Short circuit common cases // if (handler == null) return false; if ((GetRules() & SelectionRules.Visible) ==SelectionRules.None) return false; innerRect = handler.GetComponentBounds(component); if (!innerRect.IsEmpty) { innerRect = selUIsvc.RectangleToClient(innerRect); Rectangle rcOuterNew = new Rectangle( innerRect.X - GRABHANDLE_WIDTH, innerRect.Y - GRABHANDLE_HEIGHT, innerRect.Width + 2 * GRABHANDLE_WIDTH, innerRect.Height + 2 * GRABHANDLE_HEIGHT); if (outerRect.IsEmpty || !outerRect.Equals(rcOuterNew)) { if (!outerRect.IsEmpty) Invalidate(); outerRect = rcOuterNew; Invalidate(); if (region != null) { region.Dispose(); region = null; } sizeChanged = true; } } else { Rectangle rcNew = new Rectangle(0, 0, 0, 0); sizeChanged = outerRect.IsEmpty || !outerRect.Equals(rcNew); innerRect = outerRect = rcNew; } return sizeChanged; } } private class ContainerSelectionUIItem : SelectionUIItem { public const int CONTAINER_WIDTH = 13; public const int CONTAINER_HEIGHT = 13; ////// /// constructor /// public ContainerSelectionUIItem(SelectionUIService selUIsvc, object component) : base(selUIsvc, component) { } public override Cursor GetCursorAtPoint(Point pt) { if ((GetHitTest(pt) & CONTAINER_SELECTOR) != 0 && (GetRules() & SelectionRules.Moveable) != SelectionRules.None) { return Cursors.SizeAll; } else { return null; } } public override int GetHitTest(Point pt) { int ht = NOHIT; if ((GetRules() & SelectionRules.Visible) != SelectionRules.None && !outerRect.IsEmpty) { Rectangle r = new Rectangle(outerRect.X, outerRect.Y, CONTAINER_WIDTH, CONTAINER_HEIGHT); if (r.Contains(pt)) { ht = CONTAINER_SELECTOR; if ((GetRules() & SelectionRules.Moveable) != SelectionRules.None) { ht |= MOVE_X | MOVE_Y; } } } return ht; } public override void DoPaint(Graphics gr) { // If we're not visible, then there's nothing to do... // if ((GetRules() & SelectionRules.Visible) == SelectionRules.None) return; Rectangle glyphBounds = new Rectangle(outerRect.X, outerRect.Y, CONTAINER_WIDTH, CONTAINER_HEIGHT); ControlPaint.DrawContainerGrabHandle(gr, glyphBounds); } public override Region GetRegion() { if (region == null) { if ((GetRules() & SelectionRules.Visible) != SelectionRules.None && !outerRect.IsEmpty) { Rectangle r = new Rectangle(outerRect.X, outerRect.Y, CONTAINER_WIDTH, CONTAINER_HEIGHT); region = new Region(r); } else { region = new Region(new Rectangle(0, 0, 0, 0)); } } return region; } } private struct HitTestInfo { public readonly int hitTest; public readonly SelectionUIItem selectionUIHit; public readonly bool containerSelector; public HitTestInfo(int hitTest, SelectionUIItem selectionUIHit) { this.hitTest = hitTest; this.selectionUIHit = selectionUIHit; this.containerSelector = false; } public HitTestInfo(int hitTest, SelectionUIItem selectionUIHit, bool containerSelector) { this.hitTest = hitTest; this.selectionUIHit = selectionUIHit; this.containerSelector = containerSelector; } // Standard 'catch all - rethrow critical' exception pattern [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] [SuppressMessage("Microsoft.Security", "CA2102:CatchNonClsCompliantExceptionsInGeneralHandlers")] public override bool Equals(object obj) { try { HitTestInfo hi = (HitTestInfo)obj; return hitTest == hi.hitTest && selectionUIHit == hi.selectionUIHit && containerSelector == hi.containerSelector; } catch (Exception ex) { if (ClientUtils.IsCriticalException(ex)) { throw; } } return false; } public static bool operator ==(HitTestInfo left, HitTestInfo right) { return (left.hitTest == right.hitTest && left.selectionUIHit == right.selectionUIHit && left.containerSelector == right.containerSelector); } public static bool operator !=(HitTestInfo left, HitTestInfo right) { return !(left == right); } public override int GetHashCode() { int hash = hitTest | selectionUIHit.GetHashCode(); if (containerSelector) { hash |= 0x10000; } return hash; } } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- LogAppendAsyncResult.cs
- StringUtil.cs
- AsyncResult.cs
- CryptoHandle.cs
- documentation.cs
- DESCryptoServiceProvider.cs
- XamlToRtfWriter.cs
- QuaternionKeyFrameCollection.cs
- PageAdapter.cs
- SourceItem.cs
- PropertyChangedEventArgs.cs
- AnimationStorage.cs
- TextElementAutomationPeer.cs
- StringBlob.cs
- GatewayDefinition.cs
- DataMisalignedException.cs
- UIElement3D.cs
- ShaderRenderModeValidation.cs
- TimerEventSubscription.cs
- GradientStop.cs
- OdbcStatementHandle.cs
- BooleanKeyFrameCollection.cs
- Run.cs
- ConstructorExpr.cs
- UnsafeNativeMethods.cs
- SqlDependency.cs
- ReversePositionQuery.cs
- DocobjHost.cs
- URI.cs
- ControlDesigner.cs
- VBIdentifierTrimConverter.cs
- ClientFormsAuthenticationMembershipProvider.cs
- SystemWebExtensionsSectionGroup.cs
- RepeatEnumerable.cs
- XomlCompilerError.cs
- EmptyEnumerator.cs
- WebPartMenu.cs
- SafeNativeMethods.cs
- MimeReturn.cs
- OdbcConnectionString.cs
- TextEditorLists.cs
- BoolLiteral.cs
- SingleAnimationUsingKeyFrames.cs
- ApplicationSettingsBase.cs
- Span.cs
- FormsAuthenticationTicket.cs
- MouseOverProperty.cs
- HtmlTableRowCollection.cs
- Delegate.cs
- UpdateCommand.cs
- DrawingAttributeSerializer.cs
- HashAlgorithm.cs
- CatalogPartChrome.cs
- TextTreeDeleteContentUndoUnit.cs
- WebPartMenuStyle.cs
- UnhandledExceptionEventArgs.cs
- XamlTypeMapperSchemaContext.cs
- ThemeDirectoryCompiler.cs
- ACL.cs
- WindowsStatusBar.cs
- HashLookup.cs
- AsyncOperation.cs
- MutexSecurity.cs
- PreApplicationStartMethodAttribute.cs
- ScriptResourceMapping.cs
- ConditionalAttribute.cs
- HttpWriter.cs
- RoutedEventValueSerializer.cs
- SwitchLevelAttribute.cs
- InfoCardMasterKey.cs
- EntityDataSourceWrapper.cs
- HtmlGenericControl.cs
- TakeOrSkipWhileQueryOperator.cs
- IsolationInterop.cs
- LabelDesigner.cs
- ExpandoClass.cs
- WebPartTracker.cs
- DesignRelation.cs
- RuntimeConfigurationRecord.cs
- DataRecordInfo.cs
- RuleSetBrowserDialog.cs
- _StreamFramer.cs
- ChildChangedEventArgs.cs
- DecimalFormatter.cs
- CodeTypeMember.cs
- FontUnitConverter.cs
- LinkConverter.cs
- MultiAsyncResult.cs
- WinCategoryAttribute.cs
- Point3DCollectionValueSerializer.cs
- BaseCollection.cs
- XPathAxisIterator.cs
- SQLSingle.cs
- TagPrefixCollection.cs
- GACIdentityPermission.cs
- RequestCache.cs
- SerializationInfoEnumerator.cs
- HtmlFormWrapper.cs
- XMLDiffLoader.cs
- MultipartContentParser.cs