Saturday, April 28, 2007

Resizer: a WPF Control

Recently I needed to allow users to resize a WPF Popup. After implementing a non-generic solution, I decided to attempt to turn the concept into a generic WPF control. The Resizer control is a specialised ContentControl that can be used as follows:

 

<kb:Resizer xmlns:kb="http://kent.boogaart/controls"> Content </kb:Resizer>

The Resizer control adds resizing behaviour to the content you place within it. The default template uses a ResizeGrip and allows the user to drag the grip in order to resize the content. For example, here is how you might add resizing behaviour to a TextBox (XML namespace mapping assumed from hereon in):

<kb:Resizer> <TextBox>My TextBox</TextBox> </kb:Resizer>

The result is this:

textbox1

And the user can drag the grip to resize the TextBox:

textbox2

And here is how you might implement a resizable Popup:

 

<Popup> <kb:Resizer Width="100"> <Border Background="Black"> <Image Source="/Tempany.jpg"/> </Border> </kb:Resizer> </Popup>

Here is the result:

tempany1

And after resizing:

tempany2

The Resizer control contains some other functionality worth mentioning:

IsGripEnabled : enables / disables the resize grip (defaults to true).
IsGripVisible : displays / hides the resize grip (defaults to true).
IsAutoSizeEnabled : determines whether the user can double-click the resize grip to return the Resizer's content to its natural size (defaults to true).
ResizeDirection : determines the direction the user needs to drag in order to increase the size of the Resizer's content (default to ResizeDirection.SouthEast).

You can download source for the Resizer control below. Included in the source is a sample application showing the various ways to use the control, including the use of a custom template to redefine the way the Resizer looks.

Enjoy!

55 comments:

Anonymous said...

IsXYZ? I would expect such a property to be read-only. Properties shouldn't be prefixed with Is. Visible and Enabled are the correct property names, a la MS guidelines.

Kent Boogaart said...

Then MS should follow their own guidelines. I refer you to any number of read/write IsXxx properties in WPF: IsEnabled, IsEditable, IsDropDownOpen etc etc. I don't have the guidelines handy (book is at work) but IIRC, "Is" is a *recommended* prefix for boolean properties. Will confirm tomorrow.

Neil said...

I think you'll find IsEnabled is correct.

Kent Boogaart said...

From section 3.6.2 of Framework Design Guidelines (sic):

DO name Boolean proprieties with an affirmative phrase (CanSeek instead of CantSeek). Optionally, you can also prefix Boolean properties with "Is," "Can," or "Has" but only where it adds value.

I guess the WPF team thought that "Is" adds value, and so do I.

Anonymous said...

nice

Igor said...

Hi Kent,
I'm quite new to WPF and I find your control really helpful to learn the fundamentals. Anyways I've tried to use the Resizer control within a ControlTemplate of another custom control and one problem appeared. It seems that the eventhandlers hooked up on ResizeGrip doesn't work when control is used in this faction. Therefore none of commands defined in your control are executed. Am I doing sth wrong? Do u have any ideas how to fix this behavior? thanks Igor

Anonymous said...

Hi Kent,
I'm quite new to WPF and I find your control really helpful to learn the fundamentals. Anyways I've tried to use the Resizer control within a ControlTemplate of another custom control and one problem appeared. It seems that the eventhandlers hooked up on ResizeGrip doesn't work when control is used in this faction. Therefore none of commands defined in your control are executed. Am I doing sth wrong? Do u have any ideas how to fix this behavior? thanks Igor

Anonymous said...

Hi Kent,
I'm quite new to WPF and I find your control really helpful to learn the fundamentals. Anyways I've tried to use the Resizer control within a ControlTemplate of another custom control and one problem appeared. It seems that the eventhandlers hooked up on ResizeGrip doesn't work when control is used in this faction. Therefore none of commands defined in your control are executed. Am I doing sth wrong? Do u have any ideas how to fix this behavior? thanks Igor

Anonymous said...

Hi Kent,
I'm quite new to WPF and I find your control really helpful to learn the fundamentals. Anyways I've tried to use the Resizer control within a ControlTemplate of another custom control and one problem appeared. It seems that the eventhandlers hooked up on ResizeGrip doesn't work when control is used in this faction. Therefore none of commands defined in your control are executed. Am I doing sth wrong? Do u have any ideas how to fix this behavior? thanks Igor

