How bind view model class on view model / using IoC in Catel

Topics: Questions
Dec 30, 2010 at 3:10 PM
Edited Dec 30, 2010 at 5:12 PM

Hello Mr. Horrik, I thinking about port my wpf app based on caliburn to catel. I have some questions and I would be very happy if you give me answer on them.

I create simple model, it consist only 2 string properties (Nick, Password) and it also provide validation.

Here is model code:

 

    [Serializable]
    public class User:DataObjectBase<User>
    {
        #region Variables

        private const string NickNull = "e1";
        private const string NickInvalidCharacters = "e2";
        private const string PasswordNull = "e3";
        private const string PasswordInvalidCharacters = "e4";

        #endregion

        #region Constructor & destructor

        public User(){}

        protected User(SerializationInfo info, StreamingContext context) 
            : base(info, context){}

        #endregion

        #region Properties

        public string Nick
        {
            get { return GetValue<string>(NickProperty); }
            set{SetValue(NickProperty,value);}
        }


        public static readonly PropertyData NickProperty = 
        RegisterProperty("Nick", typeof(string), "Nick property", 
        (sender, e) => ((User)sender).OnNickChanged());


        private void OnNickChanged()
        {
            //TODO:
        }


        public string Password
        {
            get { return GetValue<string>(PasswordProperty); }
            set{SetValue(PasswordProperty,value);}
        }

        public static readonly PropertyData PasswordProperty =
            RegisterProperty("Password", typeof (string), "Password property",
                             (sender, e) => ((User)sender).OnPasswordChanged());

        private void OnPasswordChanged()
        {
            //TODO:
        }

        #endregion

        #region Methods

        protected override void ValidateFields()
        {
            if (string.IsNullOrEmpty(Nick))
            {
                SetFieldError(NickProperty, NickNull);
            }
            else
            {
                if (Regex.IsMatch(Nick, "[^a-zA-Z0-9-_.]"))
                {
                    SetFieldError(NickProperty, NickInvalidCharacters);
                }  
            }


            if (string.IsNullOrEmpty(Password))
            {
                SetFieldError(PasswordProperty, PasswordNull);
            }
            else
            {
                if (Regex.IsMatch(Password, "[^a-zA-Z0-9]"))
                {
                    SetFieldError(PasswordProperty,PasswordInvalidCharacters);
                }
            }

        }

        protected override void ValidateBusinessRules()
        {
            base.ValidateBusinessRules();
        }

        #endregion
    }

 

Then I create view model, which use this model and also has one command LogOn.

LogOnViewModel look like this:

 

    public class LogOnViewModel: ViewModelBase
    {
        #region Constructor & destructor

        public LogOnViewModel()
            : base()
        {
            LogOn=new Command<object, object>(LogOn_Execute, LogOn_CanExecute);
        }
        #endregion

        #region Properties

        public override string Title { get { return "Spirit"; } }

        #region Models

        [Model]
        public User Avatar
        {
            get { return GetValue<User>(AvatarProperty); }
            private set{SetValue(AvatarProperty,value);}
        }

        public static readonly PropertyData AvatarProperty =
            RegisterProperty("Avatar", typeof (User));

        #endregion

        #region View model

        [ViewModelToModel("Avatar")]
        public string Nick
        {
            get { return GetValue<string>(NickProperty); }
            set { SetValue(NickProperty, value); }

        }

        public static readonly PropertyData NickProperty =
            RegisterProperty("Nick", typeof (string));


        [ViewModelToModel("Avatar")]
        public string Password
        {
            get { return GetValue<string>(PasswordProperty); }
            set { SetValue(PasswordProperty, value); }
        }

        public static readonly PropertyData PasswordProperty =
        RegisterProperty("Password", typeof(string));

        #endregion

        #region Commands

        public Command<object, object> LogOn;

        private bool LogOn_CanExecute(object parameter)
        {
            return true;
        }

        private void LogOn_Execute(object  parameter)
        {
            var messageService = GetService<IMessageService>();
            messageService.ShowInformation("Loging on server!");
        }

        #endregion

        #endregion

        #region Methods

        /// <summary>
        /// Initializes the object by setting default values.
        /// </summary>     
        /// 
        protected override void Initialize()
        {
        }

        protected override void ValidateFields()
        {
        }

        protected override void ValidateBusinessRules()
        {

        }
   
        protected override bool Save()
        {
            return true;
        }

        #endregion

    }

 

In my scenario I would like bind properties Nick and Password from view model (LogOnViewModel) and also command LogOn on controls in view.

Nick, Password I bind on textbox controls and command LogOn on button event click.

My view is user control. Look like this:

 

 

<Controls:UserControl  
             x:Class="Spirit.Views.LogOnView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:Controls="clr-namespace:Catel.Windows.Controls;assembly=Catel.Windows"
             xmlns:UserControlWithParameter="clr-namespace:Spirit.ViewModels" 
             x:TypeArguments="UserControlWithParameter:LogOnViewModel"
             mc:Ignorable="d"             
             d:DesignHeight="300" d:DesignWidth="300">

