Can RhinoCommon be used with WPF


(Brian Gillespie) #1

Is it possible to use WPF when creating a plug-in with RhinoCommon?


WPF GUI Example
(Steve Baer) #2

Sure! Not only is it possible, I would recommend it when developing on Windows over other WinForms.

One suggestion I would make is to learn and use the MVVM pattern for your plug-in. This will separate the UI from the logic to make it easier to maintain your code and keep a separation in the case that you may want to write a plug-in for Mac in the future.
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx

This is what we are using internally to create core plug-ins for Rhino in future projects.


(Menno Deij - van Rijswijk) #3

Using WPF on its own within Rhino is great, but my experience is that when you use a WPF UserControl in a WpfElementHost in a Winforms UserControl as a RhinoPanel that it does not work out of the box.

To clarify the situation:

I have a System.Windows.Forms.UserControl with a Guid that I register when loading my plug-in. This usercontrol contains a WpfElementHost in which a WPF UserControl is placed. The WPF UserControl’s DataContext is set to the ViewModel, according to MVVM best practice.

Problem 1:

I found the problem (and solution) described here http://stackoverflow.com/questions/835878/wpf-textbox-not-accepting-input-when-in-elementhost-in-window-forms that the keyboard input was not propagated to the WPF UserControl, so I had to forward this manually.

Problem 2:

Changes in WPF TextBoxes were not propagated to the ViewModel when the WpfElementHost loses focus (this may be related to the previous problem, I’m not sure). Anyway, I had to manually update the source of all bindings to a TextBox to update the ViewModel upon loosing focus.

This is my recent experience trying to interoperate Rhino Panels with WPF using a WinForms WpfElementHost.


#4

@menno We are using WPF extensively using the WPFElementHost class to host our WPF View Controls within Rhino DockPanels with great success.

If you are still having troubles fee free to email me. jstevenson72@gmail.com


(Steve Baer) #5

We are definitely going to be using a lot more WPF in core V6 thanks in no small part to your last visit to our office :smile:


(Menno Deij - van Rijswijk) #6

@jstevenson72 Hi Jason, thank you for your offer. We’ve got it more or less under control, now but I’ll post our solution on Monday. I’m curious to see if you had to make similar adjustments to get it to work. But first, the weekend :wink:


(Menno Deij - van Rijswijk) #7

@jstevenson72 This is the WinForm code that we use to place a WPF UserControl in a WpfElementHost. The control is basically one big WpfElementHost with variable name ‘wpfElementHost’. Like I said last week, I’m curious to see if you have a better solution, esp. for the LostFocus problem addressed in the first part of the code.

public abstract partial class WPFInteropPanelBase : System.Windows.Forms.UserControl
{
    protected WPFInteropPanelBase()
    {
        InitializeComponent();
        // There is a problem with this control in that once the WPF Element Host loses focus
        // the content does not loose focus. This means that any controls with changes 
        // will not propagate these changes to their DataContext (i.e. ViewModel). 
        // Therefore, this needs to be enforced in code:
        // Step 1: subscribe to the host's LostFocus event
        wpfElementHost.LostFocus += WPFElementHostOnLostFocus;
    }
        
    private void WPFElementHostOnLostFocus(object sender, EventArgs eventArgs)
    {
        if (null != wpfElementHost.Child)
        {
            // Step 2: if there is content, update the textbox bindings
            UpdateTextboxBindings(wpfElementHost.Child);
        }
    }

    private void UpdateTextboxBindings(DependencyObject o)
    {
        // Step 3: get the binding for TextBox.Text and update it if found
        var be = BindingOperations.GetBindingExpression(o, System.Windows.Controls.TextBox.TextProperty);
        if (null != be)
            be.UpdateSource();
            
        // Step 4: continue recursively down the logical tree
        var children = LogicalTreeHelper.GetChildren(o);

        foreach(var child in children)
        {
            DependencyObject depChild = child as DependencyObject;
            if (null != depChild)
                UpdateTextboxBindings(depChild);
        }
    }

    /// <summary>
    /// This exposes the WPF UserControl's DataContext for applying a ViewModel. 
    /// Make sure to set the <see cref="WPFControl"/> property before setting the DataContext.
    /// </summary>
    public object DataContext
    {
        get
        {
            return WPFControl == null ? null : WPFControl.DataContext;
        }
        set
        {
            if (null != WPFControl)
            {
                WPFControl.DataContext = value;
            }
        }
    }

    private System.Windows.Controls.UserControl _wpfControl;