Anonymous said...

sorry for repeating comment...my IE seems to be a little buggy or what :)

Anonymous said...

Well I've found the problem. the Window in which I use the Resizer had the attribute ResizeMode="CanResizeWithGrip". In this case, Resizer doesn't work. Thanks anyways :)

Kent Boogaart said...

No problems Igor - glad you find this useful. And thanks for increasing my comments/post ratio! :)

NothingButNet said...

Kent,

Thanks so much for this example. I'm glad I Googled before adding a resizer to my custom Popup control. WPF is so awesome and I've learned a bunch, but I didn't know about FrameworkElementAutomationPeer. Your code is both totally functional and a great learning tool.

Many Thanks, Richie

Kent Boogaart said...

You're welcome Richie. Thanks for the kind words. Glad you find this useful.

NothingButNet said...

Hi Kent,

I'm seeing a problem with the Resizer TestHarness Popup examples built with VS2008 (Orcas) Beta 2. The content doesn't expand to fill a Popup resized to a larger size. As you know, the Popups work fine on VS2005. All of the other test cases seem to be working correctly on VS2008.

I tried building with both Net 3.0 and 3.5 on VS2008 Beta 2.

I haven't figured out what's causing the problem, but you may have quick insights if you have access to a VS2008 Beta 2 machine.

Best,
Rich

Kent Boogaart said...

Hey Rich,

Sorry but I don't have access to VS Orcas yet (but am planning to over the next week or two). Please let me know if you pin down the problem.

Kent

NothingButNet said...

Hi Kent,

The problem appears to be a difference in behavior between Popups built with VS2005 and VS2008 Beta 2.

I've submitted the following example to the VS2008 WPF forum on MSDN.

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1955983&SiteID=1

Anonymous said...

Hi Kent,
Thanks for the nice article and code to resize the WPF control. I am new to WPF and been researching on a sample or approach on how to resize a WPF window with all the controls at the same time. In your example, each control is individually resized. Is it possible that resizing a WPF window resizes all the child controls appropriately?

Thanks
Sivaram

Kent Boogaart said...

Hi Sivaram,

Thanks for the kind words. For an answer to your question, you'll want to look into WPF's layout system. Specifically, how panels work and can be used to achieve exactly what you describe.

Regards,
Kent

Edgar Frank said...

Hi Kent,

I came around your Resizer control by chance and find it very useful.
I could learn quite a few things about WPF from studying your code, as I'm quite new to WPF and especially control authoring in WPF (as opposed to using them).

I have a question about the custom control template and the way you're attaching the event handlers in generic.xaml.cs
Is that really the way to do it? I have seen that in your example custom template, the event handlers had to be wired up again (in generic.xaml.cs in more or less the same way).
I have seen other examples where the events were attached in the override of OnApplyTemplate in the custom control class.

Thanks in advance!

Anonymous said...

Hi Kent,

Thank you for your excellent code and for sharing it with us all. To this purpose I'd like to ask what kind of licencing applies to your code. Can I reduce, reuse, and recycle it to fit my needs?

I will make sure you will be ranking hing in the credits of my app.

Kent Boogaart said...

Hi Anon,

Thanks for the praise. Yes, you can do whatever you like with the source - learn, use as is, use modified, whatever you like. All I ask is that people don't claim that they authored it, and optionally give credit, even if it's just a comment in the source code :)

David Bernad said...

Hello Kent.
Thanks for your control, is very useful.

One question, how can I make the resizer move only diagonally?

Thanks once more.

Shrenik said...

Hi Kent, This is excellent example... but i want to use size grip to move control... is it possible!! if possible can you tell me how??? i am having very basic knowledge of WPF!!! Thanks in advance...

receptor said...

hi,

i use your Resizer in my project. I need the CanResizeWithGrip property for my main window. if i enable it, dragging your resizer grip resizes my main window. is there a way to fix it?

<>< Victor ><> said...

Hey Kent,

Your control looks very interesting and I'm considering using it to replace the custom Adorner I've created for my app. However, one feature that would be neat to have is the ability to preserve the aspect ratio of whatever element in you're resizing (perhaps enabled via a flag such as "IsPreserveAspectRationEnabled" or something like that). Any chance you will be adding this anytime soon?

