Skip to content

Commit 85bf8ed

Browse files
committed
LT-22452: fix DateSlice mouse wheel scrolling
1 parent 28e9888 commit 85bf8ed

3 files changed

Lines changed: 220 additions & 1 deletion

File tree

Src/Common/Controls/DetailControls/BasicTypeSlices.cs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
// </remarks>
1111

1212
using System;
13+
using System.Diagnostics;
1314
using System.Globalization;
1415
using System.Windows.Forms;
1516
using System.Xml;
@@ -307,13 +308,58 @@ public override void PropChanged(int hvo, int tag, int ivMin, int cvIns, int cvD
307308
/// </summary>
308309
public class DateSlice : FieldSlice, IVwNotifyChange
309310
{
311+
private static readonly TraceSwitch s_wheelTraceSwitch = new TraceSwitch("DateSliceWheel", "");
312+
private const int kWmMouseWheel = 0x020A;
313+
314+
private sealed class WheelForwardingRichTextBox : RichTextBox
315+
{
316+
protected override void WndProc(ref Message m)
317+
{
318+
if (m.Msg == kWmMouseWheel)
319+
{
320+
int delta = (short)((long)m.WParam >> 16);
321+
bool handled = TryScrollOwningDataTree(this, delta);
322+
TraceWheel(string.Format("RichTextBoxWndProc delta={0} handled={1}", delta, handled));
323+
if (handled)
324+
return;
325+
}
326+
327+
base.WndProc(ref m);
328+
}
329+
}
330+
331+
private static void TraceWheel(string message)
332+
{
333+
if (s_wheelTraceSwitch.TraceInfo || s_wheelTraceSwitch.TraceVerbose)
334+
Trace.WriteLine("DateSliceWheel: " + message);
335+
}
336+
337+
internal static bool TryScrollOwningDataTree(Control control, int delta)
338+
{
339+
for (Control current = control; current != null; current = current.Parent)
340+
{
341+
var dataTree = current as DataTree;
342+
if (dataTree != null)
343+
{
344+
bool handled = DataTree.TryHandleWheelScroll(dataTree, delta);
345+
TraceWheel(string.Format(
346+
"TryScrollOwningDataTree delta={0} dataTreeVisible={1} handled={2}",
347+
delta, dataTree.Visible, handled));
348+
return handled;
349+
}
350+
}
351+
352+
TraceWheel(string.Format("TryScrollOwningDataTree delta={0} no-datatree", delta));
353+
return false;
354+
}
355+
310356
/// -----------------------------------------------------------------------------------
311357
/// <summary>
312358
/// Initializes a new instance of the <see cref="DateSlice"/> class.
313359
/// </summary>
314360
/// -----------------------------------------------------------------------------------
315361
public DateSlice(LcmCache cache, ICmObject obj, int flid)
316-
: base(new RichTextBox(), cache, obj, flid)
362+
: base(new WheelForwardingRichTextBox(), cache, obj, flid)
317363
{
318364
// JohnT: per comment at the end of LT-7073, we want the normal window color for this
319365
// slice. It's also nice to be able to select and copy. Setting ReadOnly is enough to prevent

Src/Common/Controls/DetailControls/DataTree.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4531,6 +4531,50 @@ private bool EquivalentKeys(object[] newKey, object[] oldKey, bool fCheckInts)
45314531
}
45324532
return true;
45334533
}
4534+
4535+
internal static int GetWheelScrollPixels(DataTree dataTree, int delta)
4536+
{
4537+
if (delta == 0)
4538+
return 0;
4539+
4540+
int scrollLines = SystemInformation.MouseWheelScrollLines;
4541+
if (scrollLines == 0)
4542+
return 0;
4543+
4544+
if (scrollLines == int.MaxValue)
4545+
return Math.Sign(delta) * dataTree.ClientRectangle.Height;
4546+
4547+
double linesToScroll = (double)delta / SystemInformation.MouseWheelScrollDelta * scrollLines;
4548+
return (int)Math.Round(linesToScroll * dataTree.Font.Height, MidpointRounding.AwayFromZero);
4549+
}
4550+
4551+
internal static bool TryGetWheelScrollPosition(DataTree dataTree, int delta, out int newY)
4552+
{
4553+
int currentY = -dataTree.AutoScrollPosition.Y;
4554+
int maxScroll = Math.Max(0,
4555+
dataTree.AutoScrollMinSize.Height - dataTree.ClientRectangle.Height);
4556+
int pixelDelta = GetWheelScrollPixels(dataTree, delta);
4557+
newY = Math.Max(0, Math.Min(currentY - pixelDelta, maxScroll));
4558+
return newY != currentY;
4559+
}
4560+
4561+
internal static bool CanRedirectWheelMessage(DataTree dataTree)
4562+
{
4563+
return dataTree.IsHandleCreated && !dataTree.IsDisposed && dataTree.Visible;
4564+
}
4565+
4566+
internal static bool TryHandleWheelScroll(DataTree dataTree, int delta)
4567+
{
4568+
if (!CanRedirectWheelMessage(dataTree))
4569+
return false;
4570+
4571+
int newY;
4572+
if (!TryGetWheelScrollPosition(dataTree, delta, out newY))
4573+
return false;
4574+
4575+
dataTree.AutoScrollPosition = new Point(0, newY);
4576+
return true;
4577+
}
45344578
}
45354579

45364580
class DummyObjectSlice : Slice

Src/Common/Controls/DetailControls/DetailControlsTests/DataTreeTests.cs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
using System;
66
using System.Collections.Generic;
7+
using System.Drawing;
78
using System.IO;
9+
using System.Reflection;
810
using System.Windows.Forms;
911
using System.Xml;
1012
using NUnit.Framework;
@@ -31,6 +33,13 @@ public class DataTreeTests : MemoryOnlyBackendProviderRestoredForEachTestTestBas
3133
private DataTree m_dtree;
3234
private Form m_parent;
3335

