Code:
/ DotNET / DotNET / 8.0 / untmp / whidbey / REDBITS / ndp / fx / src / Designer / WinForms / System / WinForms / Design / DesignerActionUI.cs / 2 / DesignerActionUI.cs
namespace System.Windows.Forms.Design {
using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Design;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Windows.Forms.Design.Behavior;
using System.Text;
///
///
/// The DesignerActionUI is the designer/UI-specific implementation of the
//// DesignerActions feature. This class instantiates the DesignerActionService
/// and hooks to its DesignerActionsChanged event. Responding to this single
/// event will enable the DesignerActionUI to perform all neceessary UI-related
/// operations.
/// Note that the DesignerActionUI uses the BehaviorService to manage all UI
/// interaction. For every component containing a DesignerAction (determined
/// by the DesignerActionsChagned event) there will be an associated
/// DesignerActionGlyph and DesignerActionBehavior.
/// Finally, the DesignerActionUI is also responsible for showing and managing
/// the Action's context menus. Note that every DesignerAction context menu has
/// an item that will bring up the DesignerActions option pane in the options
/// dialog.
///
internal class DesignerActionUI : IDisposable {
private static TraceSwitch DesigneActionPanelTraceSwitch = new TraceSwitch("DesigneActionPanelTrace", "DesignerActionPanel tracing");
private Adorner designerActionAdorner;//used to add designeraction-related glyphs
private IServiceProvider serviceProvider;//standard service provider
private ISelectionService selSvc;//used to determine if comps have selection or not
private DesignerActionService designerActionService;//this is how all designeractions will be managed
private DesignerActionUIService designerActionUIService;//this is how all designeractions UI elements will be managed
private BehaviorService behaviorService;//this is how all of our UI is implemented (glyphs, behaviors, etc...)
private IMenuCommandService menuCommandService;
private DesignerActionKeyboardBehavior dapkb; //out keyboard behavior
private Hashtable componentToGlyph;//used for quick reference between compoments and our glyphs
private Control marshalingControl;//used to invoke events on our main gui thread
private IComponent lastPanelComponent;
private IUIService uiService;
private IWin32Window mainParentWindow;
internal DesignerActionToolStripDropDown designerActionHost;
private MenuCommand cmdShowDesignerActions;//used to respond to the Alt+Shft+F10 command
private bool inTransaction = false;
private IComponent relatedComponentTransaction;
private DesignerActionGlyph relatedGlyphTransaction;
private bool disposeActionService;
private bool disposeActionUIService;
private delegate void ActionChangedEventHandler(object sender, DesignerActionListsChangedEventArgs e);
#if DEBUG
internal static readonly TraceSwitch DropDownVisibilityDebug = new TraceSwitch("DropDownVisibilityDebug", "Debug ToolStrip Selection code");
#else
internal static readonly TraceSwitch DropDownVisibilityDebug;
#endif
///
///
/// Constructor that takes a service provider. This is needed to establish
/// references to the BehaviorService and SelecteionService, as well as
/// spin-up the DesignerActionService.
///
public DesignerActionUI(IServiceProvider serviceProvider, Adorner containerAdorner) {
this.serviceProvider = serviceProvider;
this.designerActionAdorner = containerAdorner;
behaviorService = (BehaviorService)serviceProvider.GetService(typeof(BehaviorService));
menuCommandService = (IMenuCommandService)serviceProvider.GetService(typeof(IMenuCommandService));
selSvc = (ISelectionService)serviceProvider.GetService(typeof(ISelectionService));
if (behaviorService == null || selSvc == null) {
Debug.Fail("Either BehaviorService or ISelectionService is null, cannot continue.");
return;
}
//query for our DesignerActionService
designerActionService = (DesignerActionService)serviceProvider.GetService(typeof(DesignerActionService));
if (designerActionService == null) {
//start the service
designerActionService = new DesignerActionService(serviceProvider);
disposeActionService = true;
}
designerActionUIService = (DesignerActionUIService)serviceProvider.GetService(typeof(DesignerActionUIService));
if (designerActionUIService == null) {
designerActionUIService = new DesignerActionUIService(serviceProvider);
disposeActionUIService = true;
}
designerActionUIService.DesignerActionUIStateChange += new DesignerActionUIStateChangeEventHandler(this.OnDesignerActionUIStateChange);
designerActionService.DesignerActionListsChanged += new DesignerActionListsChangedEventHandler(this.OnDesignerActionsChanged);
lastPanelComponent = null;
IComponentChangeService cs = (IComponentChangeService)serviceProvider.GetService(typeof(IComponentChangeService));
if (cs != null) {
cs.ComponentChanged += new ComponentChangedEventHandler(this.OnComponentChanged);
}
if (menuCommandService != null) {
cmdShowDesignerActions = new MenuCommand(new EventHandler(OnKeyShowDesignerActions), MenuCommands.KeyInvokeSmartTag);
menuCommandService.AddCommand(cmdShowDesignerActions);
}
uiService = (IUIService)serviceProvider.GetService(typeof(IUIService));
if(uiService != null)
mainParentWindow = uiService.GetDialogOwnerWindow();
componentToGlyph = new Hashtable();
marshalingControl = new Control();
marshalingControl.CreateControl();
}
///
///
/// Disposes all UI-related objects and unhooks services.
///
// Don't need to dispose of designerActionUIService.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed")]
public void Dispose() {
if (marshalingControl != null) {
marshalingControl.Dispose();
marshalingControl = null;
}
if (serviceProvider != null) {
IComponentChangeService cs = (IComponentChangeService)serviceProvider.GetService(typeof(IComponentChangeService));
if (cs != null) {
cs.ComponentChanged -= new ComponentChangedEventHandler(this.OnComponentChanged);
}
if (cmdShowDesignerActions != null) {
IMenuCommandService mcs = (IMenuCommandService)serviceProvider.GetService(typeof(IMenuCommandService));
if (mcs != null) {
mcs.RemoveCommand(cmdShowDesignerActions);
}
}
}
serviceProvider = null;
behaviorService = null;
selSvc = null;
if (designerActionService != null) {
designerActionService.DesignerActionListsChanged -= new DesignerActionListsChangedEventHandler(this.OnDesignerActionsChanged);
if (disposeActionService) {
designerActionService.Dispose();
}
}
designerActionService = null;
if (designerActionUIService != null) {
designerActionUIService.DesignerActionUIStateChange -= new DesignerActionUIStateChangeEventHandler(this.OnDesignerActionUIStateChange);
if (disposeActionUIService) {
designerActionUIService.Dispose();
}
}
designerActionUIService = null;
designerActionAdorner = null;
}
public DesignerActionGlyph GetDesignerActionGlyph(IComponent comp) {
return GetDesignerActionGlyph(comp, null);
}
internal DesignerActionGlyph GetDesignerActionGlyph(IComponent comp, DesignerActionListCollection dalColl) {
// check this component origin, this class or is it readyonly because inherited...
InheritanceAttribute attribute = (InheritanceAttribute)TypeDescriptor.GetAttributes(comp)[typeof(InheritanceAttribute)];
if(attribute == InheritanceAttribute.InheritedReadOnly) { // only do it if we can change the control...
return null;
}
// we didnt get on, fetch it
if(dalColl == null) {
dalColl = designerActionService.GetComponentActions(comp);
}
if(dalColl!= null && dalColl.Count > 0) {
DesignerActionGlyph dag = null;
if(componentToGlyph[comp] == null) {
DesignerActionBehavior dab = new DesignerActionBehavior(serviceProvider, comp, dalColl, this);
//if comp is a component then try to find a traycontrol associated with it...
// this should really be in ComponentTray but there is no behaviorService for the CT
if (!(comp is Control) || comp is ToolStripDropDown) {
//Here, we'll try to get the traycontrol associated with
//the comp and supply the glyph with an alternative bounds
ComponentTray compTray = serviceProvider.GetService(typeof(ComponentTray)) as ComponentTray;
if (compTray != null) {
ComponentTray.TrayControl trayControl = compTray.GetTrayControlFromComponent(comp);
if (trayControl != null) {
Rectangle trayBounds = trayControl.Bounds;
dag = new DesignerActionGlyph(dab, trayBounds, compTray);
}
}
}
//either comp is a control or we failed to find a traycontrol (which could be the case
//for toolstripitem components) - in this case just create a standard glyoh.
if (dag == null) {
//if the related comp is a control, then this shortcut will just hang off its bounds
dag = new DesignerActionGlyph(dab, designerActionAdorner);
}
if (dag != null) {
//store off this relationship
componentToGlyph.Add(comp, dag);
}
}
else {
dag = componentToGlyph[comp] as DesignerActionGlyph;
if (dag != null) {
DesignerActionBehavior behavior = dag.Behavior as DesignerActionBehavior;
if (behavior != null) {
behavior.ActionLists = dalColl;
}
dag.Invalidate(); // need to invalidate here too, someone could have called refresh too soon,
//causing the glyph to get created in the wrong place
}
}
return dag;
} else {
// the list is now empty... remove the panel and glyph for this control
RemoveActionGlyph(comp);
return null;
}
}
///
///
/// We monitor this event so we can update smart tag locations when
/// controls move.
///
private void OnComponentChanged(object source, ComponentChangedEventArgs ce) {
//validate event args
if (ce.Component == null || ce.Member == null || !IsDesignerActionPanelVisible) {
return;
}
// VSWhidbey 497545.
// If the smart tag is showing, we only move the smart tag if the changing
// component is the component for the currently showing smart tag.
if (lastPanelComponent != null && !lastPanelComponent.Equals(ce.Component)) {
return;
}
//if something changed on a component we have actions associated with
//then invalidate all (repaint & reposition)
DesignerActionGlyph glyph = componentToGlyph[ce.Component] as DesignerActionGlyph;
if (glyph != null) {
glyph.Invalidate();
if(ce.Member.Name.Equals("Dock")) { // this is the only case were we don't require an explicit refresh
RecreatePanel(ce.Component as IComponent); // because 99% of the time the action is name "dock in parent container" and get replaced by "undock"
}
if (ce.Member.Name.Equals("Location") ||
ce.Member.Name.Equals("Width") ||
ce.Member.Name.Equals("Height")) {
// we don't need to regen, we just need to update location
// calculate the position of the form hosting the panel
UpdateDAPLocation(ce.Component as IComponent, glyph);
}
}
}
private void RecreatePanel(IComponent comp) {
if(inTransaction || comp != selSvc.PrimarySelection) { //we only ever need to do that when the comp is the primary selection
return;
}
// we check wether or not we're in a transaction, if we are, we only the refresh at the
// end of the transaction to avoid flicker.
IDesignerHost host = serviceProvider.GetService(typeof(IDesignerHost)) as IDesignerHost;
if(host!=null) {
bool hostIsClosingTransaction = false;
IDesignerHostTransactionState hostTransactionState = host as IDesignerHostTransactionState;
if (hostTransactionState != null)
{
hostIsClosingTransaction = hostTransactionState.IsClosingTransaction;
}
if (host.InTransaction && !hostIsClosingTransaction)
{
//Debug.WriteLine("In transaction, bail, but first hookup to the end of the transaction...");
host.TransactionClosed += new DesignerTransactionCloseEventHandler(this.DesignerTransactionClosed);
inTransaction = true;
relatedComponentTransaction = comp;
return;
}
}
RecreateInternal(comp);
}
private void DesignerTransactionClosed(object sender, DesignerTransactionCloseEventArgs e) {
if(e.LastTransaction && relatedComponentTransaction != null) {
// surprise surprise we can get multiple even with e.LastTransaction set to true, even though we unhook here
// this is because the list on which we enumerate (the event handler list) is copied before it's enumerated on
// which means that if the undo engine for example creates and commit a transaction during the OnCancel of another
// completed transaction we will get this twice. So we have to check also for relatedComponentTransaction != null
inTransaction = false;
//Debug.WriteLine("End of the transaction, refresh...");
IDesignerHost host = serviceProvider.GetService(typeof(IDesignerHost)) as IDesignerHost;
host.TransactionClosed -= new DesignerTransactionCloseEventHandler(this.DesignerTransactionClosed);
//Debug.WriteLine("End of the transaction, refresh... on component");
RecreateInternal(relatedComponentTransaction);
relatedComponentTransaction = null;
}
}
private void RecreateInternal(IComponent comp) {
//Debug.WriteLine("not in a transaction, do it now!");
DesignerActionGlyph glyph = GetDesignerActionGlyph(comp);
if (glyph != null) {
//Debug.WriteLine("Recreating panel for component " + comp.Site.Name);
VerifyGlyphIsInAdorner(glyph); // this could happen when a verb change state or suddendly a control gets
// a new action in the panel and we are the primary selection
// in that case there would not be a glyph active in the adorner to be shown
// because we update that on selection change. We have to do that here too. Sad really...
RecreatePanel(glyph); // recreate the DAP itself
UpdateDAPLocation(comp, glyph); // reposition the thing
}
}
private void RecreatePanel(Glyph glyphWithPanelToRegen) {
// we don't want to do anything if the panel is not visible
if(!IsDesignerActionPanelVisible) {
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "[DesignerActionUI.RecreatePanel] panel is not visible, bail");
return;
}
//recreate a designeraction panel
if(glyphWithPanelToRegen != null) {
DesignerActionBehavior behaviorWithPanelToRegen = glyphWithPanelToRegen.Behavior as DesignerActionBehavior;
if(behaviorWithPanelToRegen!= null) {
//DesignerActionPanel dap = behaviorWithPanelToRegen.CreateDesignerActionPanel(behaviorWithPanelToRegen.RelatedComponent);
//designerActionHost.SetDesignerActionPanel(dap, glyphWithPanelToRegen);
Debug.Assert(behaviorWithPanelToRegen.RelatedComponent != null, "could not find related component for this refresh");
DesignerActionPanel dap = designerActionHost.CurrentPanel; // WE DO NOT RECREATE THE WHOLE THING / WE UPDATE THE TASKS - should flicker less
dap.UpdateTasks(behaviorWithPanelToRegen.ActionLists, new DesignerActionListCollection(), SR.GetString(SR.DesignerActionPanel_DefaultPanelTitle,
behaviorWithPanelToRegen.RelatedComponent.GetType().Name), null);
designerActionHost.UpdateContainerSize();
}
}
}
private void VerifyGlyphIsInAdorner(DesignerActionGlyph glyph) {
if (glyph.IsInComponentTray) {
ComponentTray compTray = serviceProvider.GetService(typeof(ComponentTray)) as ComponentTray;
if(compTray.SelectionGlyphs != null && !compTray.SelectionGlyphs.Contains(glyph)) {
compTray.SelectionGlyphs.Insert(0, glyph);
}
} else {
if(designerActionAdorner != null && designerActionAdorner.Glyphs != null && !designerActionAdorner.Glyphs.Contains(glyph)) {
designerActionAdorner.Glyphs.Insert(0,glyph);
}
}
glyph.InvalidateOwnerLocation();
}
///
/// This event is fired by the DesignerActionService in response
/// to a DesignerActionCollection changing. The event args contains
/// information about the related object, the type of change (added
/// or removed) and the remaining DesignerActionCollection for the
/// object.
/// Note that when new DesignerActions are added, if the related control
/// is not yet parented - we add these actions to a "delay" list and they
/// are later created when the control is finally parented.
///
private void OnDesignerActionsChanged(object sender, DesignerActionListsChangedEventArgs e) {
// We need to invoke this async because the designer action service will
// raise this event from the thread pool.
if (marshalingControl != null && marshalingControl.IsHandleCreated) {
marshalingControl.BeginInvoke(new ActionChangedEventHandler(OnInvokedDesignerActionChanged), new object[] {sender, e});
}
}
private void OnDesignerActionUIStateChange(object sender, DesignerActionUIStateChangeEventArgs e) {
IComponent comp = e.RelatedObject as IComponent;
Debug.Assert(comp!=null || e.ChangeType == DesignerActionUIStateChangeType.Hide, "related object is not an IComponent, something is wrong here...");
if(comp!=null) {
DesignerActionGlyph relatedGlyph = GetDesignerActionGlyph(comp);
if(relatedGlyph != null) {
if(e.ChangeType == DesignerActionUIStateChangeType.Show) {
DesignerActionBehavior behavior = relatedGlyph.Behavior as DesignerActionBehavior;
if(behavior != null) {
behavior.ShowUI(relatedGlyph);
}
} else if (e.ChangeType == DesignerActionUIStateChangeType.Hide){
DesignerActionBehavior behavior = relatedGlyph.Behavior as DesignerActionBehavior;
if(behavior != null) {
behavior.HideUI();
}
} else if (e.ChangeType == DesignerActionUIStateChangeType.Refresh) {
relatedGlyph.Invalidate();
RecreatePanel((IComponent)e.RelatedObject);
/*BehaviorService.MenuCommandHandler mch = menuCommandService as BehaviorService.MenuCommandHandler;
if(mch != null && mch.MenuService != null) {
MenuCommandService mcs = mch.MenuService as MenuCommandService;
if(mcs != null) {
mcs.InvalidateVerbsCollection();
} else {
Debug.Fail("Could not find our way to the real MenuCommandService");
}
} else {
Debug.Fail("Could not find our way to the real MenuCommandService");
}*/
}
}
} else {
if (e.ChangeType == DesignerActionUIStateChangeType.Hide){
HideDesignerActionPanel();
}
}
}
///
/// This is the same as DesignerActionChanged, but it is invoked on our control's thread
///
private void OnInvokedDesignerActionChanged(object sender, DesignerActionListsChangedEventArgs e) {
IComponent relatedComponent = e.RelatedObject as IComponent;
DesignerActionGlyph g = null;
if (e.ChangeType == DesignerActionListsChangedType.ActionListsAdded) {
if (relatedComponent == null) {
Debug.Fail("How can we add a DesignerAction glyphs when it's related object is not an IComponent?");
return;
}
IComponent primSel = selSvc.PrimarySelection as IComponent;
if (primSel == e.RelatedObject) {
g = GetDesignerActionGlyph(relatedComponent , e.ActionLists);
if(g != null) {
VerifyGlyphIsInAdorner(g);
} else {
RemoveActionGlyph(e.RelatedObject);
}
}
}
if (e.ChangeType == DesignerActionListsChangedType.ActionListsRemoved && e.ActionLists.Count == 0) {
//only remove our glyph if there are no more DesignerActions
//associated with it.
RemoveActionGlyph(e.RelatedObject);
} else if(g!=null) {
// we need to recreate the panel here, since it's content has changed...
RecreatePanel(relatedComponent);
}
}
///
/// Called when our KeyShowDesignerActions menu command is fired
/// (a.k.a. Alt+Shift+F10) - we will find the primary selection,
/// see if it has designer actions, and if so - show the menu.
///
private void OnKeyShowDesignerActions(object sender, EventArgs e) {
ShowDesignerActionPanelForPrimarySelection();
}
// we cannot attach several menu command to the same command id, we need
// a single entry point, we put it in designershortcutui. but we need a way to call the show ui on the related behavior
// hence this internal function to hack it together
//we return false if we have nothing to display, we hide it and return true if we're already displaying
internal bool ShowDesignerActionPanelForPrimarySelection() {
//can't do anythign w/o selection service
if (selSvc == null) {
return false;
}
object primarySelection = selSvc.PrimarySelection;
//verfiy that we have obtained a valid component with designer actions
if (primarySelection == null || !componentToGlyph.Contains(primarySelection)) {
return false;
}
DesignerActionGlyph glyph = (DesignerActionGlyph)componentToGlyph[primarySelection];
if (glyph != null && glyph.Behavior is DesignerActionBehavior) {
// show the menu
DesignerActionBehavior behavior = glyph.Behavior as DesignerActionBehavior;
if(behavior != null) {
if(!IsDesignerActionPanelVisible) {
behavior.ShowUI(glyph);
return true;
} else {
behavior.HideUI();
return false;
}
}
}
return false;
}
///
/// When all the DesignerActions have been removed for a particular
/// object, we remove any UI (glyphs) that we may have been managing.
///
internal void RemoveActionGlyph(object relatedObject) {
if (relatedObject == null) {
return;
}
if(IsDesignerActionPanelVisible && relatedObject == lastPanelComponent) {
HideDesignerActionPanel();
}
DesignerActionGlyph glyph = (DesignerActionGlyph)componentToGlyph[relatedObject];
if (glyph != null) {
// Check ComponentTray first
ComponentTray compTray = serviceProvider.GetService(typeof(ComponentTray)) as ComponentTray;
if(compTray != null && compTray.SelectionGlyphs != null) {
if (compTray != null && compTray.SelectionGlyphs.Contains(glyph)) {
compTray.SelectionGlyphs.Remove(glyph);
}
}
if(designerActionAdorner.Glyphs.Contains(glyph)) {
designerActionAdorner.Glyphs.Remove(glyph);
}
componentToGlyph.Remove(relatedObject);
// we only do this when we're in a transaction, see bug VSWHIDBEY 418709. This is for compat reason - infragistic. if we're not in a transaction, too bad, we don't update the screen
IDesignerHost host = serviceProvider.GetService(typeof(IDesignerHost)) as IDesignerHost;
if(host!=null && host.InTransaction) {
host.TransactionClosed += new DesignerTransactionCloseEventHandler(this.InvalidateGlyphOnLastTransaction);
relatedGlyphTransaction = glyph;
}
}
}
private void InvalidateGlyphOnLastTransaction(object sender, DesignerTransactionCloseEventArgs e) {
if(e.LastTransaction) {
IDesignerHost host = (serviceProvider != null) ? serviceProvider.GetService(typeof(IDesignerHost)) as IDesignerHost : null;
if (host != null) {
host.TransactionClosed -= new DesignerTransactionCloseEventHandler(this.InvalidateGlyphOnLastTransaction);
}
if(relatedGlyphTransaction != null) {
relatedGlyphTransaction.InvalidateOwnerLocation();
}
relatedGlyphTransaction = null;
}
}
internal void HideDesignerActionPanel() {
if(IsDesignerActionPanelVisible) {
designerActionHost.Close();
}
}
internal bool IsDesignerActionPanelVisible {
get {
return (designerActionHost != null && designerActionHost.Visible);
}
}
internal IComponent LastPanelComponent {
get {
return (IsDesignerActionPanelVisible ? this.lastPanelComponent : null);
}
}
private void toolStripDropDown_Closing(object sender, ToolStripDropDownClosingEventArgs e) {
if (cancelClose || e.Cancel) {
e.Cancel = true;
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "[DesignerActionUI.toolStripDropDown_Closing] cancelClose true, bail");
return;
}
if (e.CloseReason == ToolStripDropDownCloseReason.ItemClicked) {
e.Cancel = true;
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "[DesignerActionUI.toolStripDropDown_Closing] ItemClicked: e.Cancel set to: " + e.Cancel.ToString());
}
if(e.CloseReason == ToolStripDropDownCloseReason.Keyboard) {
e.Cancel = false;
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "[DesignerActionUI.toolStripDropDown_Closing] Keyboard: e.Cancel set to: " + e.Cancel.ToString());
}
if(e.Cancel == false) { // we WILL disappear
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "[DesignerActionUI.toolStripDropDown_Closing] Closing...");
Debug.Assert(lastPanelComponent!=null, "last panel component should not be null here... "+
"(except if you're currently debugging VS where deactivation messages in the middle of the pump can mess up everything...)");
if(lastPanelComponent == null)
return;
// if we're actually closing
// get the coordinate of the last message, the one causing us to close, is it within the glyph coordinate
// if it is that mean that someone just clicked back from the panel, on VS, but ON THE GLYPH, that means that he
// actually wants to close it. The activation change is going to do that for us but we should NOT reopen right away
// because he clicked on the glyph... this code is here to prevent this...
Point point = DesignerUtils.LastCursorPoint;
DesignerActionGlyph currentGlyph = componentToGlyph[lastPanelComponent] as DesignerActionGlyph;
if(currentGlyph != null) {
Point glyphCoord = GetGlyphLocationScreenCoord(lastPanelComponent, currentGlyph);
if((new Rectangle(glyphCoord, new Size(currentGlyph.Bounds.Width, currentGlyph.Bounds.Height))).Contains(point)) {
DesignerActionBehavior behavior = currentGlyph.Behavior as DesignerActionBehavior;
behavior.IgnoreNextMouseUp = true;
}
currentGlyph.InvalidateOwnerLocation();
}
// unset the ownership relationship
/* UnsafeNativeMethods.SetWindowLong(new HandleRef(designerActionHost, designerActionHost.Handle),
NativeMethods.GWL_HWNDPARENT,
new HandleRef(null, IntPtr.Zero));
*/
lastPanelComponent = null;
// panel is going away, pop the behavior that's on the stack...
Debug.Assert(dapkb != null, "why is dapkb null?");
System.Windows.Forms.Design.Behavior.Behavior popBehavior = behaviorService.PopBehavior(dapkb);
Debug.Assert(popBehavior is DesignerActionKeyboardBehavior, "behavior returned is of the wrong kind?");
}
}
internal Point UpdateDAPLocation(IComponent component, DesignerActionGlyph glyph) {
if(component == null) { // in case of a resize...
component = lastPanelComponent;
}
if(designerActionHost == null) {
return Point.Empty;
}
if (component == null || glyph == null ) {
return designerActionHost.Location;
}
// check that the glyph is still visible in the adorner window
if(behaviorService != null && behaviorService.AdornerWindowGraphics != null &&
!behaviorService.AdornerWindowControl.DisplayRectangle.IntersectsWith(glyph.Bounds)) {
HideDesignerActionPanel();
return designerActionHost.Location;
}
Point glyphLocationScreenCoord = GetGlyphLocationScreenCoord(component, glyph);
Rectangle rectGlyph = new Rectangle(glyphLocationScreenCoord, glyph.Bounds.Size);
DockStyle edgeToDock;
Point pt = DesignerActionPanel.ComputePreferredDesktopLocation(rectGlyph, designerActionHost.Size, out edgeToDock);
glyph.DockEdge = edgeToDock;
designerActionHost.Location = pt;
return pt;
}
private Point GetGlyphLocationScreenCoord(IComponent relatedComponent, Glyph glyph) {
Point glyphLocationScreenCoord = new Point(0,0);
if(relatedComponent is Control && !(relatedComponent is ToolStripDropDown)) {
Control relatedControl = relatedComponent as Control;
glyphLocationScreenCoord =behaviorService.AdornerWindowPointToScreen(glyph.Bounds.Location);
}
//ISSUE: we can't have this special cased here - we should find a more
//generic approach to solving this problem
else if (relatedComponent is ToolStripItem) {
ToolStripItem item = relatedComponent as ToolStripItem;
if (item != null && item.Owner != null) {
glyphLocationScreenCoord = behaviorService.AdornerWindowPointToScreen(glyph.Bounds.Location);
}
}
else if (relatedComponent is IComponent) {
ComponentTray compTray = serviceProvider.GetService(typeof(ComponentTray)) as ComponentTray;
if (compTray != null) {
glyphLocationScreenCoord = compTray.PointToScreen(glyph.Bounds.Location);
}
}
return glyphLocationScreenCoord;
}
bool cancelClose = false;
///
/// This shows the actual chrome paenl that is created by the
/// DesignerActionBehavior object.
///
internal void ShowDesignerActionPanel(IComponent relatedComponent, DesignerActionPanel panel, DesignerActionGlyph glyph) {
if(designerActionHost ==null) {
designerActionHost = new DesignerActionToolStripDropDown(this, mainParentWindow);
designerActionHost.AutoSize = false;
designerActionHost.Padding = Padding.Empty;
designerActionHost.Renderer = new NoBorderRenderer();
designerActionHost.Text = "DesignerActionTopLevelForm";
designerActionHost.Closing +=new ToolStripDropDownClosingEventHandler(toolStripDropDown_Closing);
}
// set the accessible name of the panel to the same title as the panel header. do that every time
designerActionHost.AccessibleName = SR.GetString(SR.DesignerActionPanel_DefaultPanelTitle, relatedComponent.GetType().Name);
panel.AccessibleName = SR.GetString(SR.DesignerActionPanel_DefaultPanelTitle, relatedComponent.GetType().Name);
//GetDesignerActionGlyph(relatedComponent); // only here to update the ActionList collection on the behavior
designerActionHost.SetDesignerActionPanel(panel, glyph);
Point location = UpdateDAPLocation(relatedComponent, glyph);
// check that the panel will have at least it's parent glyph visible on the adorner window
if(behaviorService != null && behaviorService.AdornerWindowGraphics != null &&
behaviorService.AdornerWindowControl.DisplayRectangle.IntersectsWith(glyph.Bounds)) {
//behaviorService.AdornerWindowGraphics.IsVisible(glyph.Bounds)) {
if (mainParentWindow != null && mainParentWindow.Handle != IntPtr.Zero) {
Debug.WriteLineIf(DesigneActionPanelTraceSwitch.TraceVerbose, "Assigning owner to mainParentWindow");
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "Assigning owner to mainParentWindow");
UnsafeNativeMethods.SetWindowLong(new HandleRef(designerActionHost, designerActionHost.Handle),
NativeMethods.GWL_HWNDPARENT,
new HandleRef(mainParentWindow, mainParentWindow.Handle));
}
// designerActionHost.AutoClose = false;
cancelClose = true;
designerActionHost.Show(location);
designerActionHost.Focus();
// when a control is drag and dropped and authoshow is set to true
// the vs designer is going to get activated as soon as the control is dropped
// we don't want to close the panel then, so we post a message (using the trick to
// call begin invoke) and once everything is settled re-activate the autoclose logic
designerActionHost.BeginInvoke(new EventHandler(OnShowComplete));
// invalidate the glyph to have it point the other way
glyph.InvalidateOwnerLocation();
lastPanelComponent = relatedComponent;
// push new behavior for keyboard handling on the behavior stack
dapkb = new DesignerActionKeyboardBehavior(designerActionHost.CurrentPanel, serviceProvider, behaviorService);
behaviorService.PushBehavior(dapkb);
}
}
private void OnShowComplete(object sender, EventArgs e) {
// designerActionHost.AutoClose = true;
cancelClose = false;
// force the panel to be the active window - for some reason someone else could have forced VS to become
// active for real while we were ignoring close. This might be bad cause we'd be in a bad state.
if(designerActionHost != null && designerActionHost.Handle != IntPtr.Zero && designerActionHost.Visible) {
UnsafeNativeMethods.SetActiveWindow(new HandleRef(this, designerActionHost.Handle));
designerActionHost.CheckFocusIsRight();
}
}
}
internal class DesignerActionToolStripDropDown : ToolStripDropDown {
private IWin32Window _mainParentWindow;
private ToolStripControlHost _panel;
private DesignerActionUI _designerActionUI;
private bool _cancelClose = false;
private Glyph relatedGlyph;
public DesignerActionToolStripDropDown(DesignerActionUI designerActionUI, IWin32Window mainParentWindow) {
_mainParentWindow = mainParentWindow;
_designerActionUI = designerActionUI;
}
public DesignerActionPanel CurrentPanel {
get {
if(this._panel != null) {
return _panel.Control as DesignerActionPanel;
} else {
return null;
}
}
}
// we're not topmost because we can show modal editors above us.
protected override bool TopMost {
get { return false; }
}
public void UpdateContainerSize() {
if (CurrentPanel != null) {
Size panelSize = CurrentPanel.GetPreferredSize(new Size(150, Int32.MaxValue));
if (CurrentPanel.Size == panelSize) {
// If the panel size didn't actually change, we still have to force
// a call to PerformLayout to make sure that controls get repositioned
// properly within the panel. The issue arises because we did a
// measure-only Layout that determined some sizes, and then we end up
// painting with those values even though there wasn't an actual Layout
// performed.
CurrentPanel.PerformLayout();
}
else {
CurrentPanel.Size = panelSize;
}
ClientSize = panelSize;
}
}
public void CheckFocusIsRight() { // hack to get the focus to NOT stay on ContainerControl
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "Checking focus...");
IntPtr focusedControl = UnsafeNativeMethods.GetFocus();
if(focusedControl == this.Handle) {
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, " putting focus on the panel...");
_panel.Focus();
}
focusedControl = UnsafeNativeMethods.GetFocus();
if(CurrentPanel != null && CurrentPanel.Handle == focusedControl) {
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, " selecting next available control on the panel...");
CurrentPanel.SelectNextControl(null, true, true, true, true);
}
focusedControl = UnsafeNativeMethods.GetFocus();
}
protected override void OnLayout(LayoutEventArgs levent) {
base.OnLayout(levent);
UpdateContainerSize();
}
protected override void OnClosing(ToolStripDropDownClosingEventArgs e) {
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "_____________________________Begin OnClose " + e.CloseReason.ToString());
Debug.Indent();
if (e.CloseReason == ToolStripDropDownCloseReason.AppFocusChange && _cancelClose) {
_cancelClose = false;
e.Cancel = true;
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "cancel close prepopulated");
}
// when we get closing event as a result of an activation change,
// pre-populate e.Cancel based on why we're exiting.
//
// - if it's a modal window that's owned by VS dont exit
// - if it's a window that's owned by the toolstrip dropdown dont exit
else if (e.CloseReason == ToolStripDropDownCloseReason.AppFocusChange || e.CloseReason == ToolStripDropDownCloseReason.AppClicked) {
IntPtr hwndActivating = UnsafeNativeMethods.GetActiveWindow();
if (this.Handle == hwndActivating && e.CloseReason == ToolStripDropDownCloseReason.AppClicked) {
e.Cancel = false;
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "[DesignerActionToolStripDropDown.OnClosing] activation hasnt changed, but we've certainly clicked somewhere else.");
}
else if(WindowOwnsWindow(this.Handle, hwndActivating)) {
// we're being de-activated for someone owned by the panel
e.Cancel = true;
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "[DesignerActionToolStripDropDown.OnClosing] Cancel close - the window activating is owned by this window");
}
else if(_mainParentWindow != null && !WindowOwnsWindow(_mainParentWindow.Handle, hwndActivating)) {
if (IsWindowEnabled(_mainParentWindow.Handle)) {
// the activated windows is not a child/owned windows of the main top level windows
// let toolstripdropdown handle this
e.Cancel = false;
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "[DesignerActionToolStripDropDown.OnClosing] Call close: the activated windows is not a child/owned windows of the main top level windows ");
}
else {
e.Cancel = true;
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "[DesignerActionToolStripDropDown.OnClosing] we're being deactivated by a foreign window, but the main window is not enabled - we should stay up");
}
base.OnClosing(e);
Debug.Unindent();
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "_____________________________End OnClose e.Cancel: " + e.Cancel.ToString() );
return;
}
else {
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "[DesignerActionToolStripDropDown.OnClosing] since the designer action panel dropdown doesnt own the activating window " + hwndActivating.ToString("x") + ", calling close. ");
}
// what's the owner of the windows being activated?
IntPtr parent = UnsafeNativeMethods.GetWindowLong(new HandleRef(this, hwndActivating),
NativeMethods.GWL_HWNDPARENT);
// is it currently disabled (ie, the activating windows is in modal mode)
if(!IsWindowEnabled(parent)) {
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "[DesignerActionToolStripDropDown.OnClosing] modal window activated - cancelling close");
// we are in a modal case
e.Cancel = true;
}
} else {
}
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "[DesignerActionToolStripDropDown.OnClosing] calling base.OnClosing with e.Cancel: " + e.Cancel.ToString());
base.OnClosing(e);
Debug.Unindent();
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "_____________________________End OnClose e.Cancel: " + e.Cancel.ToString());
}
public void SetDesignerActionPanel(DesignerActionPanel panel, Glyph relatedGlyph) {
if(_panel != null && panel == (DesignerActionPanel)_panel.Control)
return;
Debug.Assert(relatedGlyph != null, "related glyph cannot be null");
this.relatedGlyph = relatedGlyph;
panel.SizeChanged += new EventHandler(PanelResized);
// hook up the event
if( _panel != null) {
Items.Remove(_panel);
_panel.Dispose();
_panel = null;
}
_panel = new ToolStripControlHost(panel);
// we don't want no margin
_panel.Margin = Padding.Empty;
_panel.Size = panel.Size;
this.SuspendLayout();
this.Size = panel.Size;
this.Items.Add(_panel);
this.ResumeLayout();
if(this.Visible) {
CheckFocusIsRight();
}
}
private void PanelResized(object sender, System.EventArgs e) {
Control ctrl = sender as Control;
if(this.Size.Width != ctrl.Size.Width || this.Size.Height != ctrl.Size.Height) {
this.SuspendLayout();
this.Size = ctrl.Size;
if(_panel != null) {
_panel.Size = ctrl.Size;
}
_designerActionUI.UpdateDAPLocation(null, relatedGlyph as DesignerActionGlyph);
this.ResumeLayout();
}
}
protected override void SetVisibleCore(bool visible) {
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "[DesignerActionToolStripDropDown.SetVisibleCore] setting dropdown visible=" + visible.ToString());
base.SetVisibleCore(visible);
if(visible) {
CheckFocusIsRight();
}
}
///
/// General purpose method, based on Control.Contains()...
///
/// Determines whether a given window (specified using native window handle)
/// is a descendant of this control. This catches both contained descendants
/// and 'owned' windows such as modal dialogs. Using window handles rather
/// than Control objects allows it to catch un-managed windows as well.
///
private static bool WindowOwnsWindow(IntPtr hWndOwner, IntPtr hWndDescendant) {
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "[WindowOwnsWindow] Testing if " + hWndOwner.ToString("x")+ " is a owned by " + hWndDescendant.ToString("x") + "... ");
#if DEBUG
if (DesignerActionUI.DropDownVisibilityDebug.TraceVerbose) {
Debug.WriteLine("\t\tOWNER: " + GetControlInformation(hWndOwner));
Debug.WriteLine("\t\tOWNEE: " + GetControlInformation(hWndDescendant));
IntPtr claimedOwnerHwnd = UnsafeNativeMethods.GetWindowLong(new HandleRef(null, hWndDescendant), NativeMethods.GWL_HWNDPARENT);
Debug.WriteLine("OWNEE's CLAIMED OWNER: "+ GetControlInformation(claimedOwnerHwnd));
}
#endif
if (hWndDescendant == hWndOwner) {
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "they match, YES.");
return true;
}
while (hWndDescendant != IntPtr.Zero) {
hWndDescendant = UnsafeNativeMethods.GetWindowLong(new HandleRef(null, hWndDescendant), NativeMethods.GWL_HWNDPARENT);
if (hWndDescendant == IntPtr.Zero) {
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "NOPE.");
return false;
}
if (hWndDescendant == hWndOwner) {
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "YES.");
return true;
}
}
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "NO.");
return false;
}
// helper function for generating infomation about a particular control
// use AssertControlInformation if sticking in an assert - then the work
// to figure out the control info will only be done when the assertion is false.
internal static string GetControlInformation(IntPtr hwnd) {
if (hwnd == IntPtr.Zero) {
return "Handle is IntPtr.Zero";
}
#if DEBUG
if (!DesignerActionUI.DropDownVisibilityDebug.TraceVerbose) {
return String.Empty;
}
int textLen = SafeNativeMethods.GetWindowTextLength(new HandleRef(null, hwnd));
StringBuilder sb = new StringBuilder(textLen+1);
UnsafeNativeMethods.GetWindowText(new HandleRef(null, hwnd), sb, sb.Capacity);
string typeOfControl = "Unknown";
string nameOfControl = "";
Control c = Control.FromHandle(hwnd);
if (c != null) {
typeOfControl = c.GetType().Name;
if (!string.IsNullOrEmpty(c.Name)) {
nameOfControl += c.Name;
}
else {
nameOfControl += "Unknown";
ToolStripDropDown dd = c as ToolStripDropDown;
// some extra debug info for toolstripdropdowns...
if (dd != null) {
if (dd.OwnerItem != null) {
nameOfControl += "OwnerItem: [" + dd.OwnerItem.ToString()+ "]";
}
}
}
}
return sb.ToString() + "\r\n\t\t\tType: [" + typeOfControl + "] Name: [" + nameOfControl + "]";
#else
return String.Empty;
#endif
}
private bool IsWindowEnabled(IntPtr handle) {
int style = (int) UnsafeNativeMethods.GetWindowLong(new HandleRef(this, handle), NativeMethods.GWL_STYLE);
return (style & NativeMethods.WS_DISABLED) == 0;
}
private void WmActivate(ref Message m) {
if((int)m.WParam == NativeMethods.WA_INACTIVE) {
IntPtr hwndActivating = m.LParam;
if(WindowOwnsWindow(this.Handle, hwndActivating)) {
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "[DesignerActionUI WmActivate] setting cancel close true because WindowsOwnWindow");
Debug.WriteLineIf(DesignerActionUI.DropDownVisibilityDebug.TraceVerbose, "[DesignerActionUI WmActivate] checking the focus... " + GetControlInformation(UnsafeNativeMethods.GetFocus()));
_cancelClose = true;
}
else {
_cancelClose = false;
}
}
else {
_cancelClose = false;
}
base.WndProc(ref m);
}
protected override void WndProc(ref Message m) {
switch (m.Msg) {
case NativeMethods.WM_ACTIVATE:
WmActivate(ref m);
return;
}
base.WndProc(ref m);
}
protected override bool ProcessDialogKey(Keys keyData) {
// since we're not hosted in a form we need to do the same logic
// as Form.cs. If we get an enter key we need to find the current focused control
// if it's a button, we click it and return that we handled the message
if(keyData == Keys.Enter) {
IntPtr focusedControlPtr = UnsafeNativeMethods.GetFocus();
Control focusedControl = Control.FromChildHandle(focusedControlPtr);
IButtonControl button = focusedControl as IButtonControl;
if (button != null && button is Control) {
button.PerformClick();
return true;
}
}
/* should not need that anymore... *//*
if ( keyData == (Keys.Menu | Keys.Alt) ||
keyData == Keys.F4 ||
keyData == (Keys.Alt | Keys.Down) ||
keyData == (Keys.Alt | Keys.Up)) { //HACK HACK HACK DesignerActionPanel should handle message routing properly
// I don't think that's the case now. checking this in to get the suite to pass. Here we prevent VS from getting the F4
IntPtr focusedControlPtr = UnsafeNativeMethods.GetFocus();
if(WindowOwnsWindow(this.Handle, focusedControlPtr)) {
// we don't want VS to even get the message, but we want to
// make sure it'll cause an OnKeyDown on the panel (who has focus)
return false;
}
}*/
return base.ProcessDialogKey(keyData);
}
}
internal class NoBorderRenderer : ToolStripProfessionalRenderer {
protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e) {
}
}
}
// 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
- ViewUtilities.cs
- ProviderCommandInfoUtils.cs
- CollectionType.cs
- SimpleTextLine.cs
- _IPv6Address.cs
- CatalogZoneBase.cs
- PseudoWebRequest.cs
- ToolStripProgressBar.cs
- ImageAnimator.cs
- UIElementPropertyUndoUnit.cs
- ContentControl.cs
- SafeFileMapViewHandle.cs
- filewebrequest.cs
- Base64Encoder.cs
- ProtectedConfigurationSection.cs
- CLRBindingWorker.cs
- BackgroundWorker.cs
- WindowsListBox.cs
- SignedInfo.cs
- HtmlInputFile.cs
- RawStylusInputCustomData.cs
- SqlTopReducer.cs
- DesignerAdapterUtil.cs
- BuilderPropertyEntry.cs
- UnsettableComboBox.cs
- SoapObjectReader.cs
- PenThread.cs
- DataGridViewImageColumn.cs
- GridViewUpdatedEventArgs.cs
- RequestDescription.cs
- ThreadExceptionDialog.cs
- RoleManagerEventArgs.cs
- AxisAngleRotation3D.cs
- StorageEntityContainerMapping.cs
- VirtualizingStackPanel.cs
- SpeechSynthesizer.cs
- UIHelper.cs
- SerializableAttribute.cs
- Misc.cs
- EntitySqlQueryState.cs
- UrlUtility.cs
- AsyncCompletedEventArgs.cs
- UdpUtility.cs
- DbCommandDefinition.cs
- WebPartTransformerCollection.cs
- HttpWebRequestElement.cs
- AccessDataSourceView.cs
- TextDecoration.cs
- DataGridViewColumnHeaderCell.cs
- IgnoreSection.cs
- NaturalLanguageHyphenator.cs
- SerializableAttribute.cs
- Size.cs
- Label.cs
- InternalBufferManager.cs
- _AutoWebProxyScriptHelper.cs
- CssClassPropertyAttribute.cs
- DES.cs
- EncodingTable.cs
- RuntimeArgumentHandle.cs
- UserInitiatedNavigationPermission.cs
- QilStrConcatenator.cs
- RelatedPropertyManager.cs
- FileSystemEventArgs.cs
- CodeGenerator.cs
- TableItemStyle.cs
- OdbcHandle.cs
- Error.cs
- HitTestParameters3D.cs
- FillRuleValidation.cs
- ParagraphResult.cs
- CodeDirectoryCompiler.cs
- IncomingWebResponseContext.cs
- ipaddressinformationcollection.cs
- SqlTypesSchemaImporter.cs
- CodeDirectionExpression.cs
- CompoundFileDeflateTransform.cs
- DictionarySurrogate.cs
- HtmlInputSubmit.cs
- WebPartConnectionsDisconnectVerb.cs
- WithParamAction.cs
- X509Utils.cs
- Tile.cs
- GeneratedContractType.cs
- EntityProviderFactory.cs
- CommandPlan.cs
- HtmlInputButton.cs
- SafePEFileHandle.cs
- DateTimeFormatInfo.cs
- StringSource.cs
- CodeGroup.cs
- RewritingSimplifier.cs
- UIElement.cs
- EntryIndex.cs
- Validator.cs
- COM2ExtendedUITypeEditor.cs
- InputBuffer.cs
- ColumnMapCopier.cs
- DrawingCollection.cs
- InputManager.cs