Thanks for sharing this useful control with us!

Victor

Anonymous said...

hi
does ur resizer component work for a canvas also ...
i am unable to do it to a canvas
so help me out in it..

drew noakes said...

This looks like exactly what I need, only unfortunately the project files in the zip file don't seem to work in VS2005 or VS2008. Am I missing something?

Kent Boogaart said...

@drew: 2008 does a conversion on the solution for me without error and everything works fine. I don't have 2005 installed to test.

Harley Pebley said...

This was just what I was looking for, but I found a bug in it. There appeared to be an off-by-one problem: the cursor would progressively get further and further away from the sizing control as the size grew.

I found it to be because the control's size was being changed relative to itself, which was itself changing, if that makes any sense. To fix the problem, I changed the two occurrences of "resizer._frameworkElement.PointToScreen(Mouse.GetPosition(resizer._frameworkElement))" to "Mouse.GetPosition(Application.Current.MainWindow)" so things are relative to a fixed coordinate system.

Cheers,
Harley Pebley
http://skylark-software.com

Chris said...

Hi Kent,

thanks for this great article, this is a really nice solution.
I'm currently playing with your control and it works great, but there is one problem:

I'm trying to resize a popup, but when hitting the screen border, it's enlarging in the opposite direction. This is even worse on multi monitor systems, as the WPF popup apparently is not able to cross screen boundaries, and it's only visible on one screen although it should overlap onto the second screen.

You can easily reproduce this with the resizable ComboBox DropDown in your demo app.
You don't happen to know a quick workaround for this?

Regards,
Chris

Kent Boogaart said...

Hi Chris,

Thanks for that. I haven't come up against this scenario, so I'm afraid I don't have a quick fix for you. I don't have a multi-screen setup to test on, but you may be able to play around with Popup.Placement to get this working.

HTH,
Kent

Roni Peterson said...

Thanks for share.

C-Dan said...

Hi Kent, Thanks a ton for the control...
But I'm facing a problem(Your control works fine..)
I have custom buttons for my window for close, maximize & minimize. Before applying the Resizer control, my maximize worked fine, but after applying the control,
maximize doesn't change the state of the window (same windows size befor click)..
my code
-
private void max_Click(System.Object sender, System.Windows.RoutedEventArgs e)
{
if (this.WindowState == WindowState.Normal)
{
this.WindowState = WindowState.Maximized;
}
else if (this.WindowState == WindowState.Maximized)
{
this.WindowState = WindowState.Normal;
}
}

I'm a designer, not much of a coder. Any help would be apprecited... Thanks

C-Dan said...

Revision- Maximize works as long as I don't use the Resizer
(runtime)

C-Dan said...

Update: now i know what's happening.. The size of my container is gettin altered at runtime, when I use the resizer, which is why the maximize is no longer applicable to the original size...
& the rezire also scales my contents like a viewbox does...
Can the resizer be used only to resize the outermostgrid such that the contents retain their original size with scrolling auto...

Thanks

Kent Boogaart said...

Hi C-Dan. I'm not sure I follow exactly. I suspect you may have the resizer at the wrong level in your visual tree. If you post your XAML I might be able to point you in the right direction.

C-Dan said...

Thanks... That visual-tree tip helped...works fine now...

midnight coder said...

Hi Kent.
Thank you for sharing your control. It was very useful.
I'd like to share a little extension which enables ratio preservation during resizing.

I've added a couple of private fields for keeping initial width and height

private double? _initialWidth = null;
private double? _initialHeight = null;

I initialize them in
OnStartResizeCommand handler
if (resizer._initialHeight == null)
{
resizer._initialHeight = resizer.ActualHeight;
resizer._initialWidth = resizer.ActualWidth;
}

I've also added a couple of dependency properties
KeepInitialRatioProperty
KeepRatioProperty

and in OnUpdateSizeCommand:
after the resizer initializing

bool keepRatio = Keyboard.IsKeyDown(Key.LeftShift) || resizer.KeepRatio;
bool keepInitialRatio = (Keyboard.IsKeyDown(Key.LeftShift) && Keyboard.IsKeyDown(Key.LeftCtrl)) || resizer.KeepInitialRatio;
double ratio = 0;
if (keepInitialRatio)
{
ratio = resizer._initialWidth != null && resizer._initialHeight != null
? resizer._initialWidth.Value / resizer._initialHeight.Value
: resizer._originalWidth / resizer._originalHeight;
}
else if (keepRatio)
{
ratio = resizer._originalWidth / resizer._originalHeight;
}

