Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: [Accessibility] Fixing TabPage keyboard tooltips #2719

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8e06755
Added keyboard tooltips for TabPages
vladimir-krestov Jan 15, 2020
da8eca3
Fixed a typo
vladimir-krestov Jan 15, 2020
556d5df
Fixed build error
vladimir-krestov Jan 15, 2020
1502ec6
Fixed review points
vladimir-krestov Jan 28, 2020
3090dfa
Fixed setting a tooltip text in the first time
vladimir-krestov Jan 28, 2020
d5ae952
Fixed getting tooltips
vladimir-krestov Jan 28, 2020
893a559
Added unit tests
vladimir-krestov Jan 28, 2020
8d76402
Made code refactoring
vladimir-krestov Jan 29, 2020
2628a12
Fixed getting a tab tooltip position (magic)
vladimir-krestov Jan 29, 2020
438be4a
Fixed CI build errors
vladimir-krestov Jan 29, 2020
7ab23e6
Fixed a tooltips synchronization
vladimir-krestov Jan 30, 2020
d996028
A bit of code refactoring
vladimir-krestov Jan 31, 2020
f4a8c28
Fixed some test issues
vladimir-krestov Feb 12, 2020
6033690
Fixed a declaration
vladimir-krestov Feb 13, 2020
a75c4a4
Fixed tooltip showing for internal items
vladimir-krestov Feb 13, 2020
5c37142
Fixed handling LostFocus event
vladimir-krestov Feb 17, 2020
23cb2fc
Fixed event handling implementation
vladimir-krestov Feb 17, 2020
1294182
Code refactoring
vladimir-krestov Feb 17, 2020
d687164
Fixed review issues
vladimir-krestov Feb 19, 2020
f813971
Inverted dependencies for all controls inside ToolTIp.SetToolTip method
vladimir-krestov Feb 20, 2020
790e322
Fixed the build
vladimir-krestov Feb 20, 2020
15f4d8c
Code refactoring
vladimir-krestov Feb 20, 2020
da75950
Fixed the conflict bugs
vladimir-krestov Feb 21, 2020
3d6baa8
Fixed build
vladimir-krestov Mar 31, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/System.Windows.Forms/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
override System.Windows.Forms.TabControl.OnGotFocus(System.EventArgs e) -> void
override System.Windows.Forms.TabControl.OnLostFocus(System.EventArgs e) -> void
override System.Windows.Forms.TabPage.OnGotFocus(System.EventArgs e) -> void
override System.Windows.Forms.TabPage.OnLostFocus(System.EventArgs e) -> void
virtual System.Windows.Forms.Control.SetToolTip(System.Windows.Forms.ToolTip toolTip, string toolTipText) -> void
override System.Windows.Forms.UpDownBase.SetToolTip(System.Windows.Forms.ToolTip toolTip, string toolTipText) -> void
override System.Windows.Forms.TabControl.SetToolTip(System.Windows.Forms.ToolTip toolTip, string toolTipText) -> void
override System.Windows.Forms.Label.SetToolTip(System.Windows.Forms.ToolTip toolTip, string toolTipText) -> void
override System.Windows.Forms.ListView.SetToolTip(System.Windows.Forms.ToolTip toolTip, string toolTipText) -> void
override System.Windows.Forms.TabPage.SetToolTip(System.Windows.Forms.ToolTip toolTip, string toolTipText) -> void
override System.Windows.Forms.TreeView.SetToolTip(System.Windows.Forms.ToolTip toolTip, string toolTipText) -> void
17 changes: 16 additions & 1 deletion src/System.Windows.Forms/src/System/Windows/Forms/Control.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10918,6 +10918,19 @@ internal static IntPtr SetUpPalette(IntPtr dc, bool force, bool realizePalette)
return result;
}

