向网格MVVM Caliburn添加更多用户控件

时间:2019-04-26 19:08:34

标签: c# wpf xaml mvvm caliburn.micro

我正在建造一个座舱,我拥有许多用户控制权(不同类型的开关),我试图将它们集成到一个窗口中

我正在使用caliburn和Ninject并尝试保留MVVM。

所以我有一个问题,我必须将不同的开关动态地集成到窗口的网格中,我不知道我是否可以保留MVVM

所以在我的解决方案中,我使用网格的名称将不同的UserControl放置在不同的位置,并且破坏了MVVM

我如何使用MVVM做到这一点?我已经读过我可以使用ContentControl来绑定列表中的不同ViewModel,但是我不知道该怎么做。欢迎一些帮助

Bootstrapper.cs:

using Caliburn.Micro;
using Ninject;
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using TestNinjectCaliburn.ViewModels;
using EventAggregator = TestNinjectCaliburn.Events.EventAggregator;
using IEventAggregator = TestNinjectCaliburn.Events.IEventAggregator;

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

        protected override void Configure()
        {
            kernel = new StandardKernel();

            kernel.Bind<IEventAggregator>().To<EventAggregator>().InSingletonScope();
            kernel.Bind<IWindowManager>().To<WindowManager>().InSingletonScope();
            kernel.Bind<MainWindowViewModel>().ToSelf().InSingletonScope();

            MessageBinder.SpecialValues.Add("$pressedkey", (context) =>
            {
                // NOTE: IMPORTANT - you MUST add the dictionary key as lowercase as CM
                // does a ToLower on the param string you add in the action message, in fact ideally
                // all your param messages should be lowercase just in case. I don't really like this
                // behaviour but that's how it is!
                var keyArgs = context.EventArgs as KeyEventArgs;

                if (keyArgs != null)
                    return keyArgs.Key;

                return null;
            });
        }

        protected override void OnStartup(object sender, StartupEventArgs e)
        {
            DisplayRootViewFor<MainWindowViewModel>();
        }
        protected override object GetInstance(Type service, string key)
        {
            return kernel.Get(service);
        }

        protected override IEnumerable<object> GetAllInstances(Type service)
        {
            return kernel.GetAll(service);
        }
    }
}

MainWindowViewModel.cs,它调用SecondViewModel.cs

using Caliburn.Micro;
using IEventAggregator = TestNinjectCaliburn.Events.IEventAggregator;
using Ninject.Syntax;

namespace TestNinjectCaliburn.ViewModels
{
    public class MainWindowViewModel
    {
        private readonly IWindowManager windowmanager;
        private readonly SecondViewModel[] secondviewmodel;
        private readonly IEventAggregator eventAggregator;

        public MainWindowViewModel(IWindowManager windowmanager, IEventAggregator eventAggregator, IResolutionRoot resolutionRoot, SecondViewModel secondviewmodel)
        {
            this.eventAggregator = eventAggregator;
            this.windowmanager = windowmanager;
            this.secondviewmodel = new SecondViewModel[1];
            this.secondviewmodel[0] = new SecondViewModel(eventAggregator, resolutionRoot);
        }

        public void Launch()
        {
            windowmanager.ShowWindow(secondviewmodel[0]);
        }

    }
}

SecondViewModel.cs:

using Caliburn.Micro;
using Ninject;
using Ninject.Parameters;
using Ninject.Syntax;
using System;
using System.Linq;
using System.Reflection;
using System.Windows.Controls;
using TestNinjectCaliburn.Views;
using IEventAggregator = TestNinjectCaliburn.Events.IEventAggregator;

namespace TestNinjectCaliburn.ViewModels
{
    public class SecondViewModel:Screen
    {
        private readonly IEventAggregator eventAggregator;
        private readonly IResolutionRoot resolutionRoot;
        private UserControl usercontrol;

        public SecondView secondView;
        public SecondViewModel(IEventAggregator eventAggregator, IResolutionRoot resolutionRoot)
        {
            this.eventAggregator = eventAggregator;
            this.resolutionRoot = resolutionRoot;
        }


        protected override void OnViewReady(object view)
        {
            secondView = view as SecondView;
        }

