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;
        }
    }

Handling MessageBox in Caliburn.Micro

EisenbergEffect indicated he is injecting delegate to implement messagebox in his ongoing Caliburn.Micro application, with a very general code example:


  public delegate void ShowMessageBox(string message, string title, MessageBoxOptions options = MessageBoxOptions.Ok, Action&lt;IMessageBox&gt; callback = null);

In case you still don’t get the idea, here is a developer doing the exactly same thing.

 

My solution came up with code like this:


public class MyViewModel: Screen {
 public MyViewModel(Action<string> showMessage, Func<string, string, bool> showConfirm)
 {
     _showMessage = showMessage;
     _showConfirm = showConfirm;
 }

  public void Delete(){
     if (!_showConfirm("Are you very very sure to delete it?", "Confirmation")) return;
     // Do delete...
    _showMessage("Deleted as you reuqested!")
  }
}
   public class HelloWp7Bootstrapper : PhoneBootstrapper
   {
        PhoneContainer _container;

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

            ...
            _container.RegisterInstance(typeof(Action<string>), null, (Action<string>)(msg => MessageBox.Show(msg)));
            _container.RegisterInstance(typeof(Func<string, string, bool>), null,
                (Func<string, string, bool>)((msg, capt) => MessageBox.Show(msg, capt, MessageBoxButton.OKCancel) == MessageBoxResult.OK));

            ...

WinPhone, CaliburnMicro, and Sqlite

Where is TDD in WP dev?

Simple class can be tested in SilverLight.NUint project, (must modify guid to make it look as WP project), but most WP classes are not TDD friendly, e.g., IsolatedStorageFile (another MS no-interface sealed class), how can we make IsolatedStorageFile.GetUserStoreForApplication() run in non-WP environment?  So shameful doing F5 for testing every single code change.

Tried to switch to Sqlite on WP7 project, same issue, can’t run in out of simulator, but, sqlite is much popular, it’s also supported in iPhone, while Apple suggests developer go to core-data.

Anther bad news, no NHibernate for Sqlite on WP yet.

Powerful Caliburn.Micro

Convention binding in CaliburnMicro is amazing, cal:AppBarButton can be easily hooked up to the method with then same name as the message sent by button.

Url get parameter auto-wiring works like a charm in those decedents from Screen base class. Wiring won’t be ready in ctor, must wait in OnInitialize.

Navigation service is handy, but View-based style binding still looks awful to me, passing object instead of values to the new opened view/viewmodel is very difficult, I had to back to evil global/static class to pass object around, ugly, but very easy to use.

OnViewLoaded method is great, a perfect place to hook up event handle to view.

‘Unrecognized char’ issue in sqlite

Got this issue when trying insert string using sqlite_execute, suggestion is to use sqlite3_prepare/bind_text/step/reset instead.


string sql = string.Format("INSERT INTO my_table (id, title, summary ) VALUES({0},?,?);",
                                           Id);

Sqlite3.Vdbe stmt = null;

_rc = Sqlite3.sqlite3_prepare_v2(_db, sql, -1, ref stmt, Sqlite3.SQLITE_NULL);
if (_rc != Sqlite3.SQLITE_OK) { throw new SqliteException(Sqlite3.sqlite3_errmsg(_db) ); }

_rc = Sqlite3.sqlite3_bind_text(stmt, 1,  Title, -1, Sqlite3.SQLITE_STATIC);
if (_rc != Sqlite3.SQLITE_OK) { throw new SqliteException(Sqlite3.sqlite3_errmsg(_db)); }

_rc = Sqlite3.sqlite3_bind_text(stmt, 2,  Summary, -1, Sqlite3.SQLITE_STATIC);
if (_rc != Sqlite3.SQLITE_OK) { throw new SqliteException(Sqlite3.sqlite3_errmsg(_db)); }

_rc = Sqlite3.sqlite3_step(stmt);
if (_rc != Sqlite3.SQLITE_DONE) { throw new SqliteException(Sqlite3.sqlite3_errmsg(_db)); }

_rc = Sqlite3.sqlite3_reset(stmt);
if (_rc != Sqlite3.SQLITE_OK) { throw new SqliteException(Sqlite3.sqlite3_errmsg(_db)); }

It’s strange that sqlite3_step returns 101 instead of 0.

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"

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?