36+
private sealed class ScrollTestDataTree : DataTree
37+
{
38+
protected override void OnPaint(PaintEventArgs e)
39+
{
40+
}
41+
}
42+
3443
private CustomFieldForTest m_customField;
3544
#region Fixture Setup and Teardown
3645
internal static Inventory GenerateParts()
@@ -77,6 +86,30 @@ public override void FixtureSetup()
7786
}
7887
#endregion
7988

89+
private static DataTree CreateScrollableDataTree(Form parent)
90+
{
91+
var dataTree = new ScrollTestDataTree();
92+
parent.Size = new Size(400, 200);
93+
dataTree.Dock = DockStyle.Fill;
94+
parent.Controls.Add(dataTree);
95+
96+
for (int i = 0; i < 12; i++)
97+
{
98+
var slice = new Slice(new Panel { Dock = DockStyle.Fill })
99+
{
100+
Visible = true,
101+
Size = new Size(360, 50),
102+
Location = new Point(0, i * 50)
103+
};
104+
dataTree.Controls.Add(slice);
105+
slice.Install(dataTree);
106+
}
107+
108+
parent.Show();
109+
Application.DoEvents();
110+
return dataTree;
111+
}
112+
80113
#region Test setup and teardown
81114
/// ------------------------------------------------------------------------------------
82115
/// <summary>
@@ -275,6 +308,102 @@ public void GetGuidForJumpToTool_UsesRootObject_WhenNoCurrentSlice()
275308
}
276309
}
277310

311+
[Test]
312+
public void GetWheelScrollPixels_UsesSystemWheelSettings()
313+
{
314+
m_dtree.Bounds = new Rectangle(0, 0, 200, 100);
315+
316+
int delta = SystemInformation.MouseWheelScrollDelta;
317+
int scrollLines = SystemInformation.MouseWheelScrollLines;
318+
int expectedPixels;
319+
if (scrollLines == 0)
320+
{
321+
expectedPixels = 0;
322+
}
323+
else if (scrollLines == int.MaxValue)
324+
{
325+
expectedPixels = m_dtree.ClientRectangle.Height;
326+
}
327+
else
328+
{
329+
expectedPixels = (int)Math.Round((double)scrollLines * m_dtree.Font.Height,
330+
MidpointRounding.AwayFromZero);
331+
}
332+
333+
Assert.That(DataTree.GetWheelScrollPixels(m_dtree, delta), Is.EqualTo(expectedPixels));
334+
Assert.That(DataTree.GetWheelScrollPixels(m_dtree, -delta), Is.EqualTo(-expectedPixels));
335+
}
336+
337+
[Test]
338+
public void TryGetWheelScrollPosition_ReturnsFalse_WhenAlreadyAtTop()
339+
{
340+
m_dtree.Bounds = new Rectangle(0, 0, 200, 100);
341+
m_dtree.AutoScrollMinSize = new Size(200, 1000);
342+
m_dtree.AutoScrollPosition = new Point(0, 0);
343+
344+
int newY;
345+
bool handled = DataTree.TryGetWheelScrollPosition(m_dtree,
346+
SystemInformation.MouseWheelScrollDelta, out newY);
347+
348+
Assert.That(handled, Is.False);
349+
Assert.That(newY, Is.EqualTo(0));
350+
}
351+
352+
[Test]
353+
public void CanRedirectWheelMessage_ReturnsFalse_WhenDataTreeHidden()
354+
{
355+
m_parent.Show();
356+
m_dtree.Show();
357+
Assert.That(m_dtree.IsHandleCreated, Is.True);
358+
359+
m_dtree.Hide();
360+
361+
Assert.That(DataTree.CanRedirectWheelMessage(m_dtree), Is.False);
362+
}
363+
364+
[Test]
365+
public void TryHandleWheelScroll_UpdatesScrollPosition_WhenScrollingIsPossible()
366+
{
367+
using (var parent = new Form())
368+
{
369+
var dataTree = CreateScrollableDataTree(parent);
370+
dataTree.AutoScrollPosition = new Point(0, 0);
371+
372+
bool handled = DataTree.TryHandleWheelScroll(dataTree, -SystemInformation.MouseWheelScrollDelta);
373+
374+
Assert.That(handled, Is.True);
375+
Assert.That(-dataTree.AutoScrollPosition.Y, Is.GreaterThan(0));
376+
}
377+
}
378+
379+
[Test]
380+
public void TryScrollOwningDataTree_ScrollsContainingDataTree_FromDateSliceHostedControl()
381+
{
382+
using (var parent = new Form())
383+
{
384+
var dataTree = CreateScrollableDataTree(parent);
385+
dataTree.AutoScrollPosition = new Point(0, 0);
386+
387+
using (var host = new Panel())
388+
using (var control = new RichTextBox())
389+
{
390+
host.Bounds = new Rectangle(0, 0, 150, 20);
391+
control.Dock = DockStyle.Fill;
392+
host.Controls.Add(control);
393+
dataTree.Controls.Add(host);
394+
host.Show();
395+
control.Show();
396+
Application.DoEvents();
397+
398+
bool handled = DateSlice.TryScrollOwningDataTree(control,
399+
-SystemInformation.MouseWheelScrollDelta);
400+
401+
Assert.That(handled, Is.True);
402+
Assert.That(-dataTree.AutoScrollPosition.Y, Is.GreaterThan(0));
403+
}
404+
}
405+
}
406+
278407
/// <summary></summary>
279408
[Test]
280409
public void OwnedObjects()

0 commit comments

Comments
 (0)