protected virtual void SetToolTip(ToolTip toolTip, string toolTipText)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used protected for SetToolTip method thereby adding API for developers and they can change this implementation.
This is correct or I need to hide this protected API using the private protected modifier?
/cc: @RussKie, @Tanya-Solyanik, @merriemcgaw

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any custom control must be able to hook into the tooltip display chain to override the display logic, if necessary. With that, I believe, should be made protected.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change to the public surface will not require designer changes, I don't see any problems with it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you explain, when to use Tooltip.SetToolTip and when to use Control.SetToolTip?
Perhaps this method can be named differently?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the thing, I can't see any real benefit of using one over the other.
myToolTip.SetToolTip(myControl, toolTipText) calls into myControl.SetToolTip(myToolTip, toolTipText) which recurses.

I can guess the original idea of ToolTip was to extend arbitrary controls. However then a number of controls decided to displaying tooltips in their own way (e.g. Label when its text is ellipsised) by keeping own instances of ToolTip and exposing properties like ToolTipText. So now we have an inconsistent devex because for some controls a developer must use an instance of own ToolTip, and for some other control - not.

With this API cleanup we further blurring the line and removing reasons to use ToolTip control. Instead making it a little more consistent experience - each Control allows setting its tooltips in a common manner. There be 🐉 🐉 though (e.g. the Label's case) that we'll likely need to resolve. We touched on these with @vladimir-krestov and he was meant to raise issues for tracking purposes.
Also now it may be possible to reduce number of instances of ToolTip, have one global singleton per app and pass it to all controls that require tooltips.

Now we can probably take this further and question whether we can have a single instance of a ToolTip provider/renderer. But that's a whole separate discussion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to have xml-docs, so they will flow into the docs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that toolTipText is not used in some implementation. Please use default value for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll do, thanks!

{ }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please place the closing brace on a new line

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason can't respond to the other comment above

How would you explain, when to use Tooltip.SetToolTip and when to use Control.SetToolTip?

protected methods are usually not to be called by user code, but you are right, naming should be improved

Perhaps this method can be named differently?

How about OnSetToolTip or OnToolTipChanged? This would follow the naming pattern of similar protected virtual methods existing for overriding. Even though they are usually associated with an event there is precedence for this naming pattern to be used without events, for example Control.OnNotifyMessage or Control.OnCreateControl

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

protected methods are usually not to be called by user code, but you are right, naming should be improved

If user builds a custom control then all protected members are accessible.

How about OnSetToolTip or OnToolTipChanged? This would follow the naming pattern of similar protected virtual methods existing for overriding. Even though they are usually associated with an event there is precedence for this naming pattern to be used without events, for example Control.OnNotifyMessage or Control.OnCreateControl

Whilst we may have few odd ducks, OnXxx is really associated with events, like you said. I can't endorse such naming.
What about AssignToolTip? This verb is used few times across the codebase 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about AssignToolTip? This verb is used few times across the codebase

If you want to look for alternatives there is also NotifyToolTipChanged

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Changed suffix suggests an event after an action occurred (as per the Event design guidelines).
Here we aren't so much raising events as providing plumbing to wire up an instance of a tooltip control to a control.

There are two ways of doing this now:

public class MyForm : Form
{
	private ToolTip myToolTip = new ToolTip();

	public void Method()
	{
		ToolTip myToolTip = new ToolTip();

		// we can do this...
		this.SetToolTip(myToolTip , toolTipText);
		// ...or this
		myToolTip.SetToolTip(this, toolTipText);
	}
}

// - or -
public class MyCustom : UserControl
{
	private ToolTip myToolTip = new ToolTip();

	public void Initialise(ToolTip myToolTip)
	{
		// we can do this...
		this.SetToolTip(myToolTip, toolTipText)
		// ...or this
		myToolTip.SetToolTip(this, toolTipText);
	}
}

I see the point, it is easy to get confused. 🤔 Though in the most cases calling this.SetToolTip(...) won't do anything except falling through to the base implementation (which is empty).

Maybe AssignControlToolTip(...) or ApplyControlToolTip(...)?

public class MyCustom : UserControl
{
	private ToolTip myToolTip = new ToolTip();

	public void Initialise(ToolTip myToolTip)
	{
		myToolTip.SetToolTip(this, toolTipText);  // <-- this will call into MyCustom.AssignControlToolTip()
	}

	protected override void AssignControlToolTip(ToolTip toolTip, string toolTipText)
	{
		// custom logic goes here
	}
}


internal void SetToolTipInternal(ToolTip toolTip, string toolTipText)
{
if (!IsHandleCreated || toolTip == null)
{
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why return;? It seems to me that method should call CreateHandle(); in case IsHandleCreated == false and should throw an ArgumentNullException in case toolTip == null.

Copy link
Contributor

@weltkante weltkante Feb 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely not, you must not call CreateHandle when setting a tooltip, this would cause handle instantiation in the constructor during InitializeComponents before all designer properties have been set. The rest of the tooltip infrastructure has logic to not create handles and store the tooltip somewhere else when the handle is not created.

If anything you could throw an exception if this code path is taken when no handle is available, because it would be a bug in the internal logic.

}

SetToolTip(toolTip, toolTipText);
}

protected void SetTopLevel(bool value)
{
if (value && IsActiveX)
Expand Down Expand Up @@ -14149,9 +14162,11 @@ IList<Rectangle> IKeyboardToolTip.GetNeighboringToolsRectangles()

bool IKeyboardToolTip.IsHoveredWithMouse()
{
return ClientRectangle.Contains(PointToClient(MousePosition));
return IsHoveredWithMouse;
}

private protected virtual bool IsHoveredWithMouse => ClientRectangle.Contains(PointToClient(MousePosition));

bool IKeyboardToolTip.HasRtlModeEnabled()
{
Control topLevelControl = TopLevelControlInternal;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,29 +57,47 @@ private KeyboardToolTipStateMachine()
}

private SmState Transition(IKeyboardToolTip tool, ToolTip tooltip, SmEvent @event)
=> (_currentState, @event) switch
{
switch (_currentState, @event)
{
(SmState.Hidden, SmEvent.FocusedTool) => SetupInitShowTimer(tool, tooltip),
(SmState.Hidden, SmEvent.LeftTool) => _currentState, // OK
(SmState.ReadyForInitShow, SmEvent.FocusedTool) => _currentState, // unlikely: focus without leave
(SmState.ReadyForInitShow, SmEvent.LeftTool) => FullFsmReset(),
(SmState.ReadyForInitShow, SmEvent.InitialDelayTimerExpired) => ShowToolTip(tool, tooltip),

(SmState.Shown, SmEvent.FocusedTool) => _currentState, // unlikely: focus without leave
(SmState.Shown, SmEvent.LeftTool) => HideAndStartWaitingForRefocus(tool, tooltip),
(SmState.Shown, SmEvent.AutoPopupDelayTimerExpired) => FullFsmReset(),

(SmState.WaitForRefocus, SmEvent.FocusedTool) => SetupReshowTimer(tool, tooltip),
(SmState.WaitForRefocus, SmEvent.LeftTool) => _currentState, // OK
(SmState.WaitForRefocus, SmEvent.RefocusWaitDelayExpired) => FullFsmReset(),

(SmState.ReadyForReshow, SmEvent.FocusedTool) => _currentState, // unlikely: focus without leave
(SmState.ReadyForReshow, SmEvent.LeftTool) => StartWaitingForRefocus(tool),
(SmState.ReadyForReshow, SmEvent.ReshowDelayTimerExpired) => ShowToolTip(tool, tooltip),
case (SmState.Hidden, SmEvent.FocusedTool):
return SetupInitShowTimer(tool, tooltip);
case (SmState.Hidden, SmEvent.LeftTool):
return _currentState; // OK

case (SmState.ReadyForInitShow, SmEvent.FocusedTool):
return _currentState; // unlikely: focus without leave
case (SmState.ReadyForInitShow, SmEvent.LeftTool):
return FullFsmReset();
case (SmState.ReadyForInitShow, SmEvent.InitialDelayTimerExpired):
return ShowToolTip(tool, tooltip);

case (SmState.Shown, SmEvent.FocusedTool):
return _currentState; // unlikely: focus without leave
case (SmState.Shown, SmEvent.LeftTool):
return HideAndStartWaitingForRefocus(tool, tooltip);
case (SmState.Shown, SmEvent.AutoPopupDelayTimerExpired):
return FullFsmReset();

case (SmState.WaitForRefocus, SmEvent.FocusedTool):
return SetupReshowTimer(tool, tooltip);
case (SmState.WaitForRefocus, SmEvent.LeftTool):
return _currentState; // OK
case (SmState.WaitForRefocus, SmEvent.RefocusWaitDelayExpired):
return FullFsmReset();

case (SmState.ReadyForReshow, SmEvent.FocusedTool):
return _currentState; // unlikely: focus without leave
case (SmState.ReadyForReshow, SmEvent.LeftTool):
return StartWaitingForRefocus(tool);
case (SmState.ReadyForReshow, SmEvent.ReshowDelayTimerExpired):
return ShowToolTip(tool, tooltip);

// This is what we would have thrown historically
(_, _) => throw new KeyNotFoundException()
};
default:
throw new KeyNotFoundException();
}
}
vladimir-krestov marked this conversation as resolved.
Show resolved Hide resolved

public void ResetStateMachine(ToolTip toolTip)
{
Expand Down
2 changes: 1 addition & 1 deletion src/System.Windows.Forms/src/System/Windows/Forms/Label.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1601,7 +1601,7 @@ private bool ShouldSerializeImage()
/// <summary>
/// Called by ToolTip to poke in that Tooltip into this ComCtl so that the Native ChildToolTip is not exposed.
/// </summary>
internal void SetToolTip(ToolTip toolTip)
protected override void SetToolTip(ToolTip toolTip, string toolTipText)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❗️ Just realised, this was previously not accessible outside the assembly, and now we're exposing it to the outside.
We need to clamp it down with private protected, which means we may need to make few adjustements at the Control level.

{
if (toolTip != null && !controlToolTip)
{
Expand Down
9 changes: 7 additions & 2 deletions src/System.Windows.Forms/src/System/Windows/Forms/ListView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5072,9 +5072,14 @@ internal void UpdateSavedCheckedItems(ListViewItem item, bool addItem)
/// <summary>
/// Called by ToolTip to poke in that Tooltip into this ComCtl so that the Native ChildToolTip is not exposed.
/// </summary>
internal void SetToolTip(ToolTip toolTip, string toolTipCaption)
protected override void SetToolTip(ToolTip toolTip, string toolTipText)
{
this.toolTipCaption = toolTipCaption;
if (toolTip == null)
{
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that call SetToolTip() with toolTip = null is not correct and we should throw ArgumentNullException. Maybe argument checks can be added to the base implementation and overridden methods should call base.SetToolTip() before the remaining implementation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good call.
We'll need to record this in dotnet/docs#17085

}

this.toolTipCaption = toolTipText;

// native ListView expects tooltip HWND as a wParam and ignores lParam
IntPtr oldHandle = User32.SendMessageW(this, (User32.WM)LVM.SETTOOLTIPS, toolTip.Handle, IntPtr.Zero);
Expand Down
61 changes: 59 additions & 2 deletions src/System.Windows.Forms/src/System/Windows/Forms/TabControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,19 @@ internal TabPage GetTabPage(int index)
return _tabPages[index];
}

internal Rectangle GetItemRectangle(int index)
{
if (index < 0 || index >= TabCount)
{
return Rectangle.Empty;
}

RECT rectangle = new RECT();
User32.SendMessageW(this, (User32.WM)ComCtl32.TCM.GETITEMRECT, (IntPtr)index, ref rectangle);

return RectangleToScreen(rectangle);
}

/// <summary>
/// This has package scope so that TabStrip and TabControl can call it.
/// </summary>
Expand Down Expand Up @@ -1360,6 +1373,20 @@ protected virtual void OnDrawItem(DrawItemEventArgs e)
_onDrawItem?.Invoke(this, e);
}

protected override void OnGotFocus(EventArgs e)
{
if (TabCount > 0 && SelectedTab != null)
{
KeyboardToolTipStateMachine.Instance.NotifyAboutGotFocus(SelectedTab);
}
else
{
KeyboardToolTipStateMachine.Instance.NotifyAboutGotFocus(this);
}

base.OnGotFocus(e);
}

/// <summary>
/// Actually goes and fires the OnLeave event. Inheriting controls
/// should use this to know when the event is fired [this is preferable to
Expand All @@ -1381,6 +1408,11 @@ protected override void OnEnter(EventArgs e)
{
SelectedTab.FireEnter(e);
}

if (TabCount == 0 && Enabled)
{
KeyboardToolTipStateMachine.Instance.NotifyAboutGotFocus(this);
}
}

/// <summary>
Expand All @@ -1403,9 +1435,24 @@ protected override void OnLeave(EventArgs e)
{
SelectedTab.FireLeave(e);
}

base.OnLeave(e);
}

protected override void OnLostFocus(EventArgs e)
{
if (TabCount > 0 && SelectedTab != null)
{
KeyboardToolTipStateMachine.Instance.NotifyAboutLostFocus(SelectedTab);
}
else
{
KeyboardToolTipStateMachine.Instance.NotifyAboutLostFocus(this);
}

base.OnLostFocus(e);
}

/// <summary>
/// We override this to get tabbing functionality.
/// If overriding this, remember to call base.onKeyDown.
Expand Down Expand Up @@ -1484,6 +1531,11 @@ protected virtual void OnSelectedIndexChanged(EventArgs e)
UpdateTabSelection(GetState(State.UISelection));
SetState(State.UISelection, false);
_onSelectedIndexChanged?.Invoke(this, e);

if (TabCount > 0 && SelectedTab != null)
{
KeyboardToolTipStateMachine.Instance.NotifyAboutGotFocus(SelectedTab);
}
}