        public Type[] Typelist;
        //here i break MVVM ************************
        protected override void OnViewAttached(object secondview, object context)
        {
            Element[] elts = {
                new Element()
                {
                    viewmodel = Type.GetType("TestNinjectCaliburn.Gauges." + "SwitchOffOn_ViewModel"),
                    Top = 100,
                    Left = 100
                },
                new Element()
                {
                    viewmodel = Type.GetType("TestNinjectCaliburn.Gauges." + "SwitchOffOn_ViewModel"),
                    Top = 100,
                    Left = 200
                },
                new Element()
                {
                    viewmodel = Type.GetType("TestNinjectCaliburn.Gauges." + "SwitchOffOn_ViewModel"),
                    Top = 100,
                    Left = 300
                },
                new Element()
                {
                    viewmodel = Type.GetType("TestNinjectCaliburn.Gauges." + "SwitchOffOn_ViewModel"),
                    Top = 100,
                    Left = 400
                },
            };

            var MainGrid = (secondview as SecondView).MainGrid;

            for (int i = 0; i < elts.Length; i++)
            {
                Ninject.Parameters.Parameter[] param = {
                        new ConstructorArgument("left", elts[i].Left , true),
                        new ConstructorArgument("top", elts[i].Top, true)
                };

                // Replace the Activator.CreateInstance     
                var viewmodel = resolutionRoot.TryGet(elts[i].viewmodel, param);
                var view = ViewLocator.LocateForModel(viewmodel, null, null);

                ViewModelBinder.Bind(viewmodel, view, null);
                MainGrid.Children.Add(view);
            }

        }
    }

    public class Element
    {
        public Type viewmodel;
        public double Top;
        public double Left;
    }
}

我要设置的一个用户控件的示例:

SwitchOffOn_ViewModel.cs

using System;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using IEventAggregator = TestNinjectCaliburn.Events.IEventAggregator;

namespace TestNinjectCaliburn.Gauges
{
    public class SwitchOffOn_ViewModel : TemplateSwitch
    {
        private readonly IEventAggregator eventAggregator;

        public SwitchOffOn_ViewModel(IEventAggregator eventAggregator, double left, double top)
        {
            this.eventAggregator = eventAggregator;
            this.eventAggregator.Subscribe(this);
            //Tag = tag;

            var folder = Environment.CurrentDirectory + "\\Images\\Elements\\";
            SwitchImage = new string[] { folder + "switch_n0.png", folder + "switch_n2.png" };
            NbImages = SwitchImage.Length;
            SwitchIndex = 0;
            //double left = 200, top = 0;

            UCLeft = left;
            UCTop = top;

            InitialSize = 40;
            scaleX = InitialSize / (new BitmapImage(new Uri(SwitchImage[0])).PixelWidth / 2d);
            angle = 0d;

        }

        #region Mouse Events
        public void MouseEnter(MouseEventArgs e)
        {
            ToolTip = (e.OriginalSource as UserControl).Margin.ToString();
        }
        #endregion
    }
}

SwitchOffOn_View.xaml:

<UserControl 
    x:Class="TestNinjectCaliburn.Gauges.SwitchOffOn_View"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:cal="http://www.caliburnproject.org"
    mc:Ignorable="d" d:DesignHeight="150" d:DesignWidth="70"
    x:Name="usercontrol" Height="150" Width="70" RenderTransformOrigin="0.5,0.5" Tag="{Binding Tag, Mode=OneTime}" ToolTip="{Binding ToolTip}"
    VerticalAlignment = "Top" HorizontalAlignment = "Left" ClipToBounds="True"
    cal:Message.Attach="[Event MouseEnter] = [Action MouseEnter($eventArgs)]">

    <UserControl.Margin>
        <MultiBinding Converter="{StaticResource MyMultiConverterMargin}">
            <Binding Path="UCLeft" UpdateSourceTrigger="PropertyChanged"></Binding>
            <Binding Path="UCTop" UpdateSourceTrigger="PropertyChanged"></Binding>
        </MultiBinding>
    </UserControl.Margin>

    <UserControl.LayoutTransform>
        <TransformGroup>
            <RotateTransform x:Name="rotation" Angle="{Binding angle}"/>
            <ScaleTransform x:Name="scale" ScaleX="{Binding scaleX}" ScaleY="{Binding ElementName=scale, Path=ScaleX}"/>
        </TransformGroup>
    </UserControl.LayoutTransform>

    <Grid RenderTransformOrigin="0.5,0.5">

        <Image x:Name="SwitchUp" Source="{Binding SwitchImage[1], Mode=OneTime}"
               Width="{Binding ElementName=usercontrol, Path=Width}"
               Height="{Binding ElementName=usercontrol, Path=Height}"
               HorizontalAlignment="Center" VerticalAlignment="Center" >
            <Image.Style>
                <Style TargetType="{x:Type Image}">
                    <Setter Property="Visibility" Value="Hidden" />
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding SwitchIndex}" Value="1">
                            <Setter Property="Visibility" Value="Visible" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Image.Style>
        </Image>

        <Image x:Name="SwitchDown" Source="{Binding SwitchImage[0], Mode=OneTime}"
               Width="{Binding ElementName=usercontrol, Path=Width}"
               Height="{Binding ElementName=usercontrol, Path=Height}"
               HorizontalAlignment="Center" VerticalAlignment="Center" >
            <Image.Style>
                <Style TargetType="{x:Type Image}">
                    <Setter Property="Visibility" Value="Hidden" />
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding SwitchIndex}" Value="0">
                            <Setter Property="Visibility" Value="Visible" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Image.Style>
        </Image>

        <Rectangle x:Name="UpperRec" Visibility="Visible" Margin="5,5,0,0"
                   cal:Message.Attach="[Event MouseLeftButtonDown] = [Action MouseLeftButtonDown('true')]"
                   Width="{Binding ElementName=usercontrol, Path=Width, Converter={StaticResource MyConverterSize}, ConverterParameter=1 10}"
                   Height="{Binding ElementName=usercontrol, Path=Height, Converter={StaticResource MyConverterSize}, ConverterParameter=2 10}"
                   HorizontalAlignment="Left" VerticalAlignment="Top" Style="{StaticResource IsModeEdit}" >
        </Rectangle>

        <Rectangle x:Name="LowerRec" Visibility="Visible" Margin="0 0 5 5"
                   cal:Message.Attach="[Event MouseLeftButtonDown] = [Action MouseLeftButtonDown('false')]"
                   Width="{Binding ElementName=UpperRec, Path=Width}"
                   Height="{Binding ElementName=UpperRec, Path=Height}"
                   HorizontalAlignment="Right" VerticalAlignment="Bottom" Style="{StaticResource IsModeEdit}" >
        </Rectangle>

        <Rectangle x:Name="DesignFrame"
                   Visibility="{Binding Frame, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource BoolToVisConverter}}"
                   Width="{Binding ElementName=usercontrol, Path=Width, UpdateSourceTrigger=PropertyChanged}"
                   Height="{Binding ElementName=usercontrol, Path=Height, UpdateSourceTrigger=PropertyChanged}"
                   Margin="0,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Style="{StaticResource IsModeSelected}" >
        </Rectangle>

    </Grid>
</UserControl>

MainWindowView.xaml:

<Window x:Class="TestNinjectCaliburn.Views.MainWindowView"
        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"
        mc:Ignorable="d"
        Title="MainWindowView" Height="250" Width="400">
    <Grid>
        <Button x:Name = "Launch" Content ="Launch Test Ninject and Caliburn" HorizontalAlignment="Left" Margin="115,25,0,0" VerticalAlignment="Top" Width="209"/>
    </Grid>
</Window>

SecondView.xaml:

<Window x:Class="TestNinjectCaliburn.Views.SecondView"
        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:local="clr-namespace:TestNinjectCaliburn.Views"
        mc:Ignorable="d"
        Title="SecondView" Height="400" Width="800">
    <Grid x:Name="MainGrid">

    </Grid>
</Window>

我得到的结果(保留MVVM):

enter image description here


按照您的答案

我已经为我的所有用户控件创建了BaseViewModel

namespace TestNinjectCaliburn.Gauges
{
    public abstract class BaseViewModel:TemplateSwitch
    {
    }
}

我已将BaseViewModel添加到我的ViewModels

public class SwitchOn_Off_On_ViewModel : BaseViewModel

public class SwitchOffOn_ViewModel : BaseViewModel

我已经在SecondViewModel.cs中添加了

ViewModel集合的定义:

    private ObservableCollection<BaseViewModel> _myCockpitViewModels = new ObservableCollection<BaseViewModel>();
    public ObservableCollection<BaseViewModel> MyCockpitViewModels
    {
        get { return _myCockpitViewModels; }
        set
        {
            _myCockpitViewModels = value;
            NotifyOfPropertyChange(() => MyCockpitViewModels);
        }
    }

