Search code examples
c#wpfgridninjectcaliburn.micro

How to reuse a new view in MVVM with Caliburn.Micro and Ninject


I am testing the possibility to modify dynamically the structure of Grid control (number of Rows/Columns for example).

I am using last version Caliburn.Micro and Ninject and use a GridHelpers (i have modified) to have the possibility to bind number of Rows and Columns.

The full code of my application is available on Github

I have created an usercontrol MyGridView:

<UserControl x:Class="GridHelpersSample.MyGridView"
             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:gh="clr-namespace:GridHelpers;assembly=GridHelpers"
             xmlns:cal="http://www.caliburnproject.org"
             xmlns:local="clr-namespace:GridHelpersSample"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <ItemsControl ItemsSource="{Binding ButtonViewModels}">
            <ItemsControl.ItemContainerStyle>
                <Style>
                    <Setter Property="Grid.Row" Value="{Binding GridRow}" />
                    <Setter Property="Grid.Column" Value="{Binding GridColumn}" />
                </Style>
            </ItemsControl.ItemContainerStyle>
            <ItemsControl.ItemTemplate >
                <DataTemplate >
                    <Button cal:Message.Attach="[Event Click] = [Action OnButtonClick($dataContext)]"
                                Content="{Binding Content}"/>
                </DataTemplate >
            </ItemsControl.ItemTemplate >
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Grid ShowGridLines="true" 
                        gh:GridHelpers.RowCount = "{Binding RowCount, Mode=TwoWay}"
                              gh:GridHelpers.ColumnCount = "{Binding ColumnCount, Mode=TwoWay}"
                              gh:GridHelpers.StarRows = "{Binding StarRows, Mode=TwoWay}"
                              gh:GridHelpers.StarColumns = "{Binding StarColumns, Mode=TwoWay}"
                              gh:GridHelpers.PixelRows = "{Binding PixelRows, Mode=TwoWay}"
                              gh:GridHelpers.PixelColumns = "{Binding PixelColumns, Mode=TwoWay}" >

                    </Grid>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <!-- Now define a container that will host the ItemsControl -->
            <!--<ItemsControl.ItemsPanel >
                    <ItemsPanelTemplate >
                        <VirtualizingStackPanel Orientation="Horizontal"/>
                    </ItemsPanelTemplate >
                </ItemsControl.ItemsPanel >-->
        </ItemsControl >
    </Grid>
</UserControl>

the view model associated:

using Caliburn.Micro;

namespace GridHelpersSample
{
    public class MyGridViewModel : Screen
    {
        public BindableCollection<ButtonViewModel> ButtonViewModels { get; set; }

        public MyGridViewModel(Main2ViewModel main2ViewModel)
        {
            RowCount = main2ViewModel.RowCount;
            ColumnCount = main2ViewModel.ColumnCount;
            StarRows = main2ViewModel.StarRows;
            StarColumns = main2ViewModel.StarColumns;
            PixelRows = main2ViewModel.PixelRows;
            PixelColumns = main2ViewModel.PixelColumns;
            ButtonViewModels = main2ViewModel.ButtonViewModels;
        }

        #region datas for defining grid
        private string rowCount;
        public string RowCount
        {
            get { return rowCount; }
            set
            {
                rowCount = value;
                NotifyOfPropertyChange(() => RowCount);
            }
        }
        private string columnCount;
        public string ColumnCount
        {
            get { return columnCount; }
            set
            {
                columnCount = value;
                NotifyOfPropertyChange(() => ColumnCount);
            }
        }
        private string starRows;
        public string StarRows
        {
            get { return starRows; }
            set
            {
                starRows = value;
                NotifyOfPropertyChange(() => StarRows);
            }
        }
        private string starColumns;
        public string StarColumns
        {
            get { return starColumns; }
            set
            {
                starColumns = value;
                NotifyOfPropertyChange(() => StarColumns);
            }
        }

        private string pixelRows;
        public string PixelRows
        {
            get { return pixelRows; }
            set
            {
                pixelRows = value;
                NotifyOfPropertyChange(() => PixelRows);
            }
        }
        private string pixelColumns;

        public string PixelColumns
        {
            get { return pixelColumns; }
            set
            {
                pixelColumns = value;
                NotifyOfPropertyChange(() => PixelColumns);
            }
        }
        #endregion

        public void OnButtonClick(ButtonViewModel context)
        {

        }
    }
}

the Main2View has a content control and all the settings to define the structure of the grid defined in the usercontrol.

