diff --git a/Src/Common/Controls/DetailControls/BasicTypeSlices.cs b/Src/Common/Controls/DetailControls/BasicTypeSlices.cs
index f2135c7e68..26cc7504b8 100644
--- a/Src/Common/Controls/DetailControls/BasicTypeSlices.cs
+++ b/Src/Common/Controls/DetailControls/BasicTypeSlices.cs
@@ -10,6 +10,7 @@
//
using System;
+using System.Diagnostics;
using System.Globalization;
using System.Windows.Forms;
using System.Xml;
@@ -307,13 +308,58 @@ public override void PropChanged(int hvo, int tag, int ivMin, int cvIns, int cvD
///
public class DateSlice : FieldSlice, IVwNotifyChange
{
+ private static readonly TraceSwitch s_wheelTraceSwitch = new TraceSwitch("DateSliceWheel", "");
+ private const int kWmMouseWheel = 0x020A;
+
+ private sealed class WheelForwardingRichTextBox : RichTextBox
+ {
+ protected override void WndProc(ref Message m)
+ {
+ if (m.Msg == kWmMouseWheel)
+ {
+ int delta = (short)((long)m.WParam >> 16);
+ bool handled = TryScrollOwningDataTree(this, delta);
+ TraceWheel(string.Format("RichTextBoxWndProc delta={0} handled={1}", delta, handled));
+ if (handled)
+ return;
+ }
+
+ base.WndProc(ref m);
+ }
+ }
+
+ private static void TraceWheel(string message)
+ {
+ if (s_wheelTraceSwitch.TraceInfo || s_wheelTraceSwitch.TraceVerbose)
+ Trace.WriteLine("DateSliceWheel: " + message);
+ }
+
+ internal static bool TryScrollOwningDataTree(Control control, int delta)
+ {
+ for (Control current = control; current != null; current = current.Parent)
+ {
+ var dataTree = current as DataTree;
+ if (dataTree != null)
+ {
+ bool handled = DataTree.TryHandleWheelScroll(dataTree, delta);
+ TraceWheel(string.Format(
+ "TryScrollOwningDataTree delta={0} dataTreeVisible={1} handled={2}",
+ delta, dataTree.Visible, handled));
+ return handled;
+ }
+ }
+
+ TraceWheel(string.Format("TryScrollOwningDataTree delta={0} no-datatree", delta));
+ return false;
+ }
+
/// -----------------------------------------------------------------------------------
///
/// Initializes a new instance of the class.
///
/// -----------------------------------------------------------------------------------
public DateSlice(LcmCache cache, ICmObject obj, int flid)
- : base(new RichTextBox(), cache, obj, flid)
+ : base(new WheelForwardingRichTextBox(), cache, obj, flid)
{
// JohnT: per comment at the end of LT-7073, we want the normal window color for this
// slice. It's also nice to be able to select and copy. Setting ReadOnly is enough to prevent
diff --git a/Src/Common/Controls/DetailControls/DataTree.cs b/Src/Common/Controls/DetailControls/DataTree.cs
index 65adc8f8c6..0a02ea6748 100644
--- a/Src/Common/Controls/DetailControls/DataTree.cs
+++ b/Src/Common/Controls/DetailControls/DataTree.cs
@@ -4531,6 +4531,50 @@ private bool EquivalentKeys(object[] newKey, object[] oldKey, bool fCheckInts)
}
return true;
}
+
+ internal static int GetWheelScrollPixels(DataTree dataTree, int delta)
+ {
+ if (delta == 0)
+ return 0;
+
+ int scrollLines = SystemInformation.MouseWheelScrollLines;
+ if (scrollLines == 0)
+ return 0;
+
+ if (scrollLines == int.MaxValue)
+ return Math.Sign(delta) * dataTree.ClientRectangle.Height;
+
+ double linesToScroll = (double)delta / SystemInformation.MouseWheelScrollDelta * scrollLines;
+ return (int)Math.Round(linesToScroll * dataTree.Font.Height, MidpointRounding.AwayFromZero);
+ }
+
+ internal static bool TryGetWheelScrollPosition(DataTree dataTree, int delta, out int newY)
+ {
+ int currentY = -dataTree.AutoScrollPosition.Y;
+ int maxScroll = Math.Max(0,
+ dataTree.AutoScrollMinSize.Height - dataTree.ClientRectangle.Height);
+ int pixelDelta = GetWheelScrollPixels(dataTree, delta);
+ newY = Math.Max(0, Math.Min(currentY - pixelDelta, maxScroll));
+ return newY != currentY;
+ }
+
+ internal static bool CanRedirectWheelMessage(DataTree dataTree)
+ {
+ return dataTree.IsHandleCreated && !dataTree.IsDisposed && dataTree.Visible;
+ }
+
+ internal static bool TryHandleWheelScroll(DataTree dataTree, int delta)
+ {
+ if (!CanRedirectWheelMessage(dataTree))
+ return false;
+
+ int newY;
+ if (!TryGetWheelScrollPosition(dataTree, delta, out newY))
+ return false;
+
+ dataTree.AutoScrollPosition = new Point(0, newY);
+ return true;
+ }
}
class DummyObjectSlice : Slice
diff --git a/Src/Common/Controls/DetailControls/DetailControlsTests/DataTreeTests.cs b/Src/Common/Controls/DetailControls/DetailControlsTests/DataTreeTests.cs
index ac83210b6d..555e744b98 100644
--- a/Src/Common/Controls/DetailControls/DetailControlsTests/DataTreeTests.cs
+++ b/Src/Common/Controls/DetailControls/DetailControlsTests/DataTreeTests.cs
@@ -4,7 +4,9 @@
using System;
using System.Collections.Generic;
+using System.Drawing;
using System.IO;
+using System.Reflection;
using System.Windows.Forms;
using System.Xml;
using NUnit.Framework;
@@ -31,6 +33,13 @@ public class DataTreeTests : MemoryOnlyBackendProviderRestoredForEachTestTestBas
private DataTree m_dtree;
private Form m_parent;
+ private sealed class ScrollTestDataTree : DataTree
+ {
+ protected override void OnPaint(PaintEventArgs e)
+ {
+ }
+ }
+
private CustomFieldForTest m_customField;
#region Fixture Setup and Teardown
internal static Inventory GenerateParts()
@@ -77,6 +86,30 @@ public override void FixtureSetup()
}
#endregion
+ private static DataTree CreateScrollableDataTree(Form parent)
+ {
+ var dataTree = new ScrollTestDataTree();
+ parent.Size = new Size(400, 200);
+ dataTree.Dock = DockStyle.Fill;
+ parent.Controls.Add(dataTree);
+
+ for (int i = 0; i < 12; i++)
+ {
+ var slice = new Slice(new Panel { Dock = DockStyle.Fill })
+ {
+ Visible = true,
+ Size = new Size(360, 50),
+ Location = new Point(0, i * 50)
+ };
+ dataTree.Controls.Add(slice);
+ slice.Install(dataTree);
+ }
+
+ parent.Show();
+ Application.DoEvents();
+ return dataTree;
+ }
+
#region Test setup and teardown
/// ------------------------------------------------------------------------------------
///
@@ -275,6 +308,102 @@ public void GetGuidForJumpToTool_UsesRootObject_WhenNoCurrentSlice()
}
}
+ [Test]
+ public void GetWheelScrollPixels_UsesSystemWheelSettings()
+ {
+ m_dtree.Bounds = new Rectangle(0, 0, 200, 100);
+
+ int delta = SystemInformation.MouseWheelScrollDelta;
+ int scrollLines = SystemInformation.MouseWheelScrollLines;
+ int expectedPixels;
+ if (scrollLines == 0)
+ {
+ expectedPixels = 0;
+ }
+ else if (scrollLines == int.MaxValue)
+ {
+ expectedPixels = m_dtree.ClientRectangle.Height;
+ }
+ else
+ {
+ expectedPixels = (int)Math.Round((double)scrollLines * m_dtree.Font.Height,
+ MidpointRounding.AwayFromZero);
+ }
+
+ Assert.That(DataTree.GetWheelScrollPixels(m_dtree, delta), Is.EqualTo(expectedPixels));
+ Assert.That(DataTree.GetWheelScrollPixels(m_dtree, -delta), Is.EqualTo(-expectedPixels));
+ }
+
+ [Test]
+ public void TryGetWheelScrollPosition_ReturnsFalse_WhenAlreadyAtTop()
+ {
+ m_dtree.Bounds = new Rectangle(0, 0, 200, 100);
+ m_dtree.AutoScrollMinSize = new Size(200, 1000);
+ m_dtree.AutoScrollPosition = new Point(0, 0);
+
+ int newY;
+ bool handled = DataTree.TryGetWheelScrollPosition(m_dtree,
+ SystemInformation.MouseWheelScrollDelta, out newY);
+
+ Assert.That(handled, Is.False);
+ Assert.That(newY, Is.EqualTo(0));
+ }
+
+ [Test]
+ public void CanRedirectWheelMessage_ReturnsFalse_WhenDataTreeHidden()
+ {
+ m_parent.Show();
+ m_dtree.Show();
+ Assert.That(m_dtree.IsHandleCreated, Is.True);
+
+ m_dtree.Hide();
+
+ Assert.That(DataTree.CanRedirectWheelMessage(m_dtree), Is.False);
+ }
+
+ [Test]
+ public void TryHandleWheelScroll_UpdatesScrollPosition_WhenScrollingIsPossible()
+ {
+ using (var parent = new Form())
+ {
+ var dataTree = CreateScrollableDataTree(parent);
+ dataTree.AutoScrollPosition = new Point(0, 0);
+
+ bool handled = DataTree.TryHandleWheelScroll(dataTree, -SystemInformation.MouseWheelScrollDelta);
+
+ Assert.That(handled, Is.True);
+ Assert.That(-dataTree.AutoScrollPosition.Y, Is.GreaterThan(0));
+ }
+ }
+
+ [Test]
+ public void TryScrollOwningDataTree_ScrollsContainingDataTree_FromDateSliceHostedControl()
+ {
+ using (var parent = new Form())
+ {
+ var dataTree = CreateScrollableDataTree(parent);
+ dataTree.AutoScrollPosition = new Point(0, 0);
+
+ using (var host = new Panel())
+ using (var control = new RichTextBox())
+ {
+ host.Bounds = new Rectangle(0, 0, 150, 20);
+ control.Dock = DockStyle.Fill;
+ host.Controls.Add(control);
+ dataTree.Controls.Add(host);
+ host.Show();
+ control.Show();
+ Application.DoEvents();
+
+ bool handled = DateSlice.TryScrollOwningDataTree(control,
+ -SystemInformation.MouseWheelScrollDelta);
+
+ Assert.That(handled, Is.True);
+ Assert.That(-dataTree.AutoScrollPosition.Y, Is.GreaterThan(0));
+ }
+ }
+ }
+
///
[Test]
public void OwnedObjects()