Model-View-ViewModel xamDockManager

May 29, 2009 at 7:32 PM

I've been struggling with this for quite some time.  I'm trying to implement views as dockable windows using MVVM.  The idea is based on Josh Smith's excellent article in MSDN Magazine (http://msdn.microsoft.com/en-us/magazine/dd419663.aspx).

I'm able to add views but I can't figure out how to set the attached properties for the XamDockManagerSettings (e.g. SplitPaneName, SplitPaneProxyStyle, IsContentPaneInTabGroup), which are normally set within a UserControl.  However, since the DataTemplate is wrapped (I think) by another control, these attached properties are ignored.

Any ideas?

The same concept can be tested using a DockPanel in the shell versus a XamDockManager and using DataTemplates to set the attached Dock property within the DataTemplate.  I can't figure out how to do this either.

May 29, 2009 at 7:36 PM

http://social.msdn.microsoft.com/forums/en-US/wpf/thread/cc9ed724-600e-415a-b775-bae09eea66f8/ is close to answering my question, but it wouldn't suit an MVVM implementation.

"It's because for DataTemplate you will have a container generated for each item that will be between the StackPanel and the SlotItemPanel. The solution is to set ItemContainerStyle for the ItemsControl and on this style to set the attached property."

Anyone have any tricks up their sleeve for this problem?

Jun 3, 2009 at 9:57 AM
Edited Jun 3, 2009 at 10:05 AM

Have you tried the below, or are you trying to achieve something more complex?

<ncal:XamDockManagerSettings.ContentPaneProxyStyle>
<Style TargetType="{x:Type ncal:ContentPaneProxy}">
<Setter Property="Header" Value="{Binding Content.DataContext.Title, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
<Setter Property="Image" Value="{Binding Content.DataContext.ControlImage, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
</Style>
</ncal:XamDockManagerSettings.ContentPaneProxyStyle>

 

 

 

Jun 3, 2009 at 3:50 PM

Zack123:  Is the above code defined within a DataTemplate of a resource dictionary?  How would you set the XamDockManagerSettings.SplitPaneName, SplitPaneProxyStyle, and IsContentPaneTabGroup properties?

Jun 3, 2009 at 4:11 PM
Edited Jun 3, 2009 at 4:12 PM
Here's what I have that doesn't work... The view shows, but the XamDockManagerSettings are ignored - the dockable window is placed on the left and not in a tab group.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:MyCompany.Application.Modules"
                    xmlns:inf="clr-namespace:MyCompany.Application.Infrastructure;assembly=Infrastructure"
                    xmlns:ncal="http://infragistics.com/ncal"
>
    
    <!-- Presentation Model templates -->
    <DataTemplate DataType="{x:Type local:ClockViewModel}">
        <DataTemplate.Resources>
            
            <Style x:Key="xamDockStyle" TargetType="{x:Type ncal:ContentPaneProxy}" >
                <Setter Property="ncal:XamDockManagerSettings.SplitPaneName" Value="{x:Static inf:SplitPanes.RightSidePaneName}" />
                <Setter Property="ncal:XamDockManagerSettings.SplitPaneProxyStyle" Value="{Binding Source={x:Static inf:SplitPanes.Styles}, Path=RightSidePane}" />
                <Setter Property="ncal:XamDockManagerSettings.IsContentPaneInTabGroup" Value="True" />
            </Style>
        </DataTemplate.Resources>
        <Grid>
        </Grid>
    </DataTemplate>
</ResourceDictionary>
Jun 3, 2009 at 4:30 PM

AdaDoug,

The markup is in the UserControl that you are loading into a XamDockManagerRegion (I assume you are using Prism).  Your ViewModel should have properties that expose the values you want to bind too and be set to the DataContext of you UserControl (View).  So you can set the SplitPane, Style or any other properties in the same way.

Hope that helps.

Zack

 

Jun 3, 2009 at 4:39 PM

It is my understanding that you don't use UserControls with MVVM (per Josh Smith).  Instead, you use resource dictionaries with data templates whose TargetType properties are set to the ViewModel.  If you look at the way Josh applies a View to a ViewModel, you'll see that no UserControl is involved. This is what I'd like to do with views in an NCAL xamDockManager views.

Jun 3, 2009 at 6:12 PM

How are you adding your DataTemplate View to the Region?

Jun 3, 2009 at 6:44 PM
Edited Jun 3, 2009 at 6:45 PM

