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

Wpf: Fix crash when adding items to a TreeGridView multiple times when it has focus #2732

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions src/Eto.Wpf/Forms/Controls/TreeGridViewHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,10 @@ public ITreeGridStore<ITreeGridItem> DataStore
set
{
if (Control.ItemsSource is EtoGridCollectionView collectionView)
{
collectionView.Unregister();
Control.ItemsSource = null;
}

controller.InitializeItems(value);

Expand Down
7 changes: 5 additions & 2 deletions test/Eto.Test.Wpf/UnitTests/BitmapTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ namespace Eto.Test.Wpf.UnitTests
[TestFixture]
public class BitmapTests : TestBase
{
[Test, Timeout(1000)]
public void CreatingManySmallBitmapsShouldBeFast()
[Test, CancelAfter(1000)]
public void CreatingManySmallBitmapsShouldBeFast(CancellationToken token)
{
var sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 100; i++)
{
if (token.IsCancellationRequested)
break;

var bmp = new Bitmap(20, 20, PixelFormat.Format32bppRgba);

using (var g = new Graphics(bmp))
Expand Down
5 changes: 5 additions & 0 deletions test/Eto.Test/NativeHostControls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Eto.Test.UnitTests;

namespace Eto.Test
{
Expand All @@ -27,6 +28,10 @@ public NativeHostTest(string name, Func<object> create)

public static class NativeHostControls
{
static NativeHostControls()
{
EtoTestSetup.Initialize();
}
static INativeHostControls Handler => Platform.Instance.CreateShared<INativeHostControls>();

public static IEnumerable<NativeHostTest> GetNativeHostTests() => Handler.GetNativeHostTests();
Expand Down
34 changes: 34 additions & 0 deletions test/Eto.Test/UnitTests/Forms/Controls/TreeGridViewTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,5 +152,39 @@ protected override void SetDataStore(TreeGridView grid, IEnumerable<object> data
{
grid.DataStore = (ITreeGridStore<ITreeGridItem>)dataStore;
}

[Test, CancelAfter(3000)]
public void AddingMultipleItemsShouldNotCrash(CancellationToken token) => Async(async () =>
{
var tree = new TreeGridView { Size = new Size(200, 400) };

tree.Columns.Add(new GridColumn { HeaderText = "Column 1", DataCell = new TextBoxCell(0) });
var items = new TreeGridItemCollection
{
// important to have a row first so it can get row/column focus
new TreeGridItem { Values = new[] { "Item" } }
};

tree.DataStore = items;

var form = new Form { Content = tree };
form.Shown += (sender, e) => tree.Focus();
form.Show();

for (int i = 0; i < 1000; i++)
{
if (token.IsCancellationRequested)
break;
items.Clear();
for (int j = 0; j < i; j++)
{
items.Add(new TreeGridItem { Values = new[] { "Item " + j } });
}
tree.DataStore = items;
}
await Task.Delay(1000);
form.Close();

});
}
}
6 changes: 3 additions & 3 deletions test/Eto.Test/UnitTests/Forms/FormTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ void EventTriggered(object sender, TEvent e)
}

[Test, ManualTest]
public void MultipleChildWindowsShouldGetFocusWhenClicked() => Async(async () =>
public void MultipleChildWindowsShouldGetFocusWhenClicked() => Async(-1, async () =>
{
var form1 = new Form { ClientSize = new Size(200, 200), Location = new Point(300, 300) };
form1.Owner = Application.Instance.MainForm;
Expand Down Expand Up @@ -116,7 +116,7 @@ public void ClosedEventShouldFireOnceWithMultipleSubclasses()
[TestCase(true)]
[TestCase(false)]
[ManualTest]
public void CallingShowTwiceShouldWork(bool showActivated) => Async(async () =>
public void CallingShowTwiceShouldWork(bool showActivated) => Async(-1, async () =>
{
var form = new Form();

Expand All @@ -142,7 +142,7 @@ public void CallingShowTwiceShouldWork(bool showActivated) => Async(async () =>

[Test]
[ManualTest]
public void CallingShowAfterShownShouldNotBringItTopMost() => Async(async () =>
public void CallingShowAfterShownShouldNotBringItTopMost() => Async(-1, async () =>
{
var form = new Form();

Expand Down
4 changes: 2 additions & 2 deletions test/Eto.Test/UnitTests/Forms/WindowTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public void WindowShouldAutoSize() => Test(window =>
[TestCase(true, false, true)]
[TestCase(false, true, false)]
[TestCase(false, false, true)]
public void WindowShouldHaveCorrectInitialSizeWithWrappedLabel(bool useSize, bool setWidth, bool setHeight) => Async(async () =>
public void WindowShouldHaveCorrectInitialSizeWithWrappedLabel(bool useSize, bool setWidth, bool setHeight) => Async(-1, async () =>
{
bool wasClicked = false;

Expand Down Expand Up @@ -371,7 +371,7 @@ public void ShownShouldBeCalledInCorrectOrder() => Async(async () =>

[Test]
[ManualTest]
public void WindowShouldNotBeShowingDuringLoadComplete() => Async(async () =>
public void WindowShouldNotBeShowingDuringLoadComplete() => Async(-1, async () =>
{
bool? loadComplete = null;
bool? shown = null;
Expand Down
55 changes: 40 additions & 15 deletions test/Eto.Test/UnitTests/TestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,22 @@ public override TestResult Execute(TestExecutionContext context)
[SetUpFixture]
public class EtoTestSetup
{
static bool _quit;
static bool _initialized;
static bool _appWasCreated;

/// <summary>
/// Timeout for application initialization
/// </summary>
protected const int ApplicationTimeout = 10000;
protected const int ApplicationTimeout = 2000;

/// <summary>
/// initializes the application when running unit tests directly through the IDE or NUnit gui.
/// To run on specific platforms, run it through the test runner in the Eto.Test app
/// </summary>
public static void Initialize()
{
if (_initialized)
return;
var platform = Platform.Instance;
if (platform == null)
{
Expand Down Expand Up @@ -130,7 +132,7 @@ public static void Initialize()
{
var ev = new ManualResetEvent(false);
Exception exception = null;
var thread = new Thread(() =>
var thread = new Thread(() =>
{
try
{
Expand All @@ -150,11 +152,14 @@ public static void Initialize()
exception = ex;
ev.Set();
}
});
#if WINDOWS
}
);

#pragma warning disable CA1416 // Validate platform compatibility
if (EtoEnvironment.Platform.IsWindows)
thread.SetApartmentState(ApartmentState.STA);
#endif
#pragma warning restore CA1416 // Validate platform compatibility

thread.Start();
if (!ev.WaitOne(ApplicationTimeout))
Assert.Fail("Could not initialize application");
Expand All @@ -164,6 +169,7 @@ public static void Initialize()
ExceptionDispatchInfo.Capture(exception).Throw();
}
}
_initialized = true;
}

[OneTimeSetUp]
Expand All @@ -175,7 +181,6 @@ public void GlobalSetup()
[OneTimeTearDown]
public void GlobalTeardown()
{
_quit = true;
if (!_appWasCreated)
return;
Application.Instance?.AsyncInvoke(() =>
Expand Down Expand Up @@ -225,13 +230,17 @@ static Application Application
/// <param name="timeout">Timeout to wait for the operation to complete</param>
public static void Run(Action<Application, Action> test, int timeout = DefaultTimeout)
{
var evStart = new ManualResetEvent(false);
var ev = new ManualResetEvent(false);
var application = Application;
Exception exception = null;
Action finished = () => ev.Set();
var context = TestExecutionContext.CurrentContext;
Action run = () =>

void finished() => ev.Set();

void run()
{
evStart.Set();
try
{
context.EstablishExecutionEnvironment();
Expand All @@ -242,16 +251,19 @@ public static void Run(Action<Application, Action> test, int timeout = DefaultTi
exception = ex;
ev.Set();
}
};
}

if (application != null)
application.AsyncInvoke(run);
else
run();

if (!evStart.WaitOne(DefaultTimeout))
Assert.Fail("Could not start test in time");

if (!ev.WaitOne(timeout))
{
Assert.Fail("Test did not complete in time");
}

if (exception != null)
ExceptionDispatchInfo.Capture(exception).Throw();
}
Expand Down Expand Up @@ -329,12 +341,16 @@ public static void Form<T>(Action<T> test, int timeout = DefaultTimeout)
}
}

public static void Async(Func<Task> test)
public static void Async(Func<Task> test) => Async(DefaultTimeout, test);

public static void Async(int timeout, Func<Task> test)
{
Exception exception = null;
var mreStart = new ManualResetEvent(false);
var mre = new ManualResetEvent(false);
Application.Instance.Invoke(async () =>
Application.Instance.AsyncInvoke(async () =>
{
mreStart.Set();
try
{
await test();
Expand All @@ -348,7 +364,16 @@ public static void Async(Func<Task> test)
mre.Set();
}
});
mre.WaitOne();
if (!mreStart.WaitOne(DefaultTimeout))
{
Assert.Fail("Could not start test in time");
return;
}
if (!mre.WaitOne(timeout))
{
Assert.Fail("Test did not complete in time");
return;
}
if (exception != null)
{
ExceptionDispatchInfo.Capture(exception).Throw();
Expand Down
Loading