IOC in WPF/WCF

It makes sense to take advantage of IOC in WCF, because all WCF services are parallel, and usually WCF service suppose to behave like a wrapper to Domain service which might needs a repository which needs an NHibernate session… How to new up those dependencies? IOC is the easiest way. And it becomes the new evil global.

In WPF, we’ve already got an un-avoidable global main window, or shell view in MVVM. Adding IOC just introduces more overhead. Even further Caliburn.Micro framework forces developer to implement a bootstrapper acting as the wrapper to the actual IOC container, the only dependency in this bootstrapper is the shell view model. If your shell view model has dependencies, you need to set them up in your underlying container. To me, this is a chicken-egg question.

 public class StructureMapBootstrapper : Bootstrapper<IShellViewModel>
    {

        protected override void Configure()
        {
            ObjectFactory.Initialize(x => x.AddRegistry<WpfDependencyRegistry>());
        }

        protected override object GetInstance(System.Type service, string key)
        {
            return ObjectFactory.GetInstance(service);
        }

    }

// StructureMap is not available in Silverlight yet, use SimplerCotainer from Caliburn instead in SL
   public class SilverlightBootstrapper : Bootstrapper<IShellViewModel>
    {
        private SimpleContainer _container;

        protected override void Configure()
        {
            _container = new SimpleContainer();

            _container.RegisterSingleton(typeof(IMessageBox), null, typeof(SlMessageBox));
            _container.RegisterSingleton(typeof(IProductSearchTask), null, typeof(ProductSearchTask));
            _container.RegisterSingleton(typeof(IProductCrudTask), null, typeof(ProductCrudTask));

            _container.RegisterSingleton(typeof(IShellViewModel), null, typeof(ShellViewModel));

        }

        protected override object GetInstance(Type service, string key)
        {
            return _container.GetInstance(service, key);
        }
    }

A better solution might be to use poor man injection in shell view model to new up all the dependencies in it’s no-arg ctor. This will remove any IOC usuage from WPF/Silverlight project, so UI developer can relief from framework hassle, and concentrate on their spinning button work.

Advertisements

Implement dirty notification on avalon doc tab + caliburn micro

I got a problem to solve when trying to implement the VS-like dirty notification in my WPF app,

I realized the way I opened the AvalonDockTab in my previous post is not very friendly to make this happen.

When ViewModel changed the displayname of Screen after the tab openning, it’s very hard the notify view to refresh it.

Using code to set databinding is a much better solution.


private DocumentContent CreateTab(object view, Screen currentViewModel)
{
	var documentContent = new DocumentContent
							  {
								  DataContext =  (currentViewModel),
								  Content = view,
//                Title = currentViewModel.DisplayName, // should set it via databinding, if vm changed it later, how to refresh title?

							  };

	BindTabTitle(documentContent);

//            documentContent.SetBinding(TitleProperty, new Binding("DisplayName"));  //why this does not work?
	return documentContent;
}

static void BindTabTitle(DocumentContent tab)
{
	DependencyProperty textProp = DocumentContent.TitleProperty;
	if (!BindingOperations.IsDataBound(tab, textProp))
	{
		Binding b = new Binding("DisplayName");
		BindingOperations.SetBinding(tab, textProp, b);
	}
}
//... in view model
        private string _name;
        public string Name
        {
            get { return _name; }
            set {
                _name = value;
                DisplayName = "Product details: " + _name;

                if (_name != _product.Name) DisplayName += "*";
            }
        }
	private void SetOriginalData()
	{
		Name = _product.Name;
		Description = _product.Description;
	}

	public void Save()
	{
		_product.Name = Name;
		_product.Description = Description;

		try
		{
			_productCrudTask.Save(_product);

			_messageBox.Show("Product " + Name + " saved successfully.");

			SetOriginalData(); // to reset dirty flag, still looking for a better way.

		}
		catch (DataValidationException e)
		{
			_messageBox.Show("Error! " + e.Message);
		}
	}

	public void Cancel()
	{
		SetOriginalData();
	}

When Caliburn.Micro meets AvalonDock

Using classic WPF tabcontrol in Caliburn.Micro is very easy, view model just needs to inherited from Conductor<IScreen>.Collection.OneActive, then add sub-viewmodels into Items property, or call ActivateItem(). The view side only need to name the tablecontrol to “Items”, everything is done! (WPF only, SilverLight app needs to implement tabitemconverter explicitly)

But, in some fancy used user control world, e.g., AvalonDock, this doesn’t work anymore, because Caliburn magic just automatically find those mapping views and then convert them to tabitems, it doesn’t know Avalon Control at all.

