tags:

views:

2829

answers:

3

I've got a windows forms map in a WindowsFormsHost, and I need it to resize with the window.

I'm just not sure what event to listen to, to do this. I need the map to only resize once the mouse is up, otherwise it lags out, and tries drawing itself a million times when you resize a window really slowly.

A: 

Building on bendewey's suggestion, here's a quick sample. It defines a simple Window with a TextBlock that shows the current size. This TextBlock is updated about one second after you "finish" resizing. It determines this by starting a DispatcherTimer that, every second, checks to see if the size is the same as the previous second. If the size is the same, you are assumed to be "finished" resizing (obviously you may want to put more robust checks here, assuming the user only temporarily has stopped resizing but is still holding down the Grip or Window edge).

Once you are finished, it removes the Timer and sets the TextBlock to display the new Size. You may be able to use something like this to resize your WindowsFormsHost section.

<Window x:Class="ResizeTestApp.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300"
        SizeChanged="Window_SizeChanged" 
        >
    <Grid>
        <TextBlock x:Name="txtResize" Text="" />
    </Grid>
</Window>

Code-behind:

using System;
using System.Windows;
using System.Windows.Threading;

namespace ResizeTestApp
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
    #region Members and Properties
    Size _oldSize;
    Size OldSize
    {
        get
        {
            if (_oldSize == Size.Empty)
                _oldSize = new Size(this.ActualWidth, this.ActualHeight);
            return _oldSize;
        }
        set
        {
            _oldSize = value;
        }
    }
    DispatcherTimer _timer;
    DispatcherTimer Timer
    {
        get
        {
            if (_timer == null)
            {
                _timer = new DispatcherTimer(
                    new TimeSpan(0, 0, 1),
                    DispatcherPriority.Background,
                    TimerTick,
                    this.Dispatcher); 
            }
            return _timer;
        }
        set
        {
            _timer = value;
        }
    }
    #endregion

    public Window1()
    {
        InitializeComponent();
    }

    private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        if (!this.IsLoaded)
        {
            OldSize = new Size(this.ActualHeight, this.ActualWidth);
            return;
        }
        if (Timer == null)
        {
            Timer.Tick += TimerTick;
        }
    }

    private void TimerTick(object sender, EventArgs e)
    {
        Size sz = new Size(this.ActualHeight, this.ActualWidth);
        if (sz == OldSize)
        {
            txtResize.Text = sz.ToString();
            Timer.Tick -= TimerTick;
            Timer = null;
        }
        else
        {
            OldSize = sz;
        }
    }
}
}
KP Adrian
Definitely not the right answer, overriding WndProc on the WindowsFormsHost will give you more options, as there are more messages sent to the window than .NET is exposing.
casperOne
I agree with casperOne - this is *not* the way to do this. casper's answer is much more deterministic as far as exactly when the sizing is occurring.
Erik Forbes
hmmm... the answer seemed like it would work fine for my problem - I'm just a beginner though, so can't form any opinions. I'm not too sure what the issue is with the code given?
Louis Sayers
@Louis Sayers: The problem is that it is guessing, imposing a rule that basically says "when the user doesn't reside the window for more than a second, then the size operation stops". The problem is, that rule is wrong, as the OS tells you when the operation stops, through the WM_SIZE windows msg.
casperOne
I would agree with CasperOne's solution. My solution was a throw-together since I have little experience with Windows messages.
KP Adrian
+2  A: 

Waiting on a timer is a very, very bad idea, quite simply, it's a heuristic and you are guessing when the resize operation is done.

A better idea would be to derive a class from WindowsFormsHost and override the WndProc method, handling the WM_SIZE message. This is the message that is sent to a window when the size operation is complete (as opposed to WM_SIZING, which is sent during the process).

You can also handle the WM_SIZING message, and not call the base implementation of WndProc when you get this message, to prevent the message from being processed and having the map redraw itself all those times.

The documentation for the WndProc method on the Control class shows how to override WndProc method:

http://msdn.microsoft.com/en-us/library/system.windows.forms.control.wndproc(VS.71).aspx

Even though it is for a different class, it's the same exact principal.

Additionally, you will need the value for WM_SIZING and WM_SIZE, which you can find here:

http://www.pinvoke.net/default.aspx/Enums/WindowsMessages.html

Note that you don't need everything from the link above, just the declarations for those two values:

/// <summary>
/// The WM_SIZING message is sent to a window that
/// the user is resizing.  By processing this message,
/// an application can monitor the size and position
/// of the drag rectangle and, if needed, change its
/// size or position. 
/// </summary>
const int WM_SIZING = 0x0214;

/// <summary>
/// The WM_SIZE message is sent to a window after its
/// size has changed.
/// </summary>
const int WM_SIZE = 0x0005;

And finally, of use will be the documentation for the WM_SIZE and the WM_SIZING window messages:

WM_SIZE - http://msdn.microsoft.com/en-us/library/ms632646(VS.85).aspx

WM_SIZING - http://msdn.microsoft.com/en-us/library/ms632647(VS.85).aspx

casperOne
I deleted my answer, I wasn't trying to assume that you should run the timer the whole time. either way, I'm interested in this method. Any links or samples?
bendewey
@bendewey: Edited with additional details.
casperOne
pinvoke.net is your friend... =)
Erik Forbes
A: 

Per the suggestion here:

http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/8453ab09-ce0e-4e14-b30b-3244b51c13c4

They suggest to use timer and everytime the SizeChanged event fires, you simply restart the timer. That way the timer isn't calling "Tick" all the time to check if the size has changed - the timer only goes off once per size change. Maybe less than ideal, but I don't have to deal with any low level windows stuff. This approach works for any wpf user control too, not just wpf windows.

public MyUserControl()
{
    _resizeTimer.Tick += _resizeTimer_Tick;
}

DispatcherTimer _resizeTimer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 0, 0, 1500), IsEnabled = false };

private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
    _resizeTimer.IsEnabled = true;
    _resizeTimer.Stop();
    _resizeTimer.Start();
}

int tickCount = 0;
void _resizeTimer_Tick(object sender, EventArgs e)
{
    _resizeTimer.IsEnabled = false;
    //you can get rid of this, it just helps you see that this event isn't getting fired all the time
    Console.WriteLine("TICK" + tickCount++);

    //Do important one-time resizing work here
    //...
}
viggity