Google Search

Showing posts with label Tutorial. Show all posts
Showing posts with label Tutorial. Show all posts

Friday, May 18, 2012

Custom Resizable Windows in WPF and Winforms

The following post is relevant for both WPF and Winforms projects, wishing to create custom looking windows that can be dragged and resized from all directions (or specific ones).
We will need to use some native Win32 APIs. So first we need to declare the SendMessage function:
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, Int32 wParam, Int32 lParam);
We will use that function with the window handle, passing WM_NCLBUTTONDOWN (0x00A1) as message. The trick is to pass the correct wParam and lParams so Windows will think we're dragging the window or resizing it through any of it's resizing handles:
enum WindowHandles
{
    HTCAPTION = 2,
    HTLEFT = 10,
    HTRIGHT = 11,
    HTTOP = 12,
    HTTOPLEFT = 13,
    HTTOPRIGHT = 14,
    HTBOTTOM = 15,
    HTBOTTOMLEFT = 16,
    HTBOTTOMRIGHT = 17
}
There are actually more handles, for the client area, close button, minimize and maximize buttons, etc. They are all listed in the WM_NCHITTEST message page on MSDN. I only listed the handles relevant to our case.
We will be tricking Windows by simulating a mouse down event on a specific drag-handle. But since we know that the drag starts by listening to mouse-down even, we first need to tell Windows that the mouse button is not down anymore. To do this we will also need the ReleaseCapture function:
[DllImport("User32.dll")]
public static extern bool ReleaseCapture();
Now all there's left to do is to call the SendMessage the mouse button is down for each of our custom drag-handles with the native window handle (HWnd):
const int WM_NCLBUTTONDOWN = 0x00A1;

void RegisterCustomBorderEvents()
{
    //...
    // do similar things for all borders and for the caption.
    LeftBorder.MouseDown += OnLeftBorderMouseDown;
    //...
}

void OnLeftBorderMouseDown (object sender, EventArgs e)
{
    ReleaseCapture();
    SendMessage(windowHandle, WM_NCLBUTTONDOWN, (uint)WindowHandles.HTLeft, 0);
}

WPF Notes

In WPF you will need to use the interop API in order to get the window handle:

IntPtr windowHandle = new WindowInteropHelper(myWindow).Handle;
Where myWindow is the window you want to drag. If you implement this in the window code behind, just pass this.

Also in WPF there are built in methods in the Window level for dragging the window (DragMove) and in the UIElement level for releasing the mouse capture (ReleaseMouseCapture). You may use them instead of the PInvoke versions where possible.

Thursday, April 9, 2009

Creating customizable WPF drop-down menus (7)


Step 7: Using the new binding correctly


Remember that we overridden OnDragEnter so the menu will open upon hovering? Well, we will have to make a slight change to that code, to (also) use our new property:

protected override void OnDragEnter(DragEventArgs e)
{
    IsSubmenuOpen = true;
    IsSubmenuOpenInternal = true;
    e.Handled = true;
}

Great! Except now the menu never closes. Well, the first occasion we want it to close is when we're done dropping. So we will add the following snippet to our OnDrop code, just before the e.Handle = true code:
if (parentMenuItem != null)
    parentMenuItem.IsSubmenuOpenInternal = false;

Another case when we want it to close is whenever a neighbor menu opens, or in case we stopped using the menu at all (like, clicking any other place). But the thing is, we really don't want to write such code, as it is already written. We need a way to find when the original property would change to false and do the same.
Luckily, there is a routed event just for that: SubmenuClosed.
We will register to that event, and change our new property to false likewise. Except, we do it with a minor change: we need to make sure we are not in the middle of a drag operation, or right in the drop handling code. We will register to that event in the constructor, and implement it:
public DraggableMenuItem()
{
    AllowDrop = true;
    SubmenuClosed += new RoutedEventHandler(DraggableMenuItem_SubmenuClosed);
}

void DraggableMenuItem_SubmenuClosed(object sender, RoutedEventArgs e)
{
    if (Mouse.LeftButton == MouseButtonState.Pressed || !IsDragging)
        IsSubmenuOpenInternal = false;
}

But now there is another problem - the menu doesn't open well, since we replaced the IsSubmenuOpen with IsSubmenuOpenInternal. So the same way, we need to handle the open event - SubmenuOpened:
public DraggableMenuItem()
{
    AllowDrop = true;
    SubmenuClosed += new RoutedEventHandler(DraggableMenuItem_SubmenuClosed);
    SubmenuOpened += new RoutedEventHandler(DraggableMenuItem_SubmenuOpened);
}

void DraggableMenuItem_SubmenuOpened(object sender, RoutedEventArgs e)
{
    IsSubmenuOpenInternal = true;
}

And that's it! The menu is now Drag-and-Drop customizable! Note that the code brought here in this article is for explanatory purposes only, and is far from perfect. Its goal is to explain the main issues and problems encountered while developing such a control, so use it as a reference only.