....

            <Label  Content="Nick" 
                    Style="{StaticResource labelStyle}"
                    Grid.Row="1"/>

            <TextBox    Name="Nick"
                        Style="{StaticResource textBoxStyle}"
                        Validation.ErrorTemplate="{StaticResource TextBoxErrorTemplate}"
                        Grid.Row="2"/>

            <Label  Content="Heslo" 
                    Style="{StaticResource labelStyle}"  
                    Grid.Row="3"/>

            <TextBox    Name="Password"
                        Style="{StaticResource textBoxStyle}"
                        Validation.ErrorTemplate="{StaticResource TextBoxErrorTemplate}"
                        Grid.Row="4"/>

            <Button Command="{Binding LogOn}"
                    Width="100" 
                    Grid.Row="5"
                    Height="25" Margin="4,40,4,4" />
....

</Controls:UserControl>

 

Code behind:

 

    public partial class LogOnView : UserControl<LogOnViewModel>
    {
        public LogOnView()
        {
            InitializeComponent();
        }
    }

 

I add this view to the shell, shell is wpf window:

 

<Window x:Class="Spirit.Views.ShellView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:Views="clr-namespace:Spirit.Views" 
        Title="ShellView" Height="300" Width="300">
    <Grid>
        <Views:LogOnView></Views:LogOnView>
    </Grid>
</Window>

 

And now my questions ;)

1. My project has this file structure:

  • Model (folder)
    • User.cs
  • ViewModels (folder)
    • LogOnViewModel.cs
  • Views (folder)
    • LogOnView.xaml (user control)
    • ShellView.xaml (wpf window)

I run my wpf app,click on button but method LogOn_Execute from LogOnViewModel is not called. What I do wrong ?

 

2. I would like use my service from external assembly. Usually I use MEF and in caliburn I use bootraper on load external assembly with reflection. 

In the code I use IoC class from caliburn framework. It exist similar way in catel? I search on codeproject but I didn’t find example how work in catel in IoC, DI. Any code example?

Something like this:

 

    public interface  ILogOnViewModel
    {
        
    }

    [Export(ILogOnViewModel)]
    public class LogOnViewModel : ViewModelBase,ILogOnViewModel
    {
        private IMyService _service;

        [ImportingConstrutor]
        public LogOnViewModel(IMyService service)
        {
            _service = service;
        }
    }

 

3. My last question is about code snippets. I add refrence on your DLLs in my project, try some code snippets from codeproject article but it doesn’t works. I am not familiar with Visual Studio yet.

Then I copy XML files from Catel zip to file:///C:/Users/Jan/Documents/Visual%20Studio%202010/Code%20Snippets/XML/, but still doesn’t work :(

If my view model class is created with MEF (decorated with Export attribute), MEF automaticaly inject all services through construtor of view model class.

 

I have another questions but before I will ask  I must solve my first problem ;).

Sory if my question are stupid I  am newbie in WPF and MVVM world also sory for my english :)

 

PS: I read your article on codeproject http://www.codeproject.com/KB/WPF/Catel.aspx.

I found some keying mistake:

Original from codeproject:

 

public static readonly PropertyDataSimplePropertyProperty = 
    RegisterProperty("SimpleProperty", typeof(string), "Simple property");

 

I think it sould be:

 

public static readonly PropertyData SimplePropertyProperty = 
    RegisterProperty("SimpleProperty", typeof(string), "Simple property");

 

But this is very little mistake. Same is in:

 

public static readonly PropertyDataCallbackPropertyProperty = 
    RegisterProperty("CallbackProperty", typeof(string), "Callback property", 
    (sender, e) => ((FirstDataObject)sender).OnCallbackPropertyChanged());

 

Your articleis very detailed and clear.

Thank  you for your help and time. One advice I think it would be very helpfull if code samples are complet visual studio projects no only class file.

Developer
Jan 1, 2011 at 11:59 AM
Edited Jan 1, 2011 at 12:33 PM

Hi Miszko,

For the first question, change the declaration of the LogOn Command to:

public Command LogOn { get; private set; }

You need a public accessor on the LogOn Command.

Did you try the Code Snippets Manager, under Tools in Visual Studio  to add the snippets?

I will take a look at question two when I get the chance.

 

 

Jan 2, 2011 at 10:13 AM

Hello Mr. Vermeer, thank for advices, First and third problem is solved, it was my carelessness.

I would like use some IoC/DI in Catel if it’s possible. I search for any tutorial or code sample, but I didn’t find. If you can help me with this problem I would be very gratefull.

Coordinator
Jan 2, 2011 at 12:26 PM

I see that you are getting quite known with Catel, good use of the classes!

Thanks for the typos, I will fix them in the article on the next update! We are currently investigating for a good solution to support IoC. Currently, we have created overloads so you can inject your own (test) implementations of the services into a ViewModel. We don't want to create another reference to an external library (Microsoft.Unity), so if you have any suggestions, just let us know! You can also vote for this issue:

http://catel.codeplex.com/workitem/5814

Jan 2, 2011 at 8:36 PM
Edited Jan 2, 2011 at 8:37 PM

