Binding ViewModels to Views in a sub controls(and refreshing them)?

May 16, 2011 at 6:46 AM
Edited May 16, 2011 at 6:49 AM
Hello Mr Horrik! I am very new to C sharp and WPF, my experince is mostly with Java, but started exploring the benefits of C sharp as well.

I have made a little research on which framework to use with WPF and I stoped here as catel took my attention with its simplicity. However I have some issues that probably are very simple to solve with your framework, but I am stuck with it... :-)

Here's my problem (I hope I'll explain it righ):

I have a xaml file which has a vontent control bound to a ViewModel's Model property.


...
<ContentControl
Content="{Binding Path=Workspace"/>
....

in the ViewModel corresponding to this user control I have the model defined and a two buttons that I bind to commands in the View model:
...
[Model]
public IWorkspaceViewModel Workspace
{
get { return GetValue<IWorkspaceViewModel>(WorkspaceModelProperty); }
set { SetValue(WorkspaceModelProperty, value); }
}

/// <summary>
/// Register the WorkspaceModel property so it is known in the class.
/// </summary>
public static readonly PropertyData WorkspaceModelProperty = RegisterProperty("Workspace", typeof(IWorkspaceViewModel));
...

public Command<object> EnergyUnitsControl { get; private set; }

/// <summary>
/// Method to invoke when the EnergyUnitsControl command is executed.
/// </summary>
/// <param name="parameter">The parameter of the command.</param>
private void OnEnergyUnitsControlExecute(object parameter)
{
Workspace = new EnergyUnitsViewModel();

//WorkspaceWrapper.setActiveWorkspace(new EnergyUnitsViewModel());
}

/// <summary>
/// Gets the OptimizationControl command.
/// </summary>
public Command<object> OptimizationControl { get; private set; }

/// <summary>
/// Method to invoke when the OptimizationControl command is executed.
/// </summary>
/// <param name="parameter">The parameter of the command.</param>
private void OnOptimizationControlExecute(object parameter)
{
Workspace = new OptimizationViewModel();
}


When I hit whichever of the buttons for a first time, the corresponding IWorkspaceViewModel is assigned to the Workspace property and then the corresponding xaml file is loaded (I have the mapping defined in DataTemplates - which view to load, based on a particular view model). When debuging I can see that both of the commands are fired, but after the first button hit, the xaml content of the inner user control (that is bound to the Workspace model) is not refreshed. How can I tell my xaml file to look after changes in the model property of the ViewModel? 

I am missing something but can find out what, please any help on this would be very very useful ;)

 

<textarea id="Comment" class="commentTextBox" cols="20" rows="2" name="Comment"></textarea>
Coordinator
May 16, 2011 at 1:51 PM

Hi, and welcome to Catel :-)

 

The code you posted looks ok. However, keep in mind that a viewmodel is not a model, but you are treating (or at least naming) it like that. To be ablento help you out, do you mind posting an example project that contains this behavior so we can help you out?

 

best regards,

 

Geert

May 16, 2011 at 1:59 PM

Thank you very much for the reply! Yes for sure I would post it - I am only not sure how it would be the best approach - to post here a whole xamls with a neted controls, corresponding ViewModels and datatemplates mappings or to attach somewhere an entire project with all the references and other details (because I am starting to think that some of my references might be not ok..)? 

May 16, 2011 at 2:45 PM

Here's a sample application witch describes my problem: There's a window which has two buttons AControl and BControl. Both are bound to corresponding commands AControlCommand and BControlCommand. In the MainWindowViewModel i added a Model named Workspace. in the main Window there's a Grid with 3 rows - 2 for thw buttons and the third row contains a Grid with ContentControl in it which is bound to Workspace.AControlCommand and BControlCommand are defined in MainWindowViewModel and all they does is to assign a new instance of the proper ViewModel to Workspace.

I expect that when hitting the buttons I will see the content in the third row changing, but it appears static (Also, In the real project I was able to achieve an ArgumentException saying property set method not found for which I have no idea too, but I think if we solve this case the other would not be relevant :-) ) For WPF to know which View to load I defined a DataTemplates.xaml (below in the code postings) - and actually for solving the issue I am mostly focused here, as I did'nt red anywhere in your articles about using such templates, but wasnt able to find other solution. Other thing is that I removed the line defining catel custom theme (not sure If this could cause some errors).These resources I defined in App.xaml (below in code postings)