Hope you have found it useful, and please let me know if you did, and I'll be glad to hear about successful (preferably complete) implementations.

Creating customizable WPF drop-down menus (6)


Why dropping doesn't work on sub-menus?


This question is actually the heart of the entire process. All we did until now was quite straight-forward.
So, why doesn't it work? To solve that I tried several debugging methods, but the one that really got me somewhere was to enable Reference Code Debugging.
So I found myself debugging deep into the WPF code, finding that during the drop operation, the result of the hit-test being performed is actually 'null'(!). More than that, it seemed like the native window holding the sub-menu wasn't even there (!!). How can that be?
Well, I couldn't debug much deeper than that, but thinking about how drop-down menus work, and how drag-and-drop mechanisms work, I figured that the problem must be that the sub-menu popup was closing before the drop operation hit test takes place.

This means that if I want to be able to drop things on a sub-menu, I need to build a new mechanism that will keep it open until AFTER the drop.

Step 6: Keeping the sub-menu open


Now it is going to get clear why I insisted on inheriting MenuItem (and be sure it will get worse, as we will need to change the default control template).

We will need to introduce a new Dependency Property that will enable us to better control whenever the sub-menu is open. Let's call this property IsSubmenuOpenInternal:
public static readonly DependencyProperty IsSubmenuOpenInternalProperty =
   DependencyProperty.Register("IsSubmenuOpenInternal",
                               typeof(bool), typeof(DraggableMenuItem),
                               new FrameworkPropertyMetadata(false));

public bool IsSubmenuOpenInternal
{
   get { return(bool)GetValue(IsSubmenuOpenInternalProperty); }
   set { SetValue(IsSubmenuOpenInternalProperty, value); }
}

In the XAML code, we will change the default style (this can be easily done using "Expression Blend") so the sub-menu opening state will now bind to our new property, rather than the default one.

I will not place here the entire XAML for the new menu item class, since it is just too long.
What you will need to do is to use Expression Blend to copy the default MenuItem style, then make the following changes:
  • Change the TargetType attribute to our new class type. Remember to add the relevant namespace declaration in the XAML declaration.
  • Locate the Popup element block, and change its IsOpen binding from IsSubmenuOpen to our new IsSubmenuOpenInternal dependency property.



Important Note:
If I could, I would of course rather override the default IsSubmenuOpen property, leaving the XAML as it is. However, this is impossible (at least in accepted methods, without using reflection), so adding this property really seems like the only way to go.


Now that the new bindings are in place, we can move on to the next post :-)

Monday, April 6, 2009

Creating customizable WPF drop-down menus (4,5)


Step 4: Responding to drag hovering


Important Note:
In this part of the tutorial, we implement the draggable menu items that are static, and still in the menu, rather then the one being dragged. Keep that in mind, this is important.

We want our menus open whenever an item is dragged over them. The simplest solution would be to override the OnDragEnter method, and set the IsSubmenuOpen property to true.
This will work for now, but we will have to change that later, when we get to the drop-handling part.
Anyway, for now:
protected override void OnDragEnter(DragEventArgs e)
{
    IsSubmenuOpen = true;
    e.Handled = true;
}

Step 5: Handling drops


First we must accept drops. Lets implement the constructor:
public DraggableMenuItem()
{
    AllowDrop = true;
}

This code snippet set our draggable menu item to accept drops. Without this, it wouldn't be possible to drop anything on it, so we wouldn't be able to customize our menu.
Next we need to catch the drop operation, and perform the item movement logic. Of course we shall override and implement OnDrop. Note that we will need to extract the item being dragged from the DragEventArgs, and if it is a DraggableMenuItem, remove it from its current position, and place it just before the drop target:
protected override void OnDrop(DragEventArgs e)
{
    DraggableMenuItem draggedItem = (DraggableMenuItem)e.Data.GetData(typeof(DraggableMenuItem));
    DraggableMenuItem parentMenuItem = Parent as DraggableMenuItem;
    Menu parentMenu = Parent as Menu;
    ItemsControl itemsControl = Parent as ItemsControl;
    if (draggedItem != null && (parentMenuItem != null || parentMenu != null)) 
    {
        int i = itemsControl.Items.IndexOf(this);
        ItemsControl draggedItemParent = draggedItem.Parent as ItemsControl;
        if (draggedItemParent != null) 
        {
            draggedItemParent.Items.Remove(draggedItem);
        }
        itemsControl.Items.Insert(i, draggedItem);
        e.Handled = true;
    }
    base.OnDrop(e);
}

First attempt to test it all


Generally, while things don't look as nice as they can be, and might still be somewhat buggy, it looks like things should work.
Testing it reveals that dropping items on the menu bar level does work. However, dropping things on open sub-menus doesn't!
I will review the reason for that on the next post.

Creating customizable WPF drop-down menus (3)