    /// <summary>
    /// 
    /// </summary>
    public System.Windows.Controls.UserControl WPFControl
    {
        get { return _wpfControl; }
        set
        {
            if (null != value)
            {
                value.Loaded -= OnControlLoaded;
            }
            _wpfControl = value;
            wpfElementHost.Child = _wpfControl;
                
            if (null != _wpfControl)
            {
                _wpfControl.Loaded += OnControlLoaded;
            }
        }
    }


    // the following voodoo code is added because of the problem detailed in 
    //
    // http://stackoverflow.com/questions/835878/wpf-textbox-not-accepting-input-when-in-elementhost-in-window-forms
    //
    // basically the MFC user control blocks keyboard input to the WPF control.
    // and the solution in the question works like a charm fortunately.
    private void OnControlLoaded(object sender, RoutedEventArgs e)
    {
        HwndSource s = HwndSource.FromVisual(_wpfControl) as HwndSource;
        if (null != s)
            s.AddHook(ChildHwndSourceHook);
    }
    // ReSharper disable InconsistentNaming
    private const UInt32 DLGC_WANTARROWS = 0x0001;
    private const UInt32 DLGC_WANTTAB = 0x0002;
    private const UInt32 DLGC_WANTALLKEYS = 0x0004;
    private const UInt32 DLGC_HASSETSEL = 0x0008;
    private const UInt32 DLGC_WANTCHARS = 0x0080;
    private const UInt32 WM_GETDLGCODE = 0x0087;
    private const UInt32 WM_KILLFOCUS = 0x0008;
    // ReSharper restore InconsistentNaming

    IntPtr ChildHwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (msg == WM_GETDLGCODE)
        {
            handled = true;
            return new IntPtr(DLGC_WANTCHARS | DLGC_WANTARROWS | DLGC_HASSETSEL | DLGC_WANTTAB);
        }
        return IntPtr.Zero;
    }
}

#8

would you please provide us with resources to learn this method ?


(Dale Fugier) #9

If you are looking for learning resources on WPF (e.g. Windows Presentation Foundation), there are plenty on the Internet, as well as lots of books you can purchase.

For example:

From Microsoft…

From Amazon…

Here is a Sample C# RhinoCommon plug-in that demonstrates how to create a tabbed, docking panel that uses WPF.

Does this help?


#11

Oh, That is great … Thanks a lot… they are very helpful …


#13

Hi Dale,
I have had some success using your sample docking panel with wpf. I am having trouble with databinding though. I was able to properly bind a control on a child of the main UserControl object, but not one on the actual UserControl. (SampleCsWpfPanelUserControl in your example). My confusion is how to set the DataContext on this object.

Currently I am creating a ViewModel (at least thats what I think it is) inside RunCommand. I see that the base ctor for SampleCsWpfPanelHost takes a ViewModel as input, but mine isn’t created yet at this point. I tried to set it later but didn’t have much success. In the case where I had success it was because I was able to create a viewmodel in the ctor of the child control. Possibly this tells me I should have one more layer in my UserControls, but tbh I am very new to wpf and the MVVM model is really confusing me. Any tips for using databinding with a panel based on your example?

Thanks


#14

wes,

Chances are your binding isn’t refreshing even though the data is changing in your viewmodel because the property you are binding to is not a Dependency Property, or doesn’t implement INotifyPropertyChanged interface.

A typical property that is properly raising notification will look something like this:

private bool _isRhinoAwesome;
public bool IsRhinoAwesome {
get {
return _isRhinoAwesome;
}
set {
_isRhinoAwesome = value;
RaisePropertyChanged();
}
}

If you are using a framework like MVVM Light the RaisePropertyChanged() should already be available to you, if not then you see here how to raise notification in a class: http://stackoverflow.com/questions/1315621/implementing-inotifypropertychanged-does-a-better-way-exist


(Steve Baer) #15

Man, I sure hope that property always returns true :slight_smile:


#16

Thanks,
Yeah, I had a lot of learning to do about binding. In the end I think it was worth it, I do like wpf now. I hardly use any delegates as most of my data fits well into the pattern and there are times when binding really makes it simple.


#17

Hello, I am looking for the example you gave.
I can’t derive a class from RhinoWindows.Controls.WpfElementHost like you .
That probably come from the fact I don’t have the RhinoWindow reference. Where is is possible to find it?


(Menno Deij - van Rijswijk) #18

You should reference the RhinoWindows.dll from the System directory.