I setup a new Catel WPF Project, then from the Library package manager console typed "install-package catel.windows". Other things that I did change as follows:

 

MainWindow.xaml:

 

<Windows:DataWindow x:Class="WPFApplication1.UI.Windows.MainWindow"
                    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:Windows="clr-namespace:Catel.Windows;assembly=Catel.Windows"
                    xmlns:ViewModels="clr-namespace:WPFApplication1.UI.ViewModels"
                    x:TypeArguments="ViewModels:MainWindowViewModel"
                    ShowInTaskbar="True">
    
    <!-- Resources -->
    <Windows:DataWindow.Resources>
    </Windows:DataWindow.Resources>
    
    <!-- Content -->
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Button Grid.Row="0" 
                        Name="AControl" 
                        Content="AControl"
                Command="{Binding Path=AControlCommand}">
        </Button>
        
        <Button Content="BControl" 
                Grid.Row="1"
                HorizontalAlignment="Stretch"
                Name="BControl"
                Command="{Binding Path=AControlCommand}"/>
        <Grid Grid.Row="3" x:Name="contentGrid" HorizontalAlignment="Stretch" Grid.Column="1" Margin="0">
            <ContentControl
                    Content="{Binding Path=Workspace}"/>
        </Grid>
    </Grid>
    
    
</Windows:DataWindow>

MainWindowViewModel.cs:

using Catel.MVVM;
using Catel.Data;

namespace WPFApplication1.UI.ViewModels
{
    /// <summary>
    /// MainWindow view model.
    /// </summary>
    public class MainWindowViewModel : ViewModelBase
    {
        #region Constructor & destructor
        /// <summary>
        /// Initializes a new instance of the <see cref="MainWindowViewModel"/> class.
        /// </summary>
        public MainWindowViewModel()
            : base()
        {
            AControlCommand = new Command<object>(OnAControlCommandExecute);
            BControlCommand = new Command<object>(OnBControlCommandExecute);
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets the title of the view model.
        /// </summary>
        /// <value>The title.</value>
        public override string Title { get { return "View model title"; } }

        #region Models
        /// <summary>
        /// Gets or sets the property value.
        /// </summary>
        [Model]
        public IWorkspaceModel Workspace
        {
            get { return GetValue<IWorkspaceModel>(WorkspaceProperty); }
            private set { SetValue(WorkspaceProperty, value); }
        }

        /// <summary>
        /// Register the Workspace property so it is known in the class.
        /// </summary>
        public static readonly PropertyData WorkspaceProperty = RegisterProperty("Workspace", typeof(IWorkspaceModel));
        #endregion

        #region View model
        // TODO: Register view model properties with the vmprop or vmpropviewmodeltomodel codesnippets
        #endregion
        #endregion

        #region Commands
        /// <summary>
/// Gets the AControlCommand command.
/// </summary>
public Command<object> AControlCommand { get; private set; }

/// <summary>
/// Method to invoke when the AControlCommand command is executed.
/// </summary>
/// <param name="parameter">The parameter of the command.</param>
private void OnAControlCommandExecute(object parameter)
{
    Workspace = new AControlViewModel();
}

/// <summary>
/// Gets the BControlCommand command.
/// </summary>
public Command<object> BControlCommand { get; private set; }

/// <summary>
/// Method to invoke when the BControlCommand command is executed.
/// </summary>
/// <param name="parameter">The parameter of the command.</param>
private void OnBControlCommandExecute(object parameter)
{
    Workspace = new BControlViewModel();
}
        #endregion

        #region Methods
        /// <summary>
        /// Initializes the object by setting default values.
        /// </summary>	
        protected override void Initialize()
        {
            // TODO: Implement logic to initialize the view model
        }

        /// <summary>
        /// Validates the fields.
        /// </summary>
        protected override void ValidateFields()
        {
            // TODO: Implement any field validation of this object. Simply set any error by using the SetFieldError method
        }

        /// <summary>
        /// Validates the business rules.
        /// </summary>
        protected override void ValidateBusinessRules()
        {
            // TODO: Implement any business rules of this object. Simply set any error by using the SetBusinessRuleError method
        }

        /// <summary>
        /// Saves the data.
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if successful; otherwise <c>false</c>.
        /// </returns>	
        protected override bool Save()
        {
            // TODO: Implement logic when the view model is saved
            return true;
        }
        #endregion
    }
}

AControl.xaml:
<Controls:UserControl x:Class="WPFApplication1.UI.Controls.AControl"
                      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                      xmlns:Controls="clr-namespace:Catel.Windows.Controls;assembly=Catel.Windows"
                      xmlns:ViewModels="clr-namespace:WPFApplication1.UI.ViewModels"
                      x:TypeArguments="ViewModels:AControlViewModel">
    
