Can RhinoCommon be used with WPF

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

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.

1 Like

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.

1 Like

@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

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:

1 Like

@jstevenson 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:

@jstevenson 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;
    }
}
1 Like

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

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?

1 Like

Oh, That is great ā€¦ Thanks a lotā€¦ they are very helpful ā€¦

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

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

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

2 Likes

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.

1 Like

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?

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

Sry if Iā€™m opening this old topic again:
How can i bind data and variables between view and viewmodel?
I have a wpf plugin which has many views and only 1 viewmodel. the view model contains all logic and variables and commands. Each time when Iā€™m opening a view from mainview, DataContext is opening a new instance of viewmodel. Iā€™ve tried to bind viewmodel in App.xaml in order to be accessible for all views, but i get ā€œdoesnā€™t existā€ error:

<Application.Resources>
        <local:MainViewModel x:Key="SharedViewModel"/>
    </Application.Resources>

but when I do same with a plain wpf project, its okey.

Iā€™m trying to save ā€œuser inputsā€ of textbox and rhino objects into viewmodel in order to use them later in other views.

How do you open your views? What I typically do is the following, so you have control over which object is used as view model:

//file: MyView.xaml.cs
public class MyView : Window
{
  public MyView(MyViewModel vm)
  {
    DataContext = vm
    InitializeComponent();
  }
}
1 Like

Thanks for the reply, my views are usercontrol not a window (I get a blank window if i change usercontrol to window)

    public partial class Contour : UserControl
    {
        public MainViewModel ViewModel => DataContext as MainViewModel;
        public Contour()
        {
            DataContext = new MainViewModel();
            InitializeComponent();
        }
}

I was using this and I know its creating a new instance each time. When I use your code, returns me a blank window.

The reason you get new instances is that you do DataContext = new MainViewModel()
Iā€™m not sure how to do this correctly, other than with the example I gave earlier, sorry.