Mr. Horrik, I am 16 years old student. I am only beginning with MVVM paterns, I am looking for prepective framework ;).

Offtopic:

About IoC.

In MEF and Caliburn I have this problem:  http://mef.codeplex.com/Thread/View.aspx?ThreadId=240201

I create view model with MEF, active as screen, then I deactive this screen  and active new screen.

Instance for view model  is still alive whole lifetime of app. It would be nice have a way how destroy / kill instaces of view models classes  - if it is possible.

 

The best of Catel is this feature with InterstedIn attribute. I lovin it. In caliburn  you must use event aggregator and also write own conductor class, it is borring.

Also bussy indicator is good, in caliburn you don’t find this.

 

 

 

Coordinator
Jan 3, 2011 at 2:39 PM

Great to hear that your first experience with Catel is great! If you can find the time, can you please rate the project? Thanks in advance!

In Catel, view models are destroyed as soon as the UserControl or Window is unloaded. Therefore, your models are either cleaned up (CancelEdit if canceled) or committed (EndEdit if applied). Then, you won't have to worry about view models in your memory any longer. In Catel, we are very precise about subscribing and unsubscribing to/from events. This way, the Garbage Collector can actually do its job.

 

If you have any ideas to improve Catel, don't hesitate to create new "issues" (an issue isn't a bug or a problem, it can also be a new feature)!

 

Jan 5, 2011 at 2:33 PM

Mr. Horrik, thank for explanation of memory management with view models.  I  think of the next features would be support of IoC...also full silverlight support.

Coordinator
Jan 5, 2011 at 4:22 PM

Silverlight is already fully supported, or are you missing features?

I will look into IoC.

Jan 5, 2011 at 6:28 PM

Hello Mr. Horrik, my friend try Catel in Silverlight, and he told that something missing, I will ask him tommorow..but I  think it is his fail, appologize us we are 16 years boys from Poland.

If Catel will have support of IoC I think many peoples  will use it in own project  because for  me/us is now dificult load service from external assembly and inject in view model classes.

Thank for your time I appreciate it.

Coordinator
Jan 6, 2011 at 12:04 PM

IoC is now implemented. By default, the services of Catel will be used in the ViewModelBase. You can configure custom services implementations via configuration file or custom registration to Catel.IoC.UnityContainer instance.

Jan 13, 2011 at 10:12 PM
GeertvanHorrik wrote:

IoC is now implemented. By default, the services of Catel will be used in the ViewModelBase. You can configure custom services implementations via configuration file or custom registration to Catel.IoC.UnityContainer instance.

Mr. Horrik

Is possible somewhere find code example on Ioc, maybe with  bootstraper. Thank you

Coordinator
Jan 14, 2011 at 8:25 AM

Yes, you have two options:

1) View the 4th article about Catel on CodeProject: http://www.codeproject.com/KB/WPF/Catel_part4.aspx

2) Download the latest source and take a look at the 4th article code. In the folder "articles" (included in the root of the source), you can find the articles in word format as well.

 

Any news on what your friend is missing in the Silverlight part? If so, please start a new discussion for it, maybe other people want to join in as well.

Jan 14, 2011 at 12:56 PM

Mr. Horrik. First thank... I called him toady and everything with silverlight is ok, I think it was his mistake. Sory for this "hoax".

 

Coordinator
Jan 14, 2011 at 2:12 PM

Great to hear that! If there is anything we can do for you friend to improve the SL support, just let us know!

Oct 25, 2012 at 3:50 PM
Edited Oct 25, 2012 at 7:52 PM

Hi,

I may made some mistake for passwordbox control in this top sample program.

Base on top of this XAML program, I changed password TextBox to PasswordBox like this:

 

<PasswordBox Name="Password" Grid.Row="4">
     <i:Interaction.Behaviors>
           <catel:UpdateBindingOnPasswordChanged Password="{Binding Password, Mode=TwoWay, NotifyOnValidationError=True , ValidatesOnDataErrors=True,UpdateSourceTrigger=PropertyChanged}" />
     </i:Interaction.Behaviors>
</PasswordBox>

 

as result, it will show red box when password is empty or password is incorrectly.

But somehow, screen does not show any change when password is incorrect. I checked password in VM, it does sit in VM.Password property.

Anything is wrong about this code?

Coordinator
Oct 26, 2012 at 6:58 PM

The password is not bindable by itself. The UpdateBindingOnPasswordChanged does solve this, but this does not pass the validation to the actual control (which must show the validation error).

In other words, if you want this to work, you must set the validation adorner for the UpdateBindingOnPasswordchanged to the Password box. However, I don't think this is possible for behaviors. Also, it would be very, very easy to guess passwords this way... You should reconsider your design.

Oct 26, 2012 at 8:53 PM

Hi, thank you quickly ask my question.

In my case, I just make sure password box is not empty, if this control is empty, I will disable login button. So I think I do need this function for this case.

If you can add this function it should be perfect. 

 

Eric.

Coordinator
Oct 27, 2012 at 10:17 AM

Just override the ValidateFields method and make sure that you add the error. Then the OK command will not be available.