Step 3: Dragging items around


Once we have figured out how to detect the drag, we need to perform the actual dragging. Moreover, we want the menu to respond while we move around - hovering over a menu should cause it sub menu (if any) to open.

We will use the System.Windows.DragDrop helper class to perform the actual dragging.
The main method in the DragDrop class is DoDragDrop. This method is used to initialize a drag-and-drop session, perform the actual drag-and-drop operations, and returns the effect.

Initializing a drag-and-drop session


The DoDragDrop method takes 3 parameters:
  • A drag source (which will be our draggable menu item),
  • A data object, and
  • The allowed effects.

So as I mentioned, the drag source will be our draggable menu item. Since the code will be written as part of that menu item, we will just place there 'this'.
Our data source, while can be anything at all (as it is of type object), should be of type System.Windows.IDataObject, since otherwise it will be wrapped with a DataObject.
The best thing to do in this case would be to create a DataObject, and give it a unique name (or type), and the data itself. In our case, the data is the draggable menu item we'll be dragging, so the best unique id would be its type.
The only allowed effect, in our case, is the move effect, as this is what we want to do (at least for now) - move menu items from one place to another.

So if we implement the DoDragging method we left empty before:
private void DoDragging()
{
    IsDragging = true;
    DataObject dataObj = new DataObject(typeof(DraggableMenuItem), this);
    DragDrop.DoDragDrop(this, dataObj, DragDropEffects.Move);
    IsDragging = false;
}

Note that I raise the IsDragging flag before the dragging process begins, and lower it immediately afterwards. One reason for that is to prevent dragging to begin on other draggable menu items. Other reasons will become clear in the following posts.

Creating customizable WPF drop-down menus (2)


Step 2: Detecting drag


Drag will start when the user had pressed the mouse button on an item and moved it enough distance in pixels while being pressed.
So, we need to know where the mouse was pressed, then track it all time it is pressed until it moves far enough:
public class DraggableMenuItem : MenuItem
{
    private Point m_dragStartPoint;

    public static  bool IsDragging { get; set; }

    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
    {
        m_dragStartPoint = e.GetPosition(this);
        base.OnMouseLeftButtonDown(e);
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        if (e.LeftButton != MouseButtonState.Pressed || IsDragging)
            return;

        Point p = e.GetPosition(this);
        if (Point.Subtract(m_dragStartPoint, p).LengthSquared < 16)
        {
            e.Handled = true;
            return;
        }

        DoDragging();
    }

    protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
    {
        IsDragging = false;
        base.OnMouseLeftButtonUp(e);
    }

    private void DoDragging()
    {
    }
}
Note that the IsDragging flag is a static flag, thus shared among all instances of the DraggableMenuItem. In the code sample above we do not yet assign it. This will be done in the next steps, in addition to some other places we will need to use its value to achieve the correct behavior.

Sunday, March 29, 2009

Creating customizable WPF drop-down menus (1)

In this series of articles I'll explain the process of creating a drop-down menu that can be customized using drag-and-drop.



Enough preface, now on to the job!


Step 1: A simple drop-down menu

A simple drop down menu can be built in WPF using the Menu and MenuItem classes. This can be done is XAML of course, and would look like this:
<Menu>
    <MenuItem Header = "MenuItem1">
        <MenuItem Header = "MenuItem1.1"/>
        <MenuItem Header = "MenuItem1.2"/>
        <Separator Margin = "0,0,0,0" Height = "Auto"/>
        <MenuItem Header = "MenuItem1.3">
            <MenuItem Header = "MenuItem1.3.1"/>
        </MenuItem>
        <MenuItem Header = "MenuItem1.4">
            <MenuItem Header = "MenuItem1.4.1"/>
        </MenuItem>
    </MenuItem>
    <MenuItem Header = "MenuItem2"/>
    <MenuItem Header = "MenuItem3" HorizontalAlignment = "Right">
        <MenuItem Header = "MenuItem3.1"/>
        <MenuItem Header = "MenuItem3.2"/>
        <Separator Margin = "0,0,0,0"/>
        <MenuItem Header = "MenuItem3.3">
            <MenuItem Header = "MenuItem3.3.1"/>
        </MenuItem>
    </MenuItem>
</Menu>
This menu, however, cannot be customized using Drag-and-Drop by the end-user at runtime. In order to do that, we need to implement a dragging mechanism.

Fortunately, WPF has a built-in Drag-and-Drop assistance class, that does the hard parts for us. All we need to do is to detect when we want to begin dragging, then pass it on to the Drag-and-Drop helper. Oh, and we also need to handle the drop :-)

Important Note:
The MenuItem class is limited in its behavior. In order to prepare to some of the more advanced steps, I'll need to inherit MenuItem to my own class, that will allow me to do the needed operations.
I'll call this class DraggableMenuItem, and let it implement internally everything the draggable menu items does.