<Window x:Class="GridHelpersSample.Main2View"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:gh="clr-namespace:GridHelpers;assembly=GridHelpers"
        xmlns:cal="http://www.caliburnproject.org"       
        xmlns:local="clr-namespace:GridHelpersSample"
        mc:Ignorable="d"
        Title="Main2View" Height="600" Width="1000">
    <Window.Resources>
        <Style x:Key="LabelStyle" TargetType="Label">
            <Setter Property="Width" Value="120" />
            <Setter Property="Margin" Value="5" />
            <Setter Property="FontSize" Value="16" />
            <Setter Property="FontWeight" Value="Bold" />
            <Setter Property="Foreground" Value="Blue" />
        </Style>
        <Style x:Key="TextBoxStyle" TargetType="TextBox">
            <Setter Property="Width" Value="120" />
            <Setter Property="Margin" Value="5" />
            <Setter Property="FontSize" Value="16" />
            <Setter Property="FontWeight" Value="Regular" />
            <Setter Property="Foreground" Value="Black" />
            <Setter Property="VerticalContentAlignment" Value="Center" />
            <!--<Setter Property="cal:Message.Attach" Value="[Event LostFocus] = [Action TextBox_LostFocus($source, $this)]" />-->
            <Style.Triggers>
                <Trigger Property="Name" Value="RowCount">
                    <Setter Property="Width" Value="30"/>
                </Trigger>
                <Trigger Property="Name" Value="ColumnCount">
                    <Setter Property="Width" Value="30"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Grid x:Name="MainGrid"
        gh:GridHelpers.RowCount="2"
        gh:GridHelpers.ColumnCount="1"
        gh:GridHelpers.StarRows="1"
        gh:GridHelpers.StarColumns="*">
        <StackPanel Orientation="Vertical" Grid.Row="0" >
            <Separator />
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
                <Label Content="RowCount:" Style="{StaticResource LabelStyle}" />
                <TextBox x:Name="RowCount" Style="{StaticResource TextBoxStyle}" />
                <Label Content="StarRows:" Style="{StaticResource LabelStyle}" />
                <TextBox x:Name="StarRows" Style="{StaticResource TextBoxStyle}" />
                <Label Content="PixelRows:" Style="{StaticResource LabelStyle}" />
                <TextBox x:Name="PixelRows" Style="{StaticResource TextBoxStyle}" />
                <Button x:Name="Valider" Content="Valider la saisie" Width="100" 
                        Margin="30,20,0,-20" />
            </StackPanel>
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
                <Label Content="ColumnCount:" Style="{StaticResource LabelStyle}" />
                <TextBox x:Name="ColumnCount" Style="{StaticResource TextBoxStyle}" />
                <Label Content="StarColumns:" Style="{StaticResource LabelStyle}" />
                <TextBox x:Name="StarColumns" Style="{StaticResource TextBoxStyle}" />
                <Label Content="PixelColumns:" Style="{StaticResource LabelStyle}" />
                <TextBox x:Name="PixelColumns" Style="{StaticResource TextBoxStyle}" />
                <Button Width="100" Visibility="Hidden" Height="0" Margin="30,0,0,0"/>
            </StackPanel>
            <Separator />
        </StackPanel>
        <ContentControl x:Name="myGridViewModel" Grid.Row="1"/>
    </Grid>
</Window>

the code of Main2ViewModel:

using Caliburn.Micro;
using Ninject;
using Ninject.Syntax;
using System.Collections.Generic;

namespace GridHelpersSample
{
    public class Main2ViewModel : Screen
    {
        private readonly IResolutionRoot _resolutionRoot;
        private readonly IKernel _kernel;
        public BindableCollection<ButtonViewModel> ButtonViewModels { get; set; }
        public MyGridViewModel myGridViewModel { get; set; }
        public Main2ViewModel(IResolutionRoot resolutionRoot, IKernel kernel) 
        {
            _resolutionRoot = resolutionRoot;
            _kernel = kernel;
            RowCount = "3";
            ColumnCount = "3";
            StarRows = "*";
            StarColumns = "*";
            PixelRows = "";
            PixelColumns = "";

            AddNewContent();
        }

        #region datas for defining grid
        private string rowCount;
        public string RowCount
        {
            get { return rowCount; }
            set
            {
                rowCount = value;
                NotifyOfPropertyChange(() => RowCount);
            }
        }
        private string columnCount;
        public string ColumnCount
        {
            get { return columnCount; }
            set
            {
                columnCount = value;
                NotifyOfPropertyChange(() => ColumnCount);
            }
        }
        private string starRows;
        public string StarRows
        {
            get { return starRows; }
            set
            {
                starRows = value;
                NotifyOfPropertyChange(() => StarRows);
            }
        }
        private string starColumns;
        public string StarColumns
        {
            get { return starColumns; }
            set
            {
                starColumns = value;
                NotifyOfPropertyChange(() => StarColumns);
            }
        }