/// <summary>
Expand Down Expand Up @@ -1662,11 +1714,16 @@ private void ResizePages()
/// <summary>
/// Called by ToolTip to poke in that Tooltip into this ComCtl so that the Native ChildToolTip is not exposed.
/// </summary>
internal void SetToolTip(ToolTip toolTip, string controlToolTipText)
protected override void SetToolTip(ToolTip toolTip, string toolTipText)
{
if (toolTip == null || !ShowToolTips)
{
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same as previous: arguments check.

}

User32.SendMessageW(this, (User32.WM)ComCtl32.TCM.SETTOOLTIPS, toolTip.Handle);
GC.KeepAlive(toolTip);
_controlTipText = controlToolTipText;
_controlTipText = toolTipText;
}

private void SetTabPage(int index, TabPage value)
Expand Down
85 changes: 84 additions & 1 deletion src/System.Windows.Forms/src/System/Windows/Forms/TabPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Design;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms.Layout;
using static Interop;

namespace System.Windows.Forms
{
Expand All @@ -26,6 +28,11 @@ namespace System.Windows.Forms
[DefaultProperty("Text")]
public class TabPage : Panel
{
/// <summary>
/// Internal property that has the default <see cref='ToolTip'/> instance if <see cref='ToolTipText'/> is set.
/// This instance is replaced if an external ToolTip instance will be set.
/// </summary>
private ToolTip _toolTip;
private ImageList.Indexer _imageIndexer;
private string _toolTipText = string.Empty;
private bool _enterFired = false;
Expand Down Expand Up @@ -360,7 +367,7 @@ public string ToolTipText
get => _toolTipText;
set
{
if (value == null)
if (string.IsNullOrWhiteSpace(value))
{
value = string.Empty;
}
Expand All @@ -372,6 +379,28 @@ public string ToolTipText

_toolTipText = value;
UpdateParent();

ToolTip.SetToolTip(this, value);
KeyboardToolTipStateMachine.Instance.Hook(this, ToolTip);
}
}

private protected override bool IsHoveredWithMouse
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider a simplified name - IsHovered

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This property overrides Control.IsHoveredWithMouse property, which related to IKeyboardToolTip.IsHoveredWithMouse() method.
image

I think it will be incorrect to make different names.

{
get
{
if (ParentInternal is TabControl tabControl)
{
for (int i = 0; i < tabControl.TabCount; i++)
{
if (tabControl.GetItemRectangle(i).Contains(MousePosition))
{
return true;
}
}
}

return ParentInternal.RectangleToScreen(ClientRectangle).Contains(MousePosition);
}
}

Expand Down Expand Up @@ -442,6 +471,13 @@ internal void FireEnter(EventArgs e)
OnEnter(e);
}

