I am writing a coded ui test for my project, and i'd like to test if a label is visible or not.
However, the auto-generated code uses WinText, which does not contain any visibility option.
Is there a way to check visibility of the ui element behind WinText's or get the actual label instead ?
The easiest and most straightforward way is to call WinText's TryGetClickablePoint. If it returns false that means the control is either invisible, offscreen or blocked by an other control.
Point p = new Point();
bool isVisible = UIMap.UIForm1Window.UILabel1Window.UILabel1Text.TryGetClickablePoint(out p);
If you want to fiddle with the "ui element behind WinText's" then you have to get its native element and cast it to the appropriate class (IAccessible if winform) and get the visibility state from there. Be careful tho: you'll probably have to get the label's window's native element and not the label's.
IAccessible acc = (UIMap.UIForm1Window.UILabel1Window.NativeElement as object[])[id] as IAccessible; //id is most probably 0, check with AccExplorer
bool isVisible = (((AccessibleStates)acc.accState & AccessibleStates.Invisible) == 0) && (((AccessibleStates)acc.accState & AccessibleStates.Offscreen) == 0);
I'm using the following extension method for the UITestControl class, and so far it has been pretty bullet proof:
using Microsoft.VisualStudio.TestTools.UITesting;
namespace CodedUIExtensions
{
public static class UITestControlExtensions
{
public static bool IsElementVisible(this UITestControl control)
{
// Assume the control is invisible
bool visible = false;
System.Drawing.Point p;
try
{
// If the control is offscreen, bring it into the viewport
control.EnsureClickable();
// Now check the coordinates of the clickable point
visible = control.TryGetClickablePoint(out p)
&& (p.X > 0 || p.Y > 0);
}
catch (Exception ex)
{
// Boom goes the dynamite! Control is not visible.
// Log to stdout for debugging.
Console.WriteLine(ex);
}
return visible;
}
}
}
My requirements were:
Ensure the control is inside the viewport so a control that is visible, but offscreen doesn't trigger a false negative
Then assert the control is visible
Do not throw exceptions
If an exception occurs, do something with it for debugging purposes
And to use it:
using CodedUIExtensions;
[CodedUITest]
class MyTest
{
[TestMethod]
public void SomeVisibilityTest()
{
UITestControl control = // ...
HtmlDiv div = // ...
Assert.IsTrue(control.IsElementVisible(), "Not visible");
Asset.IsFalse(div.IsElementVisible(), "Visible");
}
}
As long as the Coded UI control class inherits from UITestControl and you have using CodedUIExtensions in your C# file, every control will have an IsElementVisible() method. This should work for web pages as well as GUI applications, such as WinForms.
You can try this:
Control.WaitForControlPropertyNotEqual(UITestControl.PropertyNames.State, ControlStates.Invisible);
This one worked for me (and also the method with .TryGetClickablePoint() ):
while (myControl.State != ControlStates.Offscreen) { }
Related
I want to enable/disable controls in a Windows Forms application according to the user privileges.
Initially I thought of writing a method in each form class that would check the user credentials and then enable/disable its controls. But then I realized I could (maybe) create a static class method which would take the form as a parameter and do the job.
So I started writing it, presuming that sometimes I would like to enable the controls of just one or two panels, instead of the whole form. So, I need the parameters to be:
a varying number of panels and/or
a form class.
My difficulties with this task is that I'm getting an error trying to make the panels argument varying, and I have no idea how to set a parameter that could take any form class. All my form classes obviously inherits from Form generic class, but I don't know how to apply this.
Here's what I got:
public static void Enable(TableLayoutPanel[] containers = null)
{
if (MyOF.isEnabled)
{
return;
}
else
{
try
{
foreach (TableLayoutPanel table in containers)
{
foreach (Control control in table.Controls)
{
control.Enabled = false;
}
}
}
catch (NullReferenceException)
{
}
}
}
If we remember that the Form class derives from Control (indirectly, by deriving from ContainerControl which derives from ScrollableControl, which derives from Control), and the Enabled property belongs to the Control class, we can write a method that will enable any control's children (including the Form or TableLayoutPanel controls), since the Controls collection also belongs to the Control class:
public static void EnableChildren(Control control, bool enabled = true)
{
foreach (Control child in control.Controls)
{
child.Enabled = enabled;
}
}
And then if we also want to be able to use this with a collection of controls (as in your example), we can write an overload that takes a collection:
public static void EnableChildren(IEnumerable<Control> controls = null,
bool enabled = true)
{
if (controls == null) return;
foreach (var control in controls)
{
EnableChildren(control, enabled);
}
}
Now we can use this with a Form or a collection of TableLayoutPanel controls (or any control that has controls in it's Controls collection).
Examples of usage:
var myForm = new Form1();
EnableChildren(this); // 'this' is the current form
EnableChildren(myForm); // a separate instance of a form control
EnableChildren(tableLayoutPanel1, false); // A single TableLayoutPanel control
var tableLayoutPanels = new [] {tableLayoutPanel1, tableLayoutPanel2, tableLayoutPanel3};
EnableChildren(tableLayoutPanels); // An array of tableLayoutPanel controls
One of the simple ways I can think about what you are trying to do, is this. Let me get away for a sec here. I worked on projects where all form controls were built from Metadata. And meta came with licensing info. So, when control was placed where it should, it also was disabled or set read-only based on Metadata but the whole feature would be hidden if licensing info was restricting access to it. Coming back to your approach, this is not a bad approach and I see that this is can be done. And it can be done in 2 ways, (quickly from my head).
Use user controls as surface for the components you want to enable/disable. Create an interface
public interface IDisableableControl // make your fine name, no methods needed - marker interface
. . . . .
public class MyFineUserControl : UserControl, IDisableableControl
And in your static method that you're going to write pass the form, and find all controls that implement this interface and work them the way you want.
2.
Similarly, you can use property Tag, which is available on each control. With that, you can actually set your complex security object that can come from DB-stored metadata and then you evaluate this object stored in Tag to apply your configuration
Your method needs to be recursive
internal static void SetAllControls(Control parent)
{
// Do something with control, for example parent.Enabled = false
if (parent is IDisableableControl)
{
// here you use your logic, evaluate your parent you're dialing with and
// enable/disable correspondingly
parent.Enabled = false;
return;
}
foreach(var c in parent.Controls)
SetAllControls(c);
}
In real life, your TOP parent will be a form and will not need to be disabled, but it's certain children will. In fact, most of the time, once you found a UserControl which implements IDisableableControl that should be end of line, means, you don't need to go into children controls as they all sit on this parent and all will be disabled
I manage to accomplish what I was trying to do with the code below, which is pretty much a blend of all the helpful answers I got:
public static void EnableContainer(params Control[] containers)
{
if(containers.Count() == 0) { return; }
if (MyOF.isEnabled)
{
return;
}
else
{
try
{
foreach (var container in containers)
{
foreach (Control control in container.Controls)
{
control.Enabled = false;
}
}
}
catch (NullReferenceException)
{
}
}
}
public static void EnableForm<form>(form f) where form : Form
{
if (MyOF.isEnabled)
{
return;
}
else
{
foreach(Control control in f.Controls)
{
control.Enabled = false;
}
}
}
The community is welcome to suggest improvements as I am far from being a professional programmer. Thanks everyone once again.
I have design 1 winform to look like the picture. But I want the highlighted yellow part to be dockable with dockpanel suite reference. Is that do-able or any other suggestion of better design?
Right now the treeview is on the dockpanel and the red box part is a usercontrol placed in the same dockpanel. I tried to put the redbox as another form but I can't place it as it is in the picture. Also, this winform is need to be responsive so I put in the redbox part in a table layout panel.winform design and not familiar actually with the dockpanel suite reference. If there is a beginner tutorial that I can refer to, it would be much appreciated.
Current design:
There are two approach to your problem. First is dirty one and second elegant one. By dirty and elegant i mean way they display. Method they work are both same.
I will explain to you how to do it on empty form and you just implement that in your populated one.
First create new form.
Add 2 or more GroupBoxes to it
Add some items inside them (just to see if it works)
At the top of the each boxes add Button which will toggle visibility
Our form now looks like this and let's look of code behind it.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Test
{
public partial class TestForm : Form
{
// This is property
bool ShowFirstGroupBox
{
get
{
// We let user get our property from private variable
return _ShowFirstGroupBox;
}
set
{
// When user change this property we do something based on that
switch(value)
{
case true:
groupBox1.Size = new Size(groupBox1.Width, FirstGroupBoxDefaultHeight);
break;
case false:
groupBox1.Size = new Size(groupBox1.Width, 55);
break;
}
_ShowFirstGroupBox = value;
}
}
bool ShowSecondGroupBox
{
get
{
return _ShowSecondGroupBox;
}
set
{
switch (value)
{
case true:
groupBox2.Size = new Size(groupBox1.Width, FirstGroupBoxDefaultHeight);
break;
case false:
groupBox2.Size = new Size(groupBox1.Width, 55);
break;
}
_ShowSecondGroupBox = value;
}
}
// We store our boxes current state ( TRUE = shown, FALSE = HIDDEN )
bool _ShowFirstGroupBox = true;
bool _ShowSecondGroupBox = true;
// We store our default height for groupboxes
int FirstGroupBoxDefaultHeight;
int SecondGroupBoxDefaultHeight;
public TestForm()
{
InitializeComponent();
// Assigning default height of our groupboxes
FirstGroupBoxDefaultHeight = groupBox1.Height;
SecondGroupBoxDefaultHeight = groupBox2.Height;
}
private void button1_Click_1(object sender, EventArgs e)
{
ShowFirstGroupBox = !(_ShowFirstGroupBox); // This sets our property value to opposite of this boolean
}
private void button1_Click_1(object sender, EventArgs e)
{
ShowSecondGroupBox = !(_ShowSecondGroupBox); // This sets our property value to opposite of this boolean
}
}
}
Now when we have code like this and press button it will collapse groupbox.
NOTE: Controls under groupbox are still on place but just hidden since they are child of groupbox and everything outside of bounds is not visible to user.
This is dirty way since i would like to display it much prettier with MINUS sign on the right side of the groupbox title so i do not have button inside it. To do this you would need to create custom control which inherits groupbox, add button to it and position it in title bar and create event for it. It is easy if you have ever tried creating custom controls but if you haven't and you think dirty approach is okay with you then do not try it.
I have a WPF CheckBox inside a Popup, and I'm finding if it is inside the item template of a TreeView, then the CheckBox does not respond to user input. If it is outside of the TreeView, then there are no problems.
I have created a relatively minimal mock-up here:
https://github.com/logiclrd/TestControlsInPopupsNotWorking
Does anyone know why the CheckBox controls popped up from within the TreeView cannot be checked?
I think this is an oversight in the design of the TreeView. Take a look at this:
Note: Some code excerpts were tidied up to avoid wrapping.
// This method is called when MouseButonDown on TreeViewItem and also listen
// for handled events too. The purpose is to restore focus on TreeView when
// mouse is clicked and focus was outside the TreeView. Focus goes either to
// selected item (if any) or treeview itself
internal void HandleMouseButtonDown()
{
if (!this.IsKeyboardFocusWithin)
{
if (_selectedContainer != null)
{
if (!_selectedContainer.IsKeyboardFocused)
_selectedContainer.Focus();
}
else
{
// If we don't have a selection - just focus the TreeView
this.Focus();
}
}
}
This method is called from TreeViewItem.OnMouseButtonDown, which we can see is a class-level handler that's configured to receive handled events too:
EventManager.RegisterClassHandler(
typeof(TreeViewItem),
Mouse.MouseDownEvent,
new MouseButtonEventHandler(OnMouseButtonDown),
/* handledEventsToo: */ true);
I have verified with the debugger that Handled is set to true by the time the event makes it to the TreeViewItem.
When you press down on the left mouse button over the CheckBox, the CheckBox begins a speculative 'click' operation and marks the event as handled. Normally, an ancestor element wouldn't see a handled event bubble up, but in this case it explicitly asked for them.
The TreeView sees that this.IsKeyboardFocusWithin resolves to false because the focused element is in another visual tree (the popup). It then gives focus back to the TreeViewItem.
Now, if you look in ButtonBase:
protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnLostKeyboardFocus(e);
if (ClickMode == ClickMode.Hover)
{
// Ignore when in hover-click mode.
return;
}
if (e.OriginalSource == this)
{
if (IsPressed)
{
SetIsPressed(false);
}
if (IsMouseCaptured)
ReleaseMouseCapture();
IsSpaceKeyDown = false;
}
}
We see that IsPressed is set to false when focus is lost. If we then go to OnMouseLeftButtonUp, we see this:
bool shouldClick = !IsSpaceKeyDown && IsPressed && ClickMode == ClickMode.Release;
With IsPressed now false, the click operation never completes, all because the TreeViewItem stole focus away from you when you tried to click the button.
As a work-around, I have had success so far with using the NuGet library Ryder (which looks like a freely-usable open-source (MIT license) version of Microsoft Detours) to intercept the HandleMouseButtonDown method in TreeView.
The Ryder library can be found in the NuGet library, and the code behind it can be found here:
https://github.com/6A/Ryder
Hooking the HandleMouseButtonDown method is pretty simple:
var realMethod = typeof(System.Windows.Controls.TreeView).GetMethod("HandleMouseButtonDown", BindingFlags.Instance | BindingFlags.NonPublic);
var replacementMethod = typeof(Program).GetMethod(nameof(TreeView_HandleMouseButtonDown_shim), BindingFlags.Static | BindingFlags.NonPublic);
Redirection.Redirect(realMethod, replacementMethod);
The shim that replaces the method can basically do what the real method does but with a fix that detects the cross-visual-tree focus situation:
static void TreeView_HandleMouseButtonDown_shim(TreeView #this)
{
// Fix as seen in: https://developercommunity.visualstudio.com/content/problem/190202/button-controls-hosted-in-popup-windows-do-not-wor.html
if (!#this.IsKeyboardFocusWithin)
{
// BEGIN NEW LINES OF CODE
var keyboardFocusedControl = Keyboard.FocusedElement;
var focusPathTrace = keyboardFocusedControl as DependencyObject;
while (focusPathTrace != null)
{
if (ReferenceEquals(#this, focusPathTrace))
return;
focusPathTrace = VisualTreeHelper.GetParent(focusPathTrace) ?? LogicalTreeHelper.GetParent(focusPathTrace);
}
// END NEW LINES OF CODE
var selectedContainer = (System.Windows.Controls.TreeViewItem)TreeView_selectedContainer_field.GetValue(#this);
if (selectedContainer != null)
{
if (!selectedContainer.IsKeyboardFocused)
selectedContainer.Focus();
}
else
{
// If we don't have a selection - just focus the treeview
#this.Focus();
}
}
}
Some reflection is needed since this interacts with a private field that is not otherwise exposed from the TreeView class, but as work-arounds go, this is a lot less invasive than what I tried at first, which was importing the entirety of the TreeView class (and related types) from Reference Source into my project in order to alter the one member. :-)
I wrote an Visual Studio 2013 extension that observes Windows.Forms designer windows. When a developer is changing controls in the designer window, the extension tries to verify that the result is consistent with our ui style guidelines. If possible violations are found they are listed in a tool window. This all works fine.
But now I would like to mark the inconsistent controls in the designer window, for example with a red frame or something like this.
Unfortunately, I did not find a way to draw adornments on controls in a designer window. I know that you can draw those adornments if you develop your own ControlDesigner, but I need to do it from "outside" the control's designer. I only have the IDesignerHost from the Dte2.ActiveWindow and can access the Controls and ControlDesigners via that host. I could not find any way to add adornments from "outside" the ControlDesigners.
My workaround for now is to catch the Paint-Events of the controls and try to draw my adornments from there. This doesn't work well for all controls (i.e. ComboBoxes etc), because not all controls let you draw on them. So I had to use their parent control's Paint event. And there are other drawbacks to this solution.
I hope someone can tell me if there is a better way. I'm pretty sure that there has to be one: If you use Menu->View->Tab Order (not sure about the correct english menu title, I'm using a german IDE), you can see that the IDE itself is able to adorn controls (no screenshot because it's my first post on SO), and I'm sure it is not using a work around like me. How does it do that?
I've been googling that for weeks now. Thanks for any help, advice, research starting points....
UPDATE:
Maybe it gets a little bit clearer with this screenshot:
Those blue numbered carets is what Visual Studio shows when selecting Tab order from the View menu. And my question is how this is done by the IDE.
As mentioned I tried to do it in the Paint event of the controls, but e.g. ComboBox doesn't actually support that event. And if I use the parent's Paint event I can only draw "around" the child controls because they are painted after the parent.
I also thought about using reflection on the controls or the ControlDesigners, but am not sure how to hook on the protected OnPaintAdornments method. And I don't think the IDE developers used those "dirty" tricks.
I believe you are seeking for BehaviorService architecture. The architecture with supporting parts like Behavior, Adorner and Glyph and some examples is explained here Behavior Service Overview. For instance
Extending the Design-Time User Interface
The BehaviorService model enables new functionality to be easily layered on an existing designer user interface. New UI remains independent of other previously defined Glyph and Behavior objects. For example, the smart tags on some controls are accessed by a Glyph in the upper-right-hand corner of the control (Smart Tag Glyph).
The smart tag code creates its own Adorner layer and adds Glyph objects to this layer. This keeps the smart tag Glyph objects separate from the selection Glyph objects. The necessary code for adding a new Adorner to the Adorners collection is straightforward.
etc.
Hope that helps.
I finally had the time to implement my solution and want to show it for completeness.
Of course I reduced the code to show only the relevant parts.
1. Obtaining the BehaviorService
This is one of the reasons why I don't like the service locator (anti) pattern. Though reading a lot of articles, I didn't came to my mind that I can obtain a BehaviorService from my IDesignerHost.
I now have something like this data class:
public class DesignerIssuesModel
{
private readonly BehaviorService m_BehaviorService;
private readonly Adorner m_Adorner = new Adorner();
private readonly Dictionary<Control, MyGlyph> m_Glyphs = new Dictionary<Control, MyGlyph>();
public IDesignerHost DesignerHost { get; private set; }
public DesignerIssuesModel(IDesignerHost designerHost)
{
DesignerHost = designerHost;
m_BehaviorService = (BehaviorService)DesignerHost.RootComponent.Site.GetService(typeof(BehaviorService));
m_BehaviorService.Adornders.Add(m_Adorner);
}
public void AddIssue(Control control)
{
if (!m_Glyphs.ContainsKey(control))
{
MyGlyph g = new MyGlyph(m_BehaviorService, control);
m_Glyphs[control] = g;
m_Adorner.Glyphs.Add(g);
}
m_Glyphs[control].Issues += 1;
}
public void RemoveIssue(Control control)
{
if (!m_Glyphs.ContainsKey(control)) return;
MyGlyph g = m_Glyphs[control];
g.Issues -= 1;
if (g.Issues > 0) return;
m_Glyphs.Remove(control);
m_Adorner.Glyphs.Remove(g);
}
}
So I obtain the BehaviorService from the RootComponent of the IDesignerHost and add a new System.Windows.Forms.Design.Behavior.Adorner to it. Then I can use my AddIssue and RemoveIssue methods to add and modify my glyphs to the Adorner.
2. My Glyph implementation
Here is the implementation of MyGlyph, a class inherited from System.Windows.Forms.Design.Behavior.Glyph:
public class MyGlyph : Glyph
{
private readonly BehaviorService m_BehaviorService;
private readonly Control m_Control;
public int Issues { get; set; }
public Control Control { get { return m_Control; } }
public VolkerIssueGlyph(BehaviorService behaviorService, Control control) : base(new MyBehavior())
{
m_Control = control;
m_BehaviorService = behaviorService;
}
public override Rectangle Bounds
{
get
{
Point p = m_BehaviorService.ControlToAdornerWindow(m_Control);
Graphics g = Graphics.FromHwnd(m_Control.Handle);
SizeF size = g.MeasureString(Issues.ToString(), m_Font);
return new Rectangle(p.X + 1, p.Y + m_Control.Height - (int)size.Height - 2, (int)size.Width + 1, (int)size.Height + 1);
}
}
public override Cursor GetHitTest(Point p)
{
return m_Control.Visible && Bounds.Contains(p) ? Cursors.Cross : null;
}
public override void Paint(PaintEventArgs pe)
{
if (!m_Control.Visible) return;
Point topLeft = m_BehaviorService.ControlToAdornerWindow(m_Control);
using (Pen pen = new Pen(Color.Red, 2))
pe.Graphics.DrawRectangle(pen, topLeft.X, topLeft.Y, m_Control.Width, m_Control.Height);
Rectangle bounds = Bounds;
pe.Graphics.FillRectangle(Brushes.Red, bounds);
pe.Graphics.DrawString(Issues.ToString(), m_Font, Brushes.Black, bounds);
}
}
The details of the overrides can be studied in the links posted in the accepted answer.
I draw a red border around (but inside) the control and add a little rectangle containing the number of found issues.
One thing to note is that I check if Control.Visible is true. So I can avoid to draw the adornment when the control is - for example - on a TabPage that is currently not selected.
3. My Behavior implementation
Since the constructor of the Glyph base class needs an instance of a class inherited from Behavior, I needed to create a new class. This can be left empty, but I used it to show a tooltip when the mouse enters the rectangle showing the number of issues:
public class MyBehavior : Behavior
{
private static readonly ToolTip ToolTip = new ToolTip
{
ToolTipTitle = "UI guide line issues found",
ToolTipIcon = ToolTipIcon.Warning
};
public override bool OnMouseEnter(Glyph g)
{
MyGlyph glyph = (MyGlyph)g;
if (!glyph.Control.Visible) return false;
lock(ToolTip)
ToolTip.Show(GetText(glyph), glyph.Control, glyph.Control.PointToClient(Control.MousePosition), 2000);
return true;
}
public override bool OnMouseLeave(Glyph g)
{
lock (ToolTip)
ToolTip.Hide(((MyGlyph)g).Control);
return true;
}
private static string GetText(MyGlyph glyph)
{
return string.Format("{0} has {1} conflicts!", glyph.Control.Name, glyph.Issues);
}
}
The overrides are called when the mouse enters/leaves the Bounds returned by the MyGlyph implementation.
4. Results
Finally I show screenshot of a example result. Since this was done by the real implementation, the tooltip is a little more advanced. The button is misaligned to all the comboboxes, because it's a little too left:
Thanks again to Ivan Stoev for pointing me to the right solution. I hope I could make clear how I implemented it.
Use the System.Drawing.Graphics.FromHwnd method, passing in the HWND for the designer window.
Get the HWND by drilling down into the window handles for visual studio, via pinvoke. Perhaps use tools like Inspect to find window classes and other information that might help you identify the correct (designer) window.
I've written a C# program to get you started here.
My setting:
I've got a C# application (.NET 3.5) in Visual Studio 2008. No chance to switch to WPF or whatsoever :).
My app contains a custom control (a button class derived from Windows.Forms.Button) that acts as a replacement for the Windows.Forms.TabControl. I can associate these buttons with one another and each button can be associated with one control that it is dealing with (usually some sort of Windows.Forms.Panel). It looks something like this:
public class TabButton : System.Windows.Forms.Button
{
// ...
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
this.myAssociatedControl.Visible = true;
this.tellMyBuddiesToHideTheirControls();
}
// ...
}
Basically it is just about clicking a button, showing its bound control and having the controls bound to the associated buttons disappear - just like the TabControl, but the approach is easily designable and I can place the buttons far from their content panels.
The problem:
This works pretty well at runtime, but the usage at design time is arguably odd: With the mouse, find a control that´s belonging to the group and run a series of <Send To Back>s until the desired control is visible.
The question:
Is there a way to tell the VS designer to evaluate the clicks on the buttons at design time like it does with the TabControl so that I can switch the tabs just by clicking them like I would at runtime?
I've been searching for quite a while now. There are some articles here at SO but they only seem to cover adding additional attributes to the properties designer.
Edith says:
By request, an answer to my own question ...
This is the solution that is suitable to my application. It is basically an example from the msdn with some twists to get the custom designer to use a callback on click. Hope it helps anyone :-).
[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
public class TabButtonDesigner : System.Windows.Forms.Design.ControlDesigner
{
ShowTabGlyph myGlyph = null;
Adorner myAdorner;
public TabButtonDesigner()
{
}
public override void Initialize(IComponent component)
{
base.Initialize(component);
// Add the custom set of glyphs using the BehaviorService.
// Glyphs live on adornders.
myAdorner = new Adorner();
BehaviorService.Adorners.Add(myAdorner);
myGlyph = new ShowTabGlyph(BehaviorService, Control);
myGlyph.Callback = () =>
{
((MyCustomTabButton)this.Control).ShowMyTab();
};
myAdorner.Glyphs.Add(myGlyph);
}
class ShowTabGlyph : Glyph
{
Control control;
BehaviorService behaviorSvc;
public Action Callback
{
get;
set;
}
public ShowTabGlyph(BehaviorService behaviorSvc, Control control) :
base(new ShowTabBehavior())
{
this.behaviorSvc = behaviorSvc;
this.control = control;
}
public override Rectangle Bounds
{
get
{
// Create a glyph that is 10x10 and sitting
// in the middle of the control. Glyph coordinates
// are in adorner window coordinates, so we must map
// using the behavior service.
Point edge = behaviorSvc.ControlToAdornerWindow(control);
Size size = control.Size;
Point center = new Point(edge.X + (size.Width / 2),
edge.Y + (size.Height / 2));
Rectangle bounds = new Rectangle(
center.X - 5,
center.Y - 5,
10,
10);
return bounds;
}
}
public override Cursor GetHitTest(Point p)
{
// GetHitTest is called to see if the point is
// within this glyph. This gives us a chance to decide
// what cursor to show. Returning null from here means
// the mouse pointer is not currently inside of the glyph.
// Returning a valid cursor here indicates the pointer is
// inside the glyph, and also enables our Behavior property
// as the active behavior.
if (Bounds.Contains(p))
{
return Cursors.Hand;
}
return null;
}
public override void Paint(PaintEventArgs pe)
{
// Draw our glyph. It is simply a blue ellipse.
pe.Graphics.DrawEllipse(Pens.Blue, Bounds);
}
// By providing our own behavior we can do something interesting
// when the user clicks or manipulates our glyph.
class ShowTabBehavior : Behavior
{
public override bool OnMouseUp(Glyph g, MouseButtons button)
{
//MessageBox.Show("Hey, you clicked the mouse here");
//this.
ShowTabGlyph myG = (ShowTabGlyph)g;
if (myG.Callback != null)
{
myG.Callback();
}
return true; // indicating we processed this event.
}
}
}
}
[DesignerAttribute(typeof(TabButtonDesigner))]
public class MyCustomTabButton : System.Windows.Forms.Button
{
// The attribute will assign the custom designer to the TabButton
// and after a rebuild the button contains a centered blue circle
// that acts at design time like the button in runtime does ...
// ...
}