我已经加载了列表:

        for (int i = 0; i < elts.Length; i++)
        {
            //if (!typelist[i].ToString().Replace("_", "").Contains("Switch")) continue;
            Ninject.Parameters.Parameter[] param = {
                    new ConstructorArgument("left", elts[i].Left , true),
                    new ConstructorArgument("top", elts[i].Top, true)
            };

            // Replace the Activator.CreateInstance                       new Ninject.Parameters.Parameter[0]
            var viewmodel = resolutionRoot.TryGet(elts[i].viewmodel, param);
            var view = ViewLocator.LocateForModel(viewmodel, null, null);

            ViewModelBinder.Bind(viewmodel, view, null);
            MyCockpitViewModels.Add((BaseViewModel)viewmodel);

现在我在SecondView.xaml中有它:

<Viewbox x:Name="MainGrid" >

    <ItemsControl ItemsSource="{Binding MyCockpitViewModels}" Width="1725" Height="800">

        <ItemsControl.Resources>
            <DataTemplate DataType="{x:Type vm:SwitchOffOn_ViewModel}">
                <vm:SwitchOffOn_View />
            </DataTemplate>
            <DataTemplate DataType="{x:Type vm:SwitchOn_Off_On_ViewModel}">
                <vm:SwitchOn_Off_On_View />
            </DataTemplate>
        </ItemsControl.Resources>

        <!-- Replace panel with a canvas -->
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>

        <!-- Set position of each element in the canvas -->
        <ItemsControl.ItemContainerStyle>
            <Style TargetType="{x:Type ContentPresenter}">
                <Setter Property="Canvas.Left" Value="{Binding UCLeft}" />
                <Setter Property="Canvas.Top" Value="{Binding UCTop}" />
            </Style>
        </ItemsControl.ItemContainerStyle>

    </ItemsControl>
</Viewbox>

如果我用以下内容替换DataTemplate的定义,那就哼哼

    <ItemsControl ItemsSource="{Binding MyCockpitViewModels}" Width="1725" Height="800">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <ContentControl cal:View.Model="{Binding .}" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>

我不需要在datatemplate中定义每个控件,caliburn似乎可以完成工作。

1 个答案:

答案 0 :(得分:1)

网格是此处使用的错误面板。如果您正在设计驾驶舱,那么您已经知道元素的位置,因此不需要WPF即可为您进行布局。嗯,使用画布。您可能希望驾驶舱随视图缩放,因此请根据自己选择的任意单位(例如1000x1000)为其设置大小,然后将整个内容包装在Viewbox中。

关于元素的实际呈现,您正在屏幕上呈现控件列表,每当这样做时,您的第一个本能就是使用ItemsControl。因此,您将从为座舱元素提供某种类型的基本视图模型开始,然后将它们全部放入列表中。要显示它们,请使用ItemsControl,将ItemsSource绑定到列表。您想在Canvas上显示所有控件,因此请模板化ItemControl的ItemsPanel。最后,您需要指定每个元素在屏幕上的位置,因此给您的基本视图模型类X / Y属性并绑定到ItemControl的ItemContainerStyle中。将所有内容放在一起,您会得到:

<Viewbox>
    <ItemsControl ItemsSource="{Binding MyCockpitViewModels}" Width="1000" Height="1000">

        <!-- Replace panel with a canvas -->
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>

        <!-- Set position of each element in the canvas -->
        <ItemsControl.ItemContainerStyle>
            <Style TargetType="{x:Type ContentPresenter}">
                <Setter Property="Canvas.Left" Value="{Binding X}" />
                <Setter Property="Canvas.Top" Value="{Binding Y}" />
            </Style>
        </ItemsControl.ItemContainerStyle>

    </ItemsControl>
</Viewbox>

如果运行此视图,则将看到视图模型的名称显示在其各自的画布位置,因此剩下要做的就是以某种方式告诉WPF为每个元素而不是文本绘制哪个控件。这是通过DataTemplates完成的,您可以将其放置在可视化树的任何位置,例如app.xaml的ResourceDictionary,或用于MainWindow的资源,或者更好的是在ItemsControl的Resources块中:

<DataTemplate DataType="{vm:SwitchOffOn_ViewModel}">
    <controls:SwitchOffOn_View />
</DataTemplate>

自从我使用Micro以来已经有一段时间了,它实际上可能为您完成了DataTemplating,但是如果不这样做,请按照我在此处为每个控件所示的那样明确声明它,您将获得一个完全渲染的驾驶舱