        private string pixelRows;
        public string PixelRows
        {
            get { return pixelRows; }
            set
            {
                pixelRows = value;
                NotifyOfPropertyChange(() => PixelRows);
            }
        }
        private string pixelColumns;

        public string PixelColumns
        {
            get { return pixelColumns; }
            set
            {
                pixelColumns = value;
                NotifyOfPropertyChange(() => PixelColumns);
            }
        }
        #endregion

        public void Valider()
        {            
            AddNewContent();
        }
        public List<ButtonViewModel> CreateButton()
        {
            var myView = _resolutionRoot.Get<Main2View>();
            var t = myView.MainGrid;
            var list = new List<ButtonViewModel>();
            for (int i = 0; i < int.Parse(RowCount); i++)
            {
                for (int j = 0; j < int.Parse(ColumnCount); j++)
                {
                    var button = new ButtonViewModel
                    {
                        Content = $"R{i} C{j}",
                        GridRow = i,
                        GridColumn = j
                    };

                    list.Add(button);
                }
            }
            return list;
        }

        private void AddNewContent()
        {
            ButtonViewModels = new BindableCollection<ButtonViewModel>(CreateButton());
            myGridViewModel = new MyGridViewModel(this);
        }
    }
}

All is fine during the intialization of view/viewmodel, but the problem begins when i change the value of RowCount or RowColumn and i click on button.

i am waiting the initializatoon of new view , but its not the case, the view MyGridView is not reloaded..

this is the boostrapper:

using Caliburn.Micro;
using Ninject;
using System;
using System.Collections.Generic;
using System.Windows;

namespace GridHelpersSample
{
    public class Bootstrapper : BootstrapperBase
    {
        private IKernel kernel;
        public Bootstrapper()
        {
            Initialize();
        }

        protected override void Configure()
        {
            kernel = new StandardKernel();
            kernel.Bind<IWindowManager>().To<WindowManager>().InSingletonScope();
            kernel.Bind<IEventAggregator>().To<EventAggregator>().InSingletonScope();
            kernel.Bind<MyGridViewModel>().ToSelf().InTransientScope();
            //kernel.Bind<MyGridViewModel>().ToSelf().InSingletonScope();
            var bindings0 = kernel.GetBindings(typeof(MyGridViewModel));
            //var bindings1 = kernel.GetBindings(typeof(MainViewModel));
        }
        protected override async void OnStartup(object sender, StartupEventArgs e)
        {
            await DisplayRootViewForAsync<Main2ViewModel>();
        }
        protected override object GetInstance(Type service, string key)
        {
            return kernel.Get(service);
        }
        protected override IEnumerable<object> GetAllInstances(Type service)
        {
            return kernel.GetAll(service);
        }
        protected override void BuildUp(object instance)
        {
            kernel.Inject(instance);
        }
    }
}

I dont arrive to relaunch a new view even i use InTransientScope() which indicates to use a new instance of view.. but maybe i am missing something..

In anyways it seems the applicationto have a problem, because when i close the main view, the debugger doesnt finish and i have to stop the application with the red button in VS2022

i have tested with the use of Unbind but no luck

    private void AddNewContent()
    {
        ButtonViewModels = new BindableCollection<ButtonViewModel>(CreateButton());
        if (myGridViewModel == null)
        {
            myGridViewModel = new MyGridViewModel(this);
            return;
        }
        //_kernel.Unbind<MyGridViewModel>();
        _kernel.Unbind<MyGridView>();
        //_kernel.Bind<MyGridViewModel>().To<MyGridViewModel>();
        _kernel.Bind<MyGridView>().To<MyGridView>();
        myGridViewModel = new MyGridViewModel(this);
    }

Solution

  • Ok i have found the solution:

    In Main2ViewModel, just refactor my variable myGridViewModel with NotifyOfPropertyChange():

        private MyGridViewModel _myGridViewModel;
        public MyGridViewModel myGridViewModel
        {
            get { return _myGridViewModel; }
            set
            {
                _myGridViewModel = value;
                NotifyOfPropertyChange(() => myGridViewModel);
            }
        }
    

    so i can write the method AddNewContent like this:

        private void AddNewContent()
        {
            ButtonViewModels = new BindableCollection<ButtonViewModel>(CreateButton());
            myGridViewModel = new MyGridViewModel(this);
        }
    

    so in bootstrapper.cs file, no need to Bind MyGridViewModel :

        protected override void Configure()
        {
            kernel = new StandardKernel();
            kernel.Bind<IWindowManager>().To<WindowManager>().InSingletonScope();
            kernel.Bind<IEventAggregator>().To<EventAggregator>().InSingletonScope();
        }
    

    And no more problem when i quit application