Добавление большего количества Usercontrol в сетку MVVM Caliburn - PullRequest
0 голосов
/ 26 апреля 2019

Я строю кабину, у меня много пользовательского контроля (разные типы переключателей), и я пытаюсь интегрировать их в окно

Я использую caliburn и Ninject и пытаюсь сохранить MVVM.

Итак, у меня есть проблема, я должен динамически интегрировать другой коммутатор в сетку окна, и я не знаю, смогу ли я сохранить MVVM

Так что в моем решении я использую имя сеткичтобы разместить разные пользовательские элементы управления на разных позициях, и я сломал 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 Ответ

1 голос
/ 27 апреля 2019

Сетка - это неправильная панель для использования здесь. Если вы проектируете кабину, то вы уже знаете расположение элементов, поэтому вам не нужен WPF, чтобы сделать макет для вас. Ergo, используйте Canvas. Вы, вероятно, захотите, чтобы ваша кабина масштабировалась с видом, поэтому задайте ей размер на основе произвольной единицы измерения по вашему выбору (например, 1000x1000) и оберните все это в Viewbox.

Что касается фактического рендеринга ваших элементов, вы отображаете список элементов управления на экране, когда вы делаете это, вашим первым инстинктом должно быть использование ItemsControl. Таким образом, вы начнете с некоторой модели базового вида для элементов кабины и поместите их все в список. Для их отображения вы используете ItemsControl, привязывая ItemsSource к вашему списку. Вы хотите отобразить все свои элементы управления на Canvas, поэтому создайте шаблон ItemsPanel ItemControl. Наконец, вам нужно указать расположение каждого элемента на экране, поэтому задайте свойства X / Y класса модели базового вида и свяжите их со свойствами ItemContainerStyle ItemControl. Соберите все это вместе, и вы получите это:

<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, которые вы можете разместить в любом месте визуального дерева, например, ResourceDictionary в app.xaml, или для вашего MainWindow, или еще лучше в блоке ресурсов ItemsControl:

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

Прошло немного времени с тех пор, как я использовал Micro, он может на самом деле сделать этот DataTemplating для вас, но если нет, то объявите его явно, как я показал здесь для каждого из ваших элементов управления, и у вас будет полностью визуализированная кабина .

...