Sure, we can create our own TabItemConverter.


   public class TabItemConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is BindableCollection<IScreen>)
            {
                var screenList = (BindableCollection<IScreen>)value;

                List<DocumentContent> result = new List<DocumentContent>();

                foreach (var screen in screenList)
                {
                    var view = LocateViewFor(screen);

                    var tabItem = new DocumentContent();
                    ViewModelBinder.Bind(screen, tabItem, null);
                    tabItem.Content = view; //TODO: can this be done by xaml caliburn.View
                    BindTabTitle(tabItem);
                    result.Add(tabItem);

                }
                return result;

            }

            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        private object LocateViewFor(object viewModel)
        {
            var view = ViewLocator.LocateForModelType(viewModel.GetType(), null, null);
            return view;
        }

        static void BindTabTitle(DocumentContent tab)
        {
            DependencyProperty textProp = DocumentContent.TitleProperty;
//            if (!BindingOperations.IsDataBound(tab, textProp)) // Not available in Silverlight
            {
                Binding b = new Binding("DisplayName");
                BindingOperations.SetBinding(tab, textProp, b);
            }
        }


                <ad:DocumentPane ItemsSource="{Binding Items, Converter={StaticResource TabItemConverter}}" SelectedItem="{Binding ActiveItem, Mode=TwoWay}" >

                </ad:DocumentPane>

It works, sort of. Opening tab looks fine, but active doesn’t. Any still have some goofy issues, like when click the dropdown icon at the upper right corner will kill the app!?It probably because the ItemsSource binding doesn’t work very well with AvalonDock control.

I created the workaround for Caliburn + Avalondock, now with the new slim but powerful Caliburn.Micro framewrok, this task is much easier, thanks to the new static classes: ViewLocator and ViewModelBinder.

Details: it was a problem in the pure view based design world. In shellView, we can easily do this:


            var dockableContent = new DockableContent
                                      {
                                          Content = new SearchView(),
                                      };

            dockManager.Show(dockableContent, DockableContentState.Docked, AnchorStyle.Left);

The idea of view model based design is to remove the new SearchView(), instead should rely completely on shellViewModel to handle all the creation, by calling ActivateItem(searchViewModel).

The problem is, AvalonDock doesn’t behave as the classic tabcontrol, we can not just bind the items into it’s datacontext.

The solution, use event handler between view and view model:

Code:


   public class ShellViewModel : Conductor<Screen>, IShellViewModel
    {
        private readonly IMessageBox _messageBox;
        private IShellView _view;

        public ShellViewModel(IMessageBox messageBox)
        {
            _messageBox = messageBox;
            RequestViewToLoadDockPanel = (o, e) => { };
        }

        protected override void OnViewLoaded(object view)
        {
            _view = (IShellView) view;
            RequestViewToLoadDockPanel += _view.LoadDockPanel;

            ShowLeftDockPanel();
        }

        private void ShowLeftDockPanel()
        {
            var searchViewModel = new SearchViewModel(_messageBox);
            ActivateItem(searchViewModel);
            RequestViewToLoadDockPanel(this, new ScreenEventArgs(searchViewModel));
        }

        public event EventHandler<ScreenEventArgs> RequestViewToLoadDockPanel;
    }

    public interface IShellView
    {
        void LoadDockPanel(object sender, ScreenEventArgs e);
    }

    public class ScreenEventArgs : EventArgs
    {
        public Screen Screen { get; set; }

        public ScreenEventArgs(Screen screen)
        {
            Screen = screen;
        }
    }

    public partial class ShellView : Window, IShellView
    {
        public ShellView()
        {
            InitializeComponent();
        }

        #region IShellView Members

        public void LoadDockPanel(object sender, ScreenEventArgs e)
        {
            var view =   LocateViewFor(e.Screen);

            var dockableContent = new DockableContent
                                      {
                                          Content = view,
                                      };

            dockManager.Show(dockableContent, DockableContentState.Docked, AnchorStyle.Left);
        }

        #endregion

        private object LocateViewFor(object viewModel)
        {
            var view = ViewLocator.LocateForModel(viewModel, null, null);

            ViewModelBinder.Bind(viewModel, view, null);

            return view;
        }
    }

Nested view/usercontrol binding in Caliburn

If we have a viewmodel like this:

 public class DemoViewModel
    {
        public string Name { get; set; }
        public DemoViewModel()
        {
            Name = "testing";
         }
    }

To bind a nested view inside usercontrol, we use cal:View.Model keyword:

<UserControl x:Class="Demo.UI.Wpf.Views.DemoView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Height="369" Width="425" xmlns:my="clr-namespace:Demo.UI.Wpf.Views"
        xmlns:cal="http://www.caliburnproject.org"
        >

	<Grid>
        <Label Content="{Binding Name}" Height="28" HorizontalAlignment="Left" Margin="54,66,0,0" Name="label1" VerticalAlignment="Top" />
        <my:SubModuleDemoView HorizontalAlignment="Left" Margin="45,164,0,0" x:Name="subModuleDemoView1" VerticalAlignment="Top" Width="307" cal:View.Model="{Binding this}" />
    </Grid>
</UserControl>

<UserControl x:Class="Demo.UI.Wpf.Views.SubModuleDemoView"
             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"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">

    <Grid>

        <Label Content="{Binding Name}" Height="28" HorizontalAlignment="Left" Margin="140,123,0,0" Name="label1" VerticalAlignment="Top" />

    </Grid>
</UserControl>

