3

According to the documentation "(FrameworkElement.SizeChanged) Occurs when either the ActualHeight or the ActualWidth properties change value on this element."

Running the following test method, my Rectangle's ActualWidth and ActualHeight are updated after calling Measure and Arrange, however the SizeChanged RoutedEvent is never raised.

    [Test]
    [RequiresSTA]
    public void Test_Something()
    {
        bool sizeChangedRaised = false;

        var r = new Rectangle { Width = 10, Height = 10 };
        r.Measure(new Size(20, 20));
        r.Arrange(new Rect(0, 0, 20, 20));

        //r.ActualWidth and r.ActualHeight are both 10 at this point.

        r.SizeChanged += (s, e) => sizeChangedRaised = true;

        r.Width = 5;
        r.Height = 5;
        r.Measure(new Size(20, 20));
        r.Arrange(new Rect(0, 0, 20, 20));

        //r.ActualWidth and r.ActualHeight are now both 5.

        Assert.That(sizeChangedRaised);// false
    }

Can anyone explain why SizeChanged isn't raised?
Is there a way I can set things up so that it is raised?


SOLUTION

Slipping in H.B.'s static class this test passes:

   [Test]
    [RequiresSTA]
    public void Test_Something()
    {
        bool sizeChangedRaised = false;
        var r = new Rectangle { Width = 10, Height = 10 };
        r.Measure(new Size(20, 20));
        r.Arrange(new Rect(0, 0, 20, 20));
        r.SizeChanged += (s, e) => {
            sizeChangedRaised = true;
        };
        r.Width = 5;
        r.Height = 5;
        r.Measure(new Size(20, 20));
        r.Arrange(new Rect(0, 0, 20, 20));
        ThreadEx.Wait(1000);
        Assert.That(sizeChangedRaised);

    }

    static class ThreadEx
    {
        public static void Wait(int millisecondsTimeout)
        {
            Wait(TimeSpan.FromMilliseconds(millisecondsTimeout));
        }

        public static void Wait(TimeSpan timeout)
        {
            var frame = new DispatcherFrame();
            new Thread(() =>
                           {
                               Thread.Sleep(timeout);
                               frame.Continue = false;
                           }).Start();
            Dispatcher.PushFrame(frame);
        }
    }
Grokodile
  • 3,881
  • 5
  • 33
  • 59
  • Just a caveat, this test does not look very safe in this form, a timeout of one second is quite generous and i do not know how your actual objects are manipulated but if something else is causing size changes you might get a false positive from that test at some point as the event fires due to a different change than the one you want to test. – H.B. Jul 27 '11 at 23:50

2 Answers2

4

The rectangle is managed by the UI-thread which does things in a queued manner, the SizeChanged fires long after the complete method has been executed, if you just set a breakpoint or show a MessageBox instead of setting a bool you will see that it indeed is being fired.

Edit: How i would go about testing it; just add some delay beforehand:

ThreadEx.Wait(1000);
Assert.That(sizeChangedRaised);
static class ThreadEx
{
    public static void Wait(int millisecondsTimeout)
    {
        Wait(TimeSpan.FromMilliseconds(millisecondsTimeout));
    }

    public static void Wait(TimeSpan timeout)
    {
        var frame = new DispatcherFrame();
        new Thread((ThreadStart)(() =>
        {
            Thread.Sleep(timeout);
            frame.Continue = false;
        })).Start();
        Dispatcher.PushFrame(frame);
    }
}
H.B.
  • 166,899
  • 29
  • 327
  • 400
  • does this relate to the Dispatcher, I have some other tests where I've created a DispatcherFrame and called Dispatcher.PushFrame(frame) so that jobs queued by Dispatcher.BeginInvoke can be tested, would I need some similar infrastructure to get RoutedEvents raised? – Grokodile Jul 27 '11 at 22:47
  • @panamack: Yes, that would be one way of doing it, i would probably approach it via some timeout so that if the event is not raised until then the test fails. – H.B. Jul 27 '11 at 22:52
  • 1
    You are now officially my Hero. – Grokodile Jul 27 '11 at 23:23
0

This can actually be done with one line of code:

[Test]
[RequiresSTA]
public void Test_Something()
{
    bool sizeChangedRaised = false;

    var r = new Rectangle { Width = 10, Height = 10 };
    r.Measure(new Size(20, 20));
    r.Arrange(new Rect(0, 0, 20, 20));

    //r.ActualWidth and r.ActualHeight are both 10 at this point.

    r.SizeChanged += (s, e) => sizeChangedRaised = true;

    r.Width = 5;
    r.Height = 5;
    r.Measure(new Size(20, 20));
    r.Arrange(new Rect(0, 0, 20, 20));

    // this is equivalent to Application.DoEvents() in WinForms.
    // we need this because events need to be fired before the method ends
    Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, new Action(() => { }));

    //r.ActualWidth and r.ActualHeight are now both 5.

    Assert.That(sizeChangedRaised);// false
}
Ketobomb
  • 2,108
  • 1
  • 17
  • 27