public class ClockModule : IModule, IModulePresentation
    {
        readonly IRegionManager _regionManager;
        private readonly IUnityContainer _container;

        public ClockModule(IUnityContainer container, IRegionManager regionManager)
        {
            _container = container;
            _regionManager = regionManager;
        }

        #region IModule Members

        public void Initialize()
        {
            RegisterResources();
            _container.RegisterInstance<IModulePresentation>("ClockModule", this);

            // region is a xamDockManagerRegion
            IRegion region = _regionManager.Regions[RegionNames.MainRegion];
            ClockViewModel clockViewModel = _container.Resolve<ClockViewModel>();
            region.Add(clockViewModel);

         }

        #endregion // IModule Members

        private static void RegisterResources()
        {
            ResourceDictionary dictionary = new ResourceDictionary();
            dictionary.Source = new Uri("pack://application:,,,/ClockModule;Component/ClockResourceDictionary.xaml");
            System.Windows.Application.Current.Resources.MergedDictionaries.Add(dictionary);
        }
}

Jun 3, 2009 at 6:49 PM

I should mention that since ClockViewModel has a DataTemplate, the WPF system knows that there is a visual representation (View) of the ViewModel.  If only I could set the properties on the xamDockManagerSettings from within the DataTemplate that one would normally set in a UserControl.

Jun 4, 2009 at 12:34 PM

Hi AdaDoug,

I could only get this working in code.  No luck on the binding.  If I were following this problem I would check out the base Region class and follow how it sets the regions content to the view.

My gut feel is that you are dealing with a scoping issue on your binding statements, not sure how the regions effect this.

            IRegion region = _regionManager.Regions[RegionNames.DockingAreaRegion];

            ClockViewModel view = _container.Resolve<ClockViewModel>();

 

            Style contentPaneProxyStyle = new Style(typeof(ContentPaneProxy));

            string header = "Clock Header";

            contentPaneProxyStyle.Setters.Add(new Setter(ContentPane.HeaderProperty, header));

            XamDockManagerSettings.SetContentPaneProxyStyle(view, contentPaneProxyStyle);

 

On MVVM - you most certainly should be using UserControls.  In the Josh Smith article you reference he is in fact using UserControls.

 

<DataTemplate DataType="{x:Type vm:AllCustomersViewModel}">
<vw:AllCustomersView />
</DataTemplate>

 

The element <vw:AllCustomersView /> is a UserControl.  What Josh is doing here, is wrapping the UserControl in a DataTemplate, this allows WPF to sort of take the place of the RegionManager.

In Prism you are explicit about which View goes into which Region with which ViewModel.  Josh is using the WPF engine to infer the view based on the ViewModel type, which is nice and dynamic.

 

I like using UserControls as views, I use DataTemplates to shape bound data that is displayed within the views. 

 

You might try moving your binding statements into a UserControl, then wrapping that in the DataTemplate, it should do the trick.

 

 

 

 

 

 

 

 

 

<!--EndFragment-->
Jun 4, 2009 at 8:14 PM
Edited Jun 4, 2009 at 8:16 PM

Aahhhh.  I see.

In PresentationModel, the View references the PresentationModel and the PresentationModel references the view.

     public interface IClockViewPresentationModel : INotifyPropertyChanged
    {
        IClockView View { get; }
        void Start();
        void Stop();
        bool SecondsAreEven { get; }
        string CurrentTime { get; }
    }

    public partial class ClockView : UserControl, IClockView
    {
        public ClockView()
        {
            InitializeComponent();

            this.Loaded += this.OnLoaded;
        }

        public IClockViewPresentationModel Model
        {
            get
            {
                return this.DataContext as IClockViewPresentationModel;
            }
            set
            {
                this.DataContext = value;
            }
        }

        #endregion IClockView Members

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            if (this.Model != null)
            {
                this.Model.Start();
            }
        }

    }
    public interface IClockView
    {
        IClockViewPresentationModel Model { get; set; }
    }
This causes a tighter coupling than MVVM because, in MVVM, the view does not know about the ViewModel... a slew of benefits over PresentationModel.  By setting the DataTemplate of the ViewModel to the View, the DataContext of the View becomes the ViewModel (very nice).
This is also nice because, if you think about it, only the view should "know" about the DockManager.  With me wanting to access attached properties from within the DataTemplate, I was causing tighter coupling than necessary.
So... thanks a ton Zack123.  You've enlightened.  I will use UserControls for the views.
Now onto the next problem: trying to handle the hidden UserControl code behind from complaining... when I change namespaces or apply an interface to the UserControl.  But that's another matter for another forum.
Thanks again Zack123!!!