and after deltas calculation:
//update the width and height, making sure we don't set to below zero
if (keepRatio || keepInitialRatio)
{
resizer.Height = Math.Max(0, resizer._originalHeight + heightDelta);
resizer.Width = ratio * resizer.Height;
}
else
{
resizer.Height = Math.Max(0, resizer._originalHeight + heightDelta);
resizer.Width = Math.Max(0, resizer._originalWidth + widthDelta);
}


That's all. Now you can preserve initial and original ratio while holding shift and ctrl+shift buttons.

Kent Boogaart said...

@midnight coder: nice mod - thanks!

Lisa said...

Hello Kent,

I like your control :)

Then I put your resizer around a DataGridTextColumn and wpf said its not possible to wrap a Collection...

Is there a workaround for that situation? I would like to offer my user the chance to resize the cell if they need more space for writing data...

Would be pleased to hear from you :)

cheers,

Lisa

Kent Boogaart said...

Hi Lisa,

To be honest, I'm not sure that scenario makes sense. Given that your content is in a grid, wouldn't you be best off allowing the users to resize the columns and rows in the grid? Resizing an individual cell isn't really possible without affecting other cells in its column and row.

Best,
Kent

gabore said...

Hi Kent
How can I add a grid inside the Resizer dynamically? I tried setting the Content but nothing is shown like that.

Kent Boogaart said...

@gabore; the same way you would with any other WPF control. If you're only adding a Grid, you won't see anything since a Grid is just a Panel. Try adding something like a Label or TextBox instead.

gabore said...

@Kent, I forgot to add your dll as a reference... Sorry for that ;)
I add a grid with a textblock inside the resizer and it works nice. You have some great work done.

Thomas Matelich said...

Not sure if the backlink will get added automatically, I've made some mods to this control and documented them on my blog

(Sorry for the typo on your name, I fixed it in the text but Blogger didn't change the url)

Anonymous said...

Any idea if it should work under a userContorl? Cool app!

Kent Boogaart said...

@anon: You mean can you use it in a user control? Sure. you mean can you use it to resize a user control? Sure, but it assumes the user control has been built with adaptive layout in mind.

PaulNielsenSQL said...

Hi - great control. a quick question - have you gotten it to work with an Expander control?

Ivan said...

Hi Kent

Fantastic control - thank you very much this has saved me a lot of time.

I just have one comment/suggestion/question: WPF elements are not necessarily axis-aligned, (particularly not in my case - I am building a multi-touch application), but your resize control always behaves as if they are - dragging down and to the right always increases size, up and to the left always decreases size. For example, when your control is used upside-down (after a RenderTransform is applied to it), it behaves in the exact opposite way that it should. Do you have any ideas for how to make it behave correctly, or intention for fixing this?

Best Regards
Ivan

Kent Boogaart said...

@Ivan: Thanks for the kind words. To be honest, I have no intention to enhance this. The control is not something I actively support, but you can certainly modify the code to suit your needs.

Anonymous said...

Just thought I'd mention that this is an awesome little control! Thanks for the great job.

menkaur said...

Wow! I'm surprised that Mircosoft hadn't implemented this control yet. It has been 5 years already.

Anyway, thanks a lot! This is really useful!

Unknown said...

Hi Kent, is there any way to limit resizing an image past the main app window frame? When I double click on the resizer the image blows up and the resizer is pushed down past where I can get to it anymore. Or how can I get the main window frame to keep the image i'm trying to display in sync with the frame so it grows and shrinks as the main app frame gets resized.. I need it to be aware of the limits of the app's main window.

thanks,
Jim

Kent Boogaart said...

Hi Jim,

In that instance, I would just put the Image inside the Window itself. If you have more than just the image in the window, use the appropriate panels (such as Grid or DockPanel) to assign whatever proportion of the window you want to the image. As you resize the window, WPF's layout system will take care of giving the image whatever space it is due. From there, make sure the image's Stretch property is set according to how you want your image to stretch, and you should be good.

Kent