DLL 项目和 GUI 项目 - Caliburn Micro:模型/视图 VM 问题

DLL Project and GUI Project - Caliburn Micro: Model / View VM Questions

我确定这个问题之前应该有人问过,但我找不到我要找的东西;

考虑以下几点:

- Solution
-- Class Library Project [Caliburn.Micro] Referenced
--- [Models] Folder
---- LogEntryModel.cs
--- [ViewModels] Folder
---- LogEntryViewModel.cs
---- ShellViewModel.cs
-- WPF GUI Project [Caliburn.Micro] Referenced
--- [Views] Folder
---- LogEntryView.xaml
---- ShellView.xaml

所以,我有 2 个项目,一个有模型,一个有视图模型和视图; 这是我的引导程序:

    public class AppBootstrapper : BootstrapperBase
    {
        private CompositionContainer container;

        public AppBootstrapper()
        {
            Initialize();
        }

        protected override void BuildUp(object instance)
        {
            this.container.SatisfyImportsOnce(instance);
        }

        /// <summary>
        ///     By default, we are configured to use MEF
        /// </summary>
        protected override void Configure()
        {



            var config = new TypeMappingConfiguration
            {
                DefaultSubNamespaceForViews = "WPFGUI.Views",
                DefaultSubNamespaceForViewModels = "ClassLibrary.ViewModels"
            };
            ViewLocator.ConfigureTypeMappings(config);
            ViewModelLocator.ConfigureTypeMappings(config);

            var catalog =
                new AggregateCatalog(
                    AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>());

            this.container = new CompositionContainer(catalog);

            var batch = new CompositionBatch();

            batch.AddExportedValue<IWindowManager>(new WindowManager());
            batch.AddExportedValue<IEventAggregator>(new EventAggregator());
            batch.AddExportedValue(this.container);
            batch.AddExportedValue(catalog);

            this.container.Compose(batch);
        }

        protected override IEnumerable<object> GetAllInstances(Type serviceType)
        {
            return this.container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
        }

        protected override object GetInstance(Type serviceType, string key)
        {
            var contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
            var exports = this.container.GetExportedValues<object>(contract);

            if (exports.Any())
            {
                return exports.First();
            }

            throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
        }

        protected override void OnStartup(object sender, StartupEventArgs e)
        {
            var startupTasks =
                GetAllInstances(typeof(StartupTask))
                .Cast<ExportedDelegate>()
                .Select(exportedDelegate => (StartupTask)exportedDelegate.CreateDelegate(typeof(StartupTask)));

            startupTasks.Apply(s => s());

            DisplayRootViewFor<IShell>();
        }

    }

现在,当我尝试使用绑定到列表框的 LogEntryModel 时,我收到 Cannot find view for ClassLibrary.Models.LogEntryModel.


[编辑] 我更改了我的文件夹结构,我的 VM 和模型现在组合在一起, 我更新了 bootstrapper.cs:

            var config = new TypeMappingConfiguration
            {
                DefaultSubNamespaceForViews = "WPFGUI.Views",
                DefaultSubNamespaceForViewModels = "ClassLibrary.ViewModels"
            };
            ViewLocator.ConfigureTypeMappings(config);
            ViewModelLocator.ConfigureTypeMappings(config);

ShellViewModel 仍然有效;但 LogEntryModel 仍然显示:

Cannot find view for ClassLibrary.Models.LogEntryModel.

[编辑 2] 日志条目模型:

public class LogEntryModel
    {
        //GUID
        public Guid GUID { get; set; }
        //The log message string
        public string Message { get; set; }
        //The module that created the logentry (see enums Module for options)
        public int Module { get; set; }
        //The urgency (used for coloring: 0 = black (normal), 1 = red (error), 2 = cyan (info)
        public int Severity { get; set; }
        //User that triggered the logentry
        public string UserID { get; set; }
        //The datetime of the logentry
        public DateTime LogEntryDateTime { get; set; }

    }

LogEntryViewModel:

    public class LogEntryViewModel
    {
//This is for testing purposes only (I'd expect "Hello World" everywhere
        public String Message { get; set; } = "Hello World";
    }

LogEntryView.xaml:

<UserControl x:Class="ServicesUI_WPF.Views.LogEntryView"
             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:local="clr-namespace:WPFGUI.Views"
             DataContext="ClassLibrary.ViewModels.LogEntryViewModel"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid Background="Red">

    </Grid>
</UserControl>

如果您希望向 link 视图模型和视图添加新规则,您必须使用 ViewLocator:

一些样本:

//link xxxxViewModel with xxxxViewX   
ViewLocator.NameTransformer.AddRule(@"ViewModel", @"ViewX");

//case when view and viewmodel are not in same library       
//link Cockpit.Core.Plugins.Plugins.Properties.xxxViewModel with
//Cockpit.General.Properties.Views.xxxView
ViewLocator.AddNamespaceMapping("Cockpit.Core.Plugins.Plugins.Properties", "Cockpit.General.Properties.Views");

我成功了;在查看了一些 Youtube 链接后,我发现我遗漏的东西有规律可循;

LogEntryModel:

namespace ServicesTools.Models
{
    public class LogEntryModel
    {
        //GUID
        public Guid GUID { get; set; }
        //The log message string
        public string Message { get; set; }
        //The module that created the logentry (see enums Module for options)
        public int Module { get; set; }
        //The urgency (used for coloring: 0 = black (normal), 1 = red (error), 2 = cyan (info)
        public int Severity { get; set; }
        //User that triggered the logentry
        public string UserID { get; set; }
        //The datetime of the logentry
        public DateTime LogEntryDateTime { get; set; }


    }
}

LogEntryViewModel

namespace ServicesTools.ViewModels
{
    public class LogEntryViewModel : Screen
    {
        //Create a property that will keep all data from LogEntryModel in this BindableCollecton
        private BindableCollection<LogEntryModel> _logEntries;
        public BindableCollection<LogEntryModel> LogEntries
        {
            get { return _logEntries; }
            set { _logEntries = value;
                NotifyOfPropertyChange(() => LogEntries);
            }
        }

        //On Instantiate; collect all the LogEntries from the datasource
        public LogEntryViewModel()
        {
            LogEntries = new BindableCollection<LogEntryModel>(GlobalConfig.Connection.GetLogEntries());
        }

    }
}

我没有 LogEntryView,因为它在 DebugView.xaml:

中被调用
<UserControl x:Class="ServicesTools.Views.DebugView" 
             xmlns:local="clr-namespace:ServicesTools.Views" 
             xmlns:vms="clr-namespace:ServicesTools.ViewModels;assembly=ServicesLibrary"
             xmlns:Controls="http://metro.mahapps.com/winfx/xaml/controls" xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
             xmlns:convert="clr-namespace:ServicesUI_WPF.Converters"
             >
        <Grid Grid.Row="2">
            <Border Padding="5" BorderThickness="1" BorderBrush="{StaticResource CompanyCore1SolidBrush}">
                <DockPanel>
                    <ScrollViewer CanContentScroll="True" VerticalScrollBarVisibility="Visible">
                        <ItemsControl ItemsSource="{Binding LogEntries}">
                            <ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <StackPanel Orientation="Horizontal" Background="{Binding Severity, Converter={StaticResource SeverityToColorConverter}}" >
                                        <TextBlock FontFamily="Consolas">
                                            <TextBlock.Text>
                                                <MultiBinding StringFormat="{}[{0:dd-MM-yy HH:mm:ss}] {1}({2}), {3}">
                                                    <Binding Path="LogEntryDateTime"/>
                                                    <Binding Path="Module" Converter="{StaticResource ModuleToEnumConverter}" />
                                                    <Binding Path="Module" />
                                                    <Binding Path="Message" />
                                                </MultiBinding>
                                            </TextBlock.Text>
                                        </TextBlock>
                                    </StackPanel>
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
                        </ItemsControl>
                    </ScrollViewer>
                </DockPanel>
            </Border>
        </Grid>
    </Grid>
</UserControl>

我几乎让它在 ListBox 中工作,但我缺少 "DisplayMemberPath" 初始值; 我在 LogEntryViewModel 中还缺少的是创建(并填充)属性 BindableCollection<LogEntries> 的实际代码,实际上没有任何东西可以绑定。

我必须编辑的最后一件事是 DebugViewModel:

namespace ServicesTools.ViewModels
{
    public class DebugViewModel : Screen
    {

        //Create an ObservableCollection property that will keep all LogEntries
        private ObservableCollection<LogEntryModel> _logEntries;
        public ObservableCollection<LogEntryModel> LogEntries
        {
            get { return _logEntries; }
            set 
            { 
              _logEntries = value;
              NotifyOfPropertyChange(() => LogEntries) ;
            }
        }

        //On Instantiate; call GetLogEntries
        public DebugViewModel()
        {
            GetLogEntries();
        }

        /// <summary>
        /// Call a stored procedure to reset TrackAndTrace
        /// Log an entry into the database with severity High on click
        /// Log an entry into the database with severity Debug on finish
        /// Refresh LogEntries
        /// </summary>
        public void TrackAndTraceReset()
        {
            //Log an Entry into the table
            HelperFunctions.CreateLogEntry(logEntryMessage:$"User Clicked the Track & Trace reset button", Enums.Severity.High, Enums.Module.Debug);

            //TODO something [...]

            HelperFunctions.CreateLogEntry(logEntryMessage: $"And it Worked!", Enums.Severity.Debug, Enums.Module.Debug);

            //Refresh the list of LogEntries
            GetLogEntries();

        }

        /// <summary>
        /// Clear the current property LogEntries (if not Null), 
        /// then instantiate a new LogEntryViewModel and insert LogEntryViewModel.LogEntries property into LogEntries
        /// </summary>
        /// <returns>ObservableCollection<LogEntryModel></returns>
        private ObservableCollection<LogEntryModel> GetLogEntries() {
            //If LogEntries is null, do nothing; otherwise Clear it
            LogEntries?.Clear();

            //Instantiate new LogEntryViewModel
            LogEntryViewModel _lEVM  = new LogEntryViewModel();

            //Insert Property into LogEntries property
            LogEntries = _lEVM.LogEntries;

            //TODO: if filter exists, filter the list

            return LogEntries;            
        }

    }
}

我仍在测试每次更新日志条目时是否真的需要实例化一个新的 LogEntryViewModel(),但这是更新列表中所有条目的最简单方法。