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.
- 我想我需要 'tell' Caliburn 在我的 Class 图书馆项目中寻找模型(如何)
- 我应该在我的 Class 图书馆中引用 Caliburn.Micro 吗? (因为它是 GUI 的东西?)
- 我的 ViewModel 应该在哪里,在 Class 库或 GUI 项目中?
[编辑]
我更改了我的文件夹结构,我的 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(),但这是更新列表中所有条目的最简单方法。
我确定这个问题之前应该有人问过,但我找不到我要找的东西;
考虑以下几点:
- 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.
- 我想我需要 'tell' Caliburn 在我的 Class 图书馆项目中寻找模型(如何)
- 我应该在我的 Class 图书馆中引用 Caliburn.Micro 吗? (因为它是 GUI 的东西?)
- 我的 ViewModel 应该在哪里,在 Class 库或 GUI 项目中?
[编辑] 我更改了我的文件夹结构,我的 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(),但这是更新列表中所有条目的最简单方法。