    <!-- Resources -->
    <UserControl.Resources>

    </UserControl.Resources>

    <!-- Content -->
    <Grid>
        <Label Content="Content for AControl" />
    </Grid>
</Controls:UserControl>

AControl.xaml.cs:
using Catel.Windows.Controls;
using WPFApplication1.UI.ViewModels;

namespace WPFApplication1.UI.Controls
{
    /// 
    /// Interaction logic for AConntrol.xaml.
    /// 
    public partial class AControl : UserControl
    {
        /// 
        /// Initializes a new instance of the  class.
        /// 
        public AControl()
        {
            InitializeComponent();
        }
    }
}

BControl.xaml:

<Controls:UserControl x:Class="WPFApplication1.UI.Controls.BControl"
                      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                      xmlns:Controls="clr-namespace:Catel.Windows.Controls;assembly=Catel.Windows"
                      xmlns:ViewModels="clr-namespace:WPFApplication1.UI.ViewModels"
                      x:TypeArguments="ViewModels:BControlViewModel">
    
    <!-- Resources -->
    <UserControl.Resources>

    </UserControl.Resources>

    <!-- Content -->
    <Grid>
        <Label Content="Content for B control" />
    </Grid>
</Controls:UserControl>

BControl.xaml.cs:

using Catel.Windows.Controls;
using WPFApplication1.UI.ViewModels;

namespace WPFApplication1.UI.Controls
{
    /// <summary>
    /// Interaction logic for BControl.xaml.
    /// </summary>
    public partial class BControl : UserControl<BControlViewModel>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="BControl"/> class.
        /// </summary>
        public BControl()
        {
            InitializeComponent();
        }
    }
}


App.xaml:

<Application x:Class="WPFApplication1.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="/UI/Windows/MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <!-- Generic Catel theme -->
            <ResourceDictionary.MergedDictionaries>
                <!-- <ResourceDictionary Source="/Catel.Windows;component/themes/generic.xaml" />-->
                <ResourceDictionary Source="Resources/DataTemplates.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

DataTemplates.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:vm="clr-namespace:WPFApplication1.UI.ViewModels"
                    xmlns:v="clr-namespace:WPFApplication1.UI.Controls">

    <DataTemplate DataType="{x:Type vm:AControlViewModel}">
        <v:AControl />
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:BControlViewModel}">
        <v:BControl />
    </DataTemplate>

</ResourceDictionary>


Many Thanks in advance!

Coordinator
May 16, 2011 at 2:53 PM

thanks for the reply. can you create a new issue and upload a zip file with the source code?

 

thanks in advance.

Coordinator
May 17, 2011 at 12:45 PM

I am currently at teched. I will take a look at this as soon as I get back home.

May 17, 2011 at 12:51 PM

greatly appreciated! I also created an Issue of this as you suggested: http://catel.codeplex.com/workitem/6633

Coordinator
May 21, 2011 at 2:14 PM

First of all, my compliments on how you set up the code. This way it is very easy for us to actually look at the issue instead of finding out how messy a project is.

The problem is that the values are the same (a == b). Therefore, the value is not overwritten. We included a fix. You have 3 options:

1) Define properties on the view-model so they are actually different

2) Download the latest source and compile the source (which includes the fix)

3) Wait for the official 1.4 release, which will be released in June

May 23, 2011 at 6:30 AM

Many thanks Geert!

For the option 1) - actually what I want is to have only one view model property which is of type some Interface that is implemented by all view models that could be assigned to this property. And then in the XAML file i have a grid with a content control which is bound to this view model property. Thus I though I could have come what of a dynamic loading of different user controls.

I guess I will wait for the 1.4 release as I have other organizationsl issues to solve and can wait.

Good luck with your cool project! :-)

Bye for now.

Coordinator
May 23, 2011 at 6:33 AM

You can use the IViewModel interface for that (or it must be that you need additional functionality).

You can follow this project and be notified when the new update gets released.

May 24, 2011 at 6:20 AM

Thanks :) 

I already subscribed for daily notifications about changes in the discussions. Will look forward for any new stuff and releases.