The trick is in cal:View.Model, it can bind to “this” current datacontext, it can also bind to a specific property which is a sub viewmodel:

    public class DemoViewModel
    {
        public string Name { get; set; }

        public SubModuleDemoViewModel SubModule { get; set; }
        public DemoViewModel()
        {
            Name = "testing";
            SubModule = new SubModuleDemoViewModel();
        }
    }

        ...
        <my:SubModuleDemoView HorizontalAlignment="Left" Margin="45,164,0,0" x:Name="subModuleDemoView1" VerticalAlignment="Top" Width="307" cal:View.Model="{Binding SubModule}" />
        ...

The label will display the Name property from sub-viewmodel.

I’m still learning xaml, why use

 xmlns:cal="http://www.caliburnproject.org"

in xaml? Instead of

xmlns:cal="clr-namespace:Caliburn.PresentationFramework.ApplicationModel;assembly=Caliburn.PresentationFramework"

AvalonDock databinding problems

The old version Avalondock 1.2.2691.0, when windowState is set to maxsized, the first tab won’t open in docmanager!

Upgraded to latest version, this problem seems be fixed, but, we have to call notifyPropertyChange for every single ViewModel properties to refresh UI!

Both version are having different databinding issues.

To upgrade or not, it’s a question.

Adopting Caliburn to easy WPF MVVM development

How to manage child window/tabpages, like open/close, close-query event, …? I was wondering about this for a long time when starting MVVM pattern.

In another word, where is the best place to hold the states of all child tabs? The storytellor example created by  Jeremy Miller was using shell presenter to control this. Same as the caiburm framework, RootViewModel is acting as a multi-presenter manager. Open a new presenter just add a new item into presenters property of shell view model, as long as the tabcontrol on view bind to this presenter property, the tabpage will be fully controlled by shell view model, as this post explained.

But, we are using AvalonDock tab control in our WPF app, the datacontext of Avalon dockingManger can only be DocumentContent, can’t be the invisible viewmodel/presenter. An typeConverter might fix this problem, but my xaml skill is too poor, I create a complete action for Open(Presenter) method provided by caliburn.

Some nice features I really like from Caliburn:

  1. Provide Out-of-box adapter for structureMap to hook up it’s standard IOC (Unity).
  2. Covention over configuration. VM in ViewModels, View in Views model, binding by same name.
  3. Context base binding, dependency property on view to control binding context specific view.
  4. Button on view will look for the matching name method on view model.
  5. Show static class to solve the classic question of popping up message box in view model.(I haven’t fully understand IResult yet, but it’s cool and very flexible).

Some questions to answer:

  1. How to automatically hook up event handler between view and view model? such as,  hooking up closing event in shell view to shellviewmodel.Shutdown()
  2. Button can automatically bind to method, what about menu?
  3. Where is the context parameter in Open(Presenter, Action) method?
  4. The next version of caliburn will replacePresenter to screen, should we jump into it right now?

UnhandledExceptionDispatcher for WPF app

It’s been a while since my previous post about UnhandledExceptionDispatcher in Wpf app. Requests have changed very much, such as:

  1. UnhandledException should be displayed in a nice user friendly window.
  2. UnhandledException should be logged in somewhere.

For 2nd one, I’ve already found the solution by using log4net remotesink. What I need to do is just merging the logging into my logviewer window.

There is a very good exception viewer from the Google result, very nice and handy. My tasks:

  1. Port it into WPF and C#
  2. Remove option of sending and not sending, because logging is happening behind the scene. It will be useful if we are dealing with wide used internet WPF application, for intranet, we don’t need those options.
    public partial class App : Application
    {
        private void OnStartup(object sender, StartupEventArgs e)
        {
            Log.Initialize(new Log4NetLogger(SystemInfo.ApplicationBaseDirectory + "\\Log4Net.config"));
            Log.For(this).Info("Log4Net initialized in app startup.");

            new UnhandledExceptionDispatcher(this, false, true);
            var shell = new Shell();
            shell.Show();
            MainWindow = shell;
            shell.BindViewModel(ObjectFactory.GetInstance<IShellViewModel>());

        }
    public class UnhandledExceptionDispatcher
    {
        private readonly Application _app;
        private readonly bool _handleException;
        public bool RestartApp { get; set; }

        public UnhandledExceptionDispatcher(Application app, bool restartApp, bool handleException)
        {
            _app = app;
            _handleException = handleException;
            RestartApp = restartApp;
            _app.DispatcherUnhandledException += Application_DispatcherUnhandledException;

            Log.For(_app).Info("UnhandledExceptionDispatcher Initialized.");
        }

        public void Application_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
        {

            Log.For(_app).Error("Application error occurred", e.Exception);
            ShowUnhandledException(e.Exception);
            e.Handled = _handleException;
            if (_app != null) _app.Shutdown(-1);
        }

        private void ShowUnhandledException(Exception e)
        {
            var viewer = new UnhandledExceptionViewer(e, RestartApp);
            //  or use the simple built-in viewer:
            // MessageBox.Show(e.Message, "This Application has encountered a problem");

           if (_app.MainWindow.IsVisible)
                  viewer.Owner = _app.MainWindow;

           viewer.ShowDialog();
     }
 }