DLL Project и GUI Project - Caliburn Micro: вопросы модели / просмотра ВМ - PullRequest
1 голос
/ 25 мая 2020

Я уверен, что этот вопрос должен был задаваться раньше, но я не могу найти именно то, что ищу;

Учтите следующее:

- 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 проекта, один с моделями и один с ViewModels и Views; Это мой загрузчик:

    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.

  • Я предполагаю, что мне нужно «сообщить» Caliburn для поиска моделей в моем проекте библиотеки классов (как)
  • Следует ли мне ссылаться на Caliburn.Micro в моей библиотеке классов? (Поскольку это GUI вещь?)
  • Где должны быть мои ViewModels, в ClassLibrary или в проекте GUI?

[edit] Я изменил Структура папок, моя виртуальная машина и модели теперь сгруппированы вместе, я обновил 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.

[edit 2] LogEntryModel:

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>

Ответы [ 2 ]

0 голосов
/ 27 мая 2020

У меня все заработало; просмотрев некоторые ссылки на 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 () при каждом обновлении записей журнала, но это был самый простой способ обновить все записи в списке.

0 голосов
/ 25 мая 2020

если вы хотите добавить новые правила для связывания viewModel и view, вы должны использовать 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");
...