protected override void OnGotFocus(EventArgs e)
{
KeyboardToolTipStateMachine.Instance.NotifyAboutGotFocus(this);

base.OnGotFocus(e);
}

/// <summary>
/// Actually goes and fires the OnEnter event. Inheriting controls should use this to know
/// when the event is fired [this is preferable to adding an event handler on yourself for
Expand All @@ -464,6 +500,23 @@ protected override void OnEnter(EventArgs e)
}
}

internal override Rectangle GetToolNativeScreenRectangle()
vladimir-krestov marked this conversation as resolved.
Show resolved Hide resolved
{
// A tooltip will be shown near a selected tab
if (ParentInternal is TabControl tabControl)
{
return tabControl.GetItemRectangle(tabControl.SelectedIndex);
}

return Rectangle.Empty;
}

internal ToolTip ToolTip
{
get => _toolTip ??= new ToolTip();
set => _toolTip = value;
}

/// <summary>
/// Actually goes and fires the OnLeave event. Inheriting controls should use this to know
/// when the event is fired [this is preferable to adding an event handler on yourself for
Expand All @@ -483,10 +536,18 @@ protected override void OnLeave(EventArgs e)
base.OnLeave(e);
}

KeyboardToolTipStateMachine.Instance.NotifyAboutLostFocus(this);
_leaveFired = false;
}
}

protected override void OnLostFocus(EventArgs e)
{
KeyboardToolTipStateMachine.Instance.NotifyAboutLostFocus(this);

base.OnLostFocus(e);
}

protected override void OnPaintBackground(PaintEventArgs e)
{
// Utilize the TabRenderer new to Whidbey to draw the tab pages so that the panels are
Expand Down Expand Up @@ -542,6 +603,28 @@ protected override void SetBoundsCore(int x, int y, int width, int height, Bound
}
}

protected override void SetToolTip(ToolTip toolTip, string toolTipText)
{
if (toolTip == null)
{
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same as previous: argument check.

}

// Check if there is an existing ToolTip object that is showing tooltips,
// if so - reset it to avoid showing several tooltips (old and new) at the same time.
if (ToolTip != toolTip)
{
ToolTip.SetToolTip(this, null);
ToolTip = toolTip;
}

// Show the same tooltip for the page's tab.
if (toolTipText != null && ToolTipText != toolTipText)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test ToolTipText != toolTipText is already in the property setter implementation.

{
ToolTipText = toolTipText;
}
}

/// <summary>
/// Determines if the Location property needs to be persisted.
/// </summary>
Expand Down
Loading