使用 Calcium 使 MVVM 更简单 - 第 1 部分






4.97/5 (35投票s)
了解如何使用 Calcium SDK 创建一个基于简单 MVVM 模式的应用程序。

目录
- 引言
- 演示
- 你需要什么
- Q1. 如何安装 Calcium SDK?
- Q2. 安装程序做了什么?
- Q3. 如何升级到最新版本?
- Q4. 如何创建 Calcium 应用程序?
- Q5. 如何扩展我的 Calcium 应用程序?
- Q6. 向现有项目添加视图的最佳方法是什么?
- Q7. Calcium 提供 Web 应用程序模板吗?
- Q8. 如何排除默认模块?
- Q9. 如何更改 Calcium 默认主题?
- Q10. 我可以定义自己的主题吗?
- Q11. 如何重塑 Calcium 品牌?
- Q12. 如何自定义标准菜单和标准工具栏?
- Q13. 如何隐藏标准菜单或标准工具栏?
- Q14. 如何显示模式对话框?
- Q15. 如何利用 T4 模板?
- Q16. 我可以在哪里提出未来版本的功能请求?
- 结论
- 参考文献
- 历史
引言
如果您是 WPF 或 Silverlight 开发人员,您可能已经听说过关于 MVVM 模式的热议。简而言之,它是一种有助于将视图层与应用程序其余部分分离的模式。开发 WPF 或 Silverlight 应用程序可能需要仔细思考如何最好地应用此模式。在某些情况下,您可能希望编写自己的框架,但在大多数情况下,最佳选择可能是利用一些现有的开源 MVVM 框架。这些框架种类繁多,您可以在 Jeremy Alles 的博客上查看他的概述。每个框架都有其优缺点,并且每个都适用于不同的情况。其中一个框架是 Calcium,它构建在复合应用程序库(也称为 PRISM)之上。PRISM 没有明确提供对 MVVM 模式的支持,因为其主要目的是协助构建模块化应用程序。然而,Calcium 补充了 PRISM 提供的基础设施,包括高级模块管理、客户端-服务器日志记录、主题支持、撤消/重做/重复任务管理系统,以及用户消息和文件管理的抽象。因此,如果您正在构建模块化 WPF 或 Silverlight 应用程序,也许您已经发现了 Calcium。Calcium 确实是一块宝石(我可能有些偏颇,但我了解其背后的策划者以及为此付出了多少努力)。由于 Calcium 提供了大量基础设施,并且可能被认为是复杂的,因此需要一份关于如何配置和自定义 Calcium 项目的简单指南。这就是本文的目的。我将以 FAQ 格式提供此指南,并在随附的演示项目中演示本 FAQ 中解释的要点。本文是一项正在进行的工作,因为 Calcium 是一个活跃项目,并且正在不断发展。事实上,由于需要涵盖的内容很多,我已将 FAQ 分为两篇文章。第一部分,即本文,将主要关注如何开始使用 Calcium 以及如何自定义和重塑 Calcium 项目的品牌。第二篇文章将侧重于示例应用程序中实现的其他功能。除了本 FAQ,还有 Daniel 撰写的 3 篇详细文章,描述了 Calcium 框架,2 篇文章描述了撤消/重做/重复任务管理框架,以及一个入门视频。
演示
本文附带的演示应用程序是一个 Calcium 示例项目,演示了本文中解释的自定义、品牌重塑、模块开发和模式对话框支持,以及将在第 2 部分中介绍的其他功能,例如文件服务。该演示是一个功能很少的简单图像设计器,但允许用户将本地驱动器中的图像上传到 Calcium 外壳的工具区域。当用户单击图像时,所选图像将显示在设计器工作区中。用户可以使用设计器的工具栏放大和缩小图像。用户还可以使用“文件”菜单打开另一个图像设计器。
你需要什么
- Calcium SDK - 请参阅本 FAQ 的 Q1 以了解如何安装 Calcium SDK
- VS2008 或 VS2010 - Calcium 支持 VS2008 和 VS2010。演示应用程序是在 VS2010 中开发的。
Q1. 如何安装 Calcium SDK?
Calcium 托管在 codeplex 上,您可以在其中选择下载发行版本或源代码版本。源代码版本定期更新,因此是最新版本。
安装发行版本
将 zip 文件的内容解压到您选择的目录。根据您的环境,打开 VS2008 或 VS2010 文件夹,然后启动 setup.exe 文件以安装 Calcium SDK。安装向导将引导您完成安装过程。
从源代码安装
将 zip 文件的内容解压到您选择的目录,然后导航到 Calcium 解决方案 (Source->Calcium->Calcium)。对于桌面 CLR,打开 CalciumDesktopClr 解决方案;对于 Silverlight,打开 CalciumSilverlightClr 解决方案(请注意,CalciumSilverlitghtClr 安装程序尚未实现)。将您的解决方案配置更改为 Release 并重新生成解决方案。在解决方案中找到 Visual Studio 集成项目,并根据您的环境右键单击 Calcium.VSIntegration.VS09Setup 或 Calcium.VSIntegration.VS10Setup 项目,然后选择安装选项,如图 1 所示。按照安装向导安装 Calcium SDK。
无需卸载以前的 Calcium 版本,除非您希望安装更早的版本。
图 1:安装 Calcium
注意:当您发布构建解决方案时,可能会遇到此错误:“如果非 Release 等于 Release 则转到末尾 echo 压缩模板... 如果脚本在 PowerShell 中失败,可能是脚本权限问题。在 PowerShell 提示符下尝试:set-executionpolicy unrestricted...” 如果您不修改或自定义模板,则可以安全地删除 Calcium.VSIntegration 项目的预构建事件,该事件执行 PowerShell 脚本。
Q2. 安装程序做了什么?
安装程序执行四项操作
- 将已构建的程序集复制到安装目录(指定的路径)。
- 程序集不会复制到 GAC,因为使用源代码版本(尽管这可能很少见)的开发人员需要更新所有 Calcium 程序集的版本号。这是因为在运行时,CLR 在解析程序集引用时会首先查找 GAC,即使您正在调试应用程序也是如此。
- 将 VS 模板所需的程序集放置到 GAC 中。
- 设置一个包含安装位置的注册表字符串。
- 将压缩的模板复制到目录中。
Q3. 如何升级到最新版本?
由于 Calcium 的工作非常活跃,您经常需要重新安装 Calcium SDK。如果您正在使用源代码版本,并且希望在获取最新版本后重新安装 Calcium,为了让安装程序更新安装目录中的程序集,您需要确保程序集的文件版本已更改。这是文件版本而不是程序集版本。Windows 安装程序会忽略程序集版本,并且仅当程序集具有不同的文件版本时才安装新版本!Calcium SDK 解决方案包含 T4 模板,用于更新所有生成的程序集的文件版本。如果您在将安装程序重新构建为发布版本之前修改了源代码版本,请务必单击“转换所有模板”按钮(如图 2 所示),因为这将确保当您重新安装最新的 Calcium SDK 时,最新的程序集将复制到应用程序目录。安装完成后,对现有项目进行干净构建,您的项目将自动使用最新的程序集。
图 2:转换所有模板
如果您经常使用 Codeplex 上的源代码管理提供程序获取最新版本,那么最好的方法是引用位于 Calcium 解决方案源代码版本的发布目录中的 Calcium 项目发布版本中的程序集。使用您使用的任何客户端从源代码管理中获取最新版本,并将其构建为发布版本。然后在您自己的项目中将这些程序集引用到源代码中各个项目的 bin 目录中的程序集。例如,DanielVaughan
程序集位于 Core (Calcium\Source\Core\Core\bin\Release\DanielVaughan.dll) 下,DanielVaughan.Calcium.Client
程序集位于 Calcium.Client (Calcium\Source\Calcium\Calcium\Calcium.Client\bin\Release\DanielVaughan.Calcium.Client.dll) 下,Logging
程序集位于 Clog (Calcium\Source\Clog\bin\Release\DanielVaughan.Logging.dll) 下等等。这样,您就不必每次获取最新版本时都使用安装程序。这也将允许您调试 Calcium 程序集,因为在您自己的机器上编译它们意味着源代码程序集和行号信息将可供调试器使用。此外,通过引用程序集而不是项目,您可能会发现您的解决方案感觉更轻,并且可能允许您更高效地工作。
Q4. 如何创建 Calcium 应用程序?
打开 Visual Studio 并导航到创建新项目。从“Windows”类别中,选择“Calcium 项目”,指定名称,然后单击“确定”创建新的 Calcium 应用程序。如果您刚刚安装了 Calcium SDK 的源代码版本,但看不到 Calcium 项目模板,请尝试重新启动 Visual Studio。当您的新 Calcium 应用程序启动时,您会看到它已经填充了几个默认模块:文本编辑器、模块管理器、Web 浏览器和输出窗口。请参阅本 FAQ 的 Q7,了解如何阻止这些模块加载的说明。有关创建 Calcium 应用程序的更多信息,请观看 Calcium 入门视频。
Q5. 如何扩展我的 Calcium 应用程序?
Calcium 基于 PRISM,PRISM 提供了构建模块化应用程序的基础设施。因此,扩展 Calcium 应用程序的主要方式是创建模块。模块可以分组到一个项目中,也可以放在单独的项目中。要创建新的 Calcium 模块,右键单击解决方案并选择“添加”->“新建项目”。从“Windows”类别中选择“Calcium 模块”,重命名并单击“确定”创建新的 Calcium 模块。参见图 3。您可以根据需要创建任意数量的模块,以及每个模块中任意数量的视图。示例应用程序由一个模块 (ImageDesigner) 组成,其中包含两个视图 (ImageList 和 ImageSelector)。
图 3:添加 Calcium 模块
每个 Calcium 模块都预设了一个模块、视图、ViewModel 和一个 T4 模板。模块在初始化期间以及响应事件时创建和显示视图。视图是我们可以放置数据绑定到 ViewModel 的控件的容器。ViewModel 包含视图的布局和业务逻辑。ViewModel 不了解视图。当您创建新的 Calcium 模块时,系统会为您提供模块名称的命名指导:YourNamespace.ModuleName。这是一种命名约定,用于自动命名各种生成的项。例如,演示项目中的模块名为 CalciumSample.ImageDesigner,生成的项(模块、视图和 ViewModel)分别自动命名为 CalciumSample.ImageDesignerModule、CalciumSample.ImageDesignerView 和 CalciumSample.ImageDesignerViewModel。
默认情况下,模块的程序集需要存在于主应用程序的 bin 目录中。因此,为了使您的新模块在应用程序启动时加载,您必须添加对新模块项目的引用,或者添加一个后期构建事件以将程序集复制到 bin 目录。然后,您的模块将自动显示在应用程序的“视图”菜单中。
添加后期构建事件以将程序集复制到 bin 目录
右键单击您的模块项目,然后转到属性。打开“构建事件”选项卡,如图 4 所示,将以下代码放置到后期构建事件命令行文本框中
COPY "$(TargetPath)" "$(SolutionDir)ModuleProjectName\bin\$(ConfigurationName)\
将 ModuleProjectName 替换为您的启动器项目的名称。
图 4:添加后期构建事件以将构建的程序集推送到应用程序的 bin 目录。
使用后期构建事件将已构建的程序集推送到应用程序的 bin 目录的好处是,您的主项目和模块项目之间没有依赖关系。因此,模块是独立的。缺点是,如果您计划使用 ClickOnce 部署,您可能会发现此方法效果不佳,因为不会检测到对模块程序集的依赖关系。
Q6. 向现有项目添加视图的最佳方法是什么?
Calcium 附带了一个视图模板。要将视图添加到您的模块,只需右键单击模块项目,然后选择“添加”->“新建项”,并从 WPF 类别中选择“Calcium View”,如图 5 所示。系统将为您生成一个新的 Calcium View 和一个 ViewModel。在演示应用程序中,我们创建了一个名为 ImageList 的新视图。在解决方案中,您可以看到新视图 (CalciumSample.ImageListView) 和 ViewModel (CalciumSample.ImageListViewModel) 的自动命名。
图 5:添加新的 Calcium 视图。
Q7. Calcium 提供 Web 应用程序模板吗?
是的,Calcium 提供了一个 Web 应用程序模板。为了获得一些通信服务和客户端服务器日志记录,您可能需要创建一个 Calcium Web 应用程序。要创建新的 Calcium Web 应用程序,请右键单击解决方案并选择“添加”->“新建项目”。在“Web”类别下选择“Calcium Web 应用程序”,为其命名并单击“确定”。参见图 6。演示应用程序不使用 Calcium Web 应用程序。
图 6:添加 Calcium Web 应用程序。
Q8. 如何排除默认模块?
Calcium 项目开箱即用,附带了几个模块。所有或任何默认模块都可以通过 AppStarter 阻止加载。AppStarter 显示启动画面、加载资源、运行 Bootstrapper 并显示外壳或主窗口。
在您的项目中,打开 App.xaml.cs(供您参考,在源代码中,App.xaml.cs 位于 Calcium.Client.Launcher 项目下)。在 Application_Startup
方法中使用 ExcludedModules
列表排除任何默认模块,如下所示
/* To exclude default modules use the ExcludedModules list. */
var starter = new AppStarter();
starter.StartupOptions.ModuleCatalogOptions.ExcludedModules.Add(ModuleNames.TextEditor);
starter.Start();
如果您希望一步到位删除所有默认模块(OutputDisplay、ModuleManager、Communication、History 和 WebBrowser),您可以将 DefaultModuleNames
列表添加到 ExcluededModules
,如下所示
starter.StartupOptions.ModuleCatalogOptions.ExcludedModules.AddRange(ModuleNames.DefaultModuleNames);
但是,不建议删除 ModuleManager,除非您提供自己的自定义模块管理。
诸如 DiagramDesigner 之类的模块可以通过从项目中删除模块引用来删除。例如,要删除 DiagramDesigner 模块,只需从项目中删除 DanielVaughan.Calcium.DiagramDesigner
引用。
图 7:从项目中删除 DiagramDesigner 引用。
您还可以使用模块管理器在运行时启用或禁用模块。
Q9. 如何更改 Calcium 默认主题?
Calcium 提供了两种主题:Expression Dark(默认)和 Bureau Blue。在 Calcium 项目中,可以在 App.config 中更改主题。要将默认主题更改为 Bureau Blue 主题,只需将默认主题更改为 Bureau Blue,如下代码所示
<!-- Themes: "Default", "Bureau Blue" -->
<add key="Theme" value="Bureau Blue"/>
您还可以使用模块管理器在运行时激活/禁用 Bureau Blue 主题。
Q10. 我可以定义自己的主题吗?
Shell 的样式和整个 ControlTemplate 都可以自定义。有一个 IThemeService 可以用来在运行时注册主题。新的默认主题可以作为如何创建自定义主题的示例。
Q11. 如何重塑 Calcium 品牌?
一个 Calcium 项目开箱即用,带有 Calcium 品牌的启动画面、应用程序图标、标题文本、关于框以及带有徽标的标题横幅。所有这些都可以轻松删除或重新 branding 以适应您的应用程序。
启动画面
启动画面可以通过 AppStarter 自定义,或者通过注册实现 ISplashScreen
的窗口。
通过 AppStarter 自定义启动画面
打开 App.xaml.cs(供您参考,在源代码中,App.xaml.cs 位于 Calcium.Client.Launcher 项目下)。在 Application_Startup
方法中使用 StartupOptions
,如下所示
var starter = new AppStarter();
/* To customize the splash screen image use the StartupOptions as shown below. */
starter.StartupOptions.SplashImagePackUri
= new Uri("pack://application:,,,/Assembly;component/YourImage.gif");
将上述代码中的 Your Assembly 和 YourImage.jpg 替换为您自己的。您可以在项目属性中找到您的程序集名称(在解决方案资源管理器中,右键单击项目并转到“属性”。在“应用程序”选项卡下可以找到“程序集名称”)。上述代码假设 YourImage.jpg 文件位于项目根目录。
或者,如果您希望使用元数据生成来避免魔术字符串,那么您可以使用
starter.StartupOptions.SplashImagePackUri
= new Uri(Metadata.YourImageJpg. RelativePackUri);
有关如何利用 T4 模板的更多信息,请参阅本 FAQ 的 Q15。
撰写本文时,SplashScreenView
正在重构,以允许重写启动画面模板。
应用程序图标
应用程序图标可以通过覆盖默认的 ShellView 样式来定制。在您的 Calcium 项目中,将以下代码粘贴到您的 App.xaml 的 <Application.Resources>
元素中。
<BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter" />
<Style TargetType="{x:Type Gui:ShellView}">
<Setter Property="Icon" Value="..\Icons\icon.ico" />
<Setter Property="Title" Value="{Binding Title}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Gui:ShellView}">
<Grid x:Name="PART_Grid_Root"
Background="{DynamicResource WindowBackgroundBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="PART_ColumnDefinition_Left"
MinWidth="80"/>
<ColumnDefinition x:Name="PART_ColumnDefinition_Center"
Width="2.5*" MinWidth="80" />
<ColumnDefinition x:Name="PART_ColumnDefinition_Right"
MinWidth="80" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition x:Name="PART_RowDefinition_Banner"
Height="Auto"/>
<RowDefinition x:Name="PART_RowDefinition_Menu"
Height="Auto"/>
<RowDefinition x:Name="PART_RowDefinition_Content"
Height="2*" MinHeight="80" />
<RowDefinition x:Name="PART_RowDefinition_OutputDisplay"
MinHeight="80"/>
<RowDefinition x:Name="PART_RowDefinition_StatusBar"
Height="Auto" />
</Grid.RowDefinitions>
<!-- Banner -->
<Border x:Name="PART_Border_Banner" Grid.Row="0"
Grid.ColumnSpan="3" Margin="10, 0, 0, 0">
<Expander x:Name="PART_Expander_Banner" IsExpanded="True"
ExpandDirection="Down"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" MinHeight="6"
Style="{DynamicResource ExpanderStyle}"
Visibility="{Binding BannerVisible,
Converter={StaticResource booleanToVisibilityConverter}}">
<StackPanel x:Name="PART_StackPanel_TitleBanner"
cal:RegionManager.RegionName
="{x:Static Calcium:RegionNames.Banner}"
Orientation="Horizontal">
<Controls:ShellBanner x:Name="PART_ShellBanner"
HorizontalAlignment="Stretch" Padding="0, 0, 10, 5"
Visibility="{Binding LogoVisible,
Converter
={StaticResource booleanToVisibilityConverter}}"/>
</StackPanel>
</Expander>
</Border>
<!-- Menu -->
<ContentControl x:Name="PART_ContentControl_Menu" Grid.Row="1"
Grid.ColumnSpan="3"
Margin="7,0,0,5" Padding="5" >
<StackPanel x:Name="PART_StackPanel_Menu">
<Controls:StandardMenu x:Name="PART_Menu"
Margin="0, 0, 0, 0" />
<!--<Controls:StandardToolBarTray x:Name="PART_ToolBar"
Margin="0, 5, 0, 0"/>-->
<Border x:Name="Menu_Border" Width="Auto">
<ToolBarTray cal:RegionManager.RegionName
="{x:Static Calcium:RegionNames.StandardToolBarTray}">
<ToolBar x:Name="ToolBar_Standard">
<Button Command="ApplicationCommands.Save"
ToolTip="{Binding RelativeSource
={RelativeSource Self},
Path=Command.Text}">
<Image Width="15" Height="15"
Source="{x:Static m:SavePng.Source}" />
</Button>
<Button Command="{x:Static Gui:ShellView.SaveAll}"
ToolTip="{Binding RelativeSource
={RelativeSource Self},
Path=Command.Text}">
<Image Width="15" Height="15"
Source="{x:Static m:SaveAllPng.Source}" />
</Button>
</ToolBar>
</ToolBarTray>
</Border>
</StackPanel>
</ContentControl>
<!-- Workspace -->
<Border x:Name="PART_Border_Workspace"
Grid.Row="2" Grid.Column="1">
<TabControl x:Name="PART_TabControl_Workspace"
cal:RegionManager.RegionName
="{x:Static Calcium:RegionNames.Workspace}"
IsSynchronizedWithCurrentItem="True"
Width="Auto" Height="Auto"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Margin="0,0,0,0"/>
</Border>
<!-- Tools Left -->
<Border x:Name="PART_Border_Left" Grid.Row="2" Padding="0,0,1,10"
Grid.RowSpan="2">
<Expander x:Name="PART_Expander_Left" IsExpanded="True"
ExpandDirection="Left"
Style="{DynamicResource ExpanderStyle}"
BorderThickness="0,0,0,0" Margin="0,0,0,0">
<TabControl x:Name="PART_TabControl_Left"
cal:RegionManager.RegionName
="{x:Static Calcium:RegionNames.Tools}"
MinWidth="0" Margin="10,0,1,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsSynchronizedWithCurrentItem="True"
BorderThickness="0,0,0,0"/>
</Expander>
</Border>
<!-- ModuleManager etc., Right -->
<Border x:Name="PART_Border_Right" Grid.Row="2"
Grid.Column="2" Grid.RowSpan="2"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Padding="1,0,0,10" Margin="0,0,0,0">
<Expander x:Name="PART_Expander_Right"
IsExpanded="True" ExpandDirection="Right"
Style="{DynamicResource ExpanderStyle}"
BorderThickness="0,0,0,0">
<TabControl x:Name="PART_TabControl_Right"
cal:RegionManager.RegionName
="{x:Static Calcium:RegionNames.Properties}"
MinWidth="0" Margin="1,0,10,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsSynchronizedWithCurrentItem="True"
BorderThickness="0,0,0,0"
Background="{x:Null}" Foreground="{x:Null}"
BorderBrush="{x:Null}"/>
</Expander>
</Border>
<!-- OutputDisplay etc. -->
<Border x:Name="PART_Border_Bottom"
Grid.Row="3" Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Margin="0,0,0,0">
<Expander x:Name="PART_Expander_Bottom" IsExpanded="True"
MinHeight="6" Style="{DynamicResource ExpanderStyle}"
BorderThickness="0,0,0,0"
Margin="0,0,0,0" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch">
<TabControl x:Name="PART_TabControl_Bottom"
cal:RegionManager.RegionName
="{x:Static Calcium:RegionNames.Footer}"
MinHeight="80"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsSynchronizedWithCurrentItem="True"
Margin="0,1,0,10" />
</Expander>
</Border>
<StackPanel x:Name="PART_StackPanel_StatusBar"
Grid.Row="4" Grid.ColumnSpan="3"
cal:RegionManager.RegionName
="{x:Static Calcium:RegionNames.StatusBar}"
Orientation="Horizontal" Margin="0,-10,0,0">
</StackPanel>
<!-- GridSplitters -->
<GridSplitter x:Name="PART_GridSplitter_Left"
Grid.Column="0" Grid.Row="2" Grid.RowSpan="2"
HorizontalAlignment="Right"
VerticalAlignment="Stretch" Width="4"
Opacity="0" Margin="0,20,-4,6" />
<GridSplitter x:Name="PART_GridSplitter_Bottom"
Grid.Column="1" Grid.Row="3" Visibility="Visible"
HorizontalAlignment="Stretch"
VerticalAlignment="Top" Height="4"
Opacity="0" Grid.ColumnSpan="1"
Margin="0,-4,0,0" />
<GridSplitter x:Name="PART_GridSplitter_Right"
Grid.Column="2" Grid.Row="2" Visibility="Visible"
HorizontalAlignment="Left"
VerticalAlignment="Stretch" Width="4"
Opacity="0" Margin="-4,20,0,6"
Grid.RowSpan="2" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
将以下命名空间引用添加到 App.xaml 的顶部,或者让 Resharper 等工具为您添加这些引用。
xmlns:Gui="clr-namespace:DanielVaughan.Calcium.Gui;assembly=DanielVaughan.Calcium.Client"
xmlns:cal="clr-namespace:Microsoft.Practices.Composite.Presentation.Regions;assembly
=Microsoft.Practices.Composite.Presentation"
xmlns:Calcium="clr-namespace:DanielVaughan.Calcium;assembly=DanielVaughan.Calcium.Client"
xmlns:Controls="clr-namespace:DanielVaughan.Calcium.Gui.Controls;assembly
=DanielVaughan.Calcium.Client"
请注意,您可以在此处覆盖任何 Shell 视图类型的样式。为确保您始终使用最新的模板,请下载最新的源代码并从 Desktop CLR Client -> Calcium.Client 项目下 Themes 文件夹中的 WindowDictionary.xaml 文件中复制上述代码。
要更改应用程序图标,请将 Icon 值替换为如下所示
<Setter Property="Icon" Value="..\..\Icon.ico" />
应用程序标题
应用程序标题可以通过重写样式(如上图标所做)进行自定义,也可以通过从服务定位器中检索 IShell
实例,并在 Application_Startup
中设置 Title 来完成,如下所示
var shell = ServiceLocatorSingleton.Instance.GetInstance<IShell>();
shell.Title = "Calcium Sample";
目前在 Application_Startup
中设置标题可能不理想,因为它发生在窗口加载之后,但在不久的将来,将有一种方法可以创建在窗口显示之前执行的命令。
带徽标的标题横幅
通过覆盖默认的 Shell 横幅样式,可以自定义 Shell 横幅。将以下代码复制到 App.xaml 的应用程序资源中,并根据您的需要进行修改。
<!--Override the shell banner style-->
<Style TargetType="{x:Type Controls:ShellBanner}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Controls:ShellBanner}">
<Image x:Name="PART_Image_Banner" Source="logo.png"
Stretch="None" HorizontalAlignment="Left"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
如果您希望隐藏横幅或横幅徽标,您可以从服务定位器中检索 IShell
实例,并将 LogoVisible
和 BannerVisible
属性设置为 false
,如以下摘录所示
var shell = ServiceLocatorSingleton.Instance.GetInstance<IShell>();
shell.BannerVisible = false;
shell.LogoVisible = false;
关于框
可以通过在 ServiceLocatorSingleton
中注册 IAboutBox
类型关联来替换默认的 AboutBox。这可以在项目或模块中完成。确保您的 AboutBox 实现 IAboutBox
接口。在项目中,可以在 App.xaml.cs 的 Application_Startup
方法中完成,如下所示
var container = new UnityContainer();
ServiceLocatorSingleton.Instance.InitializeServiceLocator(container);
ServiceLocatorSingleton.Instance.RegisterType<IAboutBox, AboutBox>();
在 module
中,您可以在模块初始化期间向 ServiceLocatorSingleton
注册 AboutBox
,如下所示
ServiceLocatorSingleton.Instance.RegisterType<IAboutBox, AboutBox>();
建议在模块中注册 AboutBox
,因为它不需要替换容器。
第三,可以通过在 unity 配置 App.config 中注册您的 IAboutBox
实现来实现
<unity>
<typeAliases>
</typeAliases>
<containers>
<container>
<types>
<type type=" DanielVaughan.Calcium.Gui.Windows.IAboutBox,
DanielVaughan.Calcium.Client" mapTo= "
YourNamespace.YourAboutBox, YourAssembly" />
</types>
<instances>
</instances>
</container>
</containers>
</unity>
Q12. 如何自定义标准菜单和标准工具栏?
StandardMenu
和 StandardToolBarTray
控件承载了许多区域,可用于添加模块特定的菜单、菜单项和工具栏。
标准菜单
您可以向标准菜单添加菜单项,也可以创建自己的自定义菜单并将其添加到标准菜单。
自定义菜单项
可以在 module
类中,在模块初始化期间添加新的菜单项,如下所示
ShowView(MenuNames.File, new MenuItem() {Header = "Menu Item"});
另一个选项是创建自定义菜单控件。以下示例显示了一个带有分隔符的简单菜单控件,该控件用于演示中。
<UserControl x:Class="CalciumSample.ImageDesigner.ImageDesignerMenu"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Menu x:Name="CustomMenu">
<MenuItem Header="Designer">
<MenuItem Header="Menu Item1"/>
<Separator />
<MenuItem Header="Menu Item2"/>
</MenuItem>
</Menu>
</Grid>
</UserControl>
使用此方法可获得设计器支持。在自定义菜单控件的代码隐藏中,我们需要将菜单项从菜单中分离,如下所示
public IEnumerable<FrameworkElement> RetrieveAndDetachItems()
{
var list = new List<FrameworkElement>();
foreach (var item in CustomMenu.Items)
{
list.Add((FrameworkElement)item);
}
foreach (var item in list)
{
CustomMenu.Items.Remove(item);
}
return list;
}
最后,我们使用新的菜单项填充菜单区域,如下所示。我们在模块初始化期间在 module
类中执行此操作
/* Add custom menu items to the standard menu under File menu. */
CustomMenuItems customMenuItems = new CustomMenuItems();
var menuItems = customMenuItems.RetrieveAndDetachItems();
foreach (var frameworkElement in menuItems)
{
ShowView(MenuNames.File, frameworkElement);
}
如上所示,我们实例化了新的菜单控件,获取了菜单项列表,并将每个菜单项添加到“文件”菜单区域。目前,菜单项位于菜单的末尾,这对于“文件”菜单来说可能不理想。未来的版本可能会具有菜单项排序功能。
自定义菜单
您还可以创建带有根菜单的自定义菜单,如下面的摘录所示
<UserControl x:Class="CalciumSample.ImageDesigner.CustomMenuItems"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Menu x:Name="CustomMenu">
<MenuItem Header="Menu Item1"/>
<Separator />
<MenuItem Header="Menu Item2"/>
</Menu>
</Grid>
</UserControl>
同样,我们需要在自定义菜单控件的代码隐藏中将菜单项从菜单中分离,如下所示
public IEnumerable<FrameworkElement> RetrieveAndDetachItems()
{
var list = new List<FrameworkElement>();
foreach (var item in CustomMenu.Items)
{
list.Add((FrameworkElement)item);
}
foreach (var item in list)
{
CustomMenu.Items.Remove(item);
}
return list;
}
最后,我们在模块初始化期间在 module
类中将自定义菜单放置到 MainMenu
区域,如下所示
var viewService = ServiceLocatorSingleton.Instance.GetInstance();
/* Add custom menu. */
ImageDesignerMenu imageDesignerMenu = new ImageDesignerMenu();
var imageDesignerMenuItems = imageDesignerMenu.RetrieveAndDetachItems();
foreach (var frameworkElement in imageDesignerMenuItems)
{
ShowView(MenuNames.MainMenu, frameworkElement);
/*Show only when module is active*/
viewService.AssociateVisibility(typeof(ImageDesignerView),
new UIElementAdapter(frameworkElement), Visibility.Collapsed);
}
如果您希望仅当模块存在于工作区区域时才显示菜单,则可以将其注册到视图服务,如上所示。
标准工具栏
您可以为任何模块创建自定义工具栏,也可以自定义标准工具栏。
自定义工具栏
以下示例显示了一个简单的工具栏控件,带有演示应用程序中使用的两个图标。
<UserControl x:Class="CalciumSample.ImageDesigner.ImageDesignerToolBar"
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:m="clr-namespace:CalciumSample.ImageDesigner.FileMetadata.Icons.Metadata"
xmlns:ImageDesigner="clr-namespace:CalciumSample.ImageDesigner"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
<Grid x:Name="LayoutRoot">
<ToolBar Name="toolBar_Main">
<Button x:Name="Button_ZoomIn"ToolTip="Zoom in"
Command="{x:Static ImageDesigner:ImageDesignerViewModel.ZoomInCommand}">
<Image Source="{x:Static m:ZoomInPng.Source}" Width="16" Height="16" />
</Button>
<Button x:Name="Button_ZoomOut" ToolTip="Zoom out"
Command="{x:Static ImageDesigner:ImageDesignerViewModel.ZoomOutCommand}">
<Image Source="{x:Static m:ZoomOutPng.Source}" Width="16" Height="16" />
</Button>
</ToolBar>
</Grid>
</UserControl>
(示例图标从此处下载)
由于 ToolBar
不能分配给另一个父容器,我们在用户控件中定义了一个属性,该属性将 ToolBar
分离,如下所示
/// <summary>
/// Gets the tool bar by detaching it from the root control.
/// </summary>
/// <value>The tool bar.</value>
public ToolBar ToolBar
{
get
{
LayoutRoot.Children.Remove(toolBar_Main);
return toolBar_Main;
}
}
下一步是将我们的新工具栏连接到工具栏托盘。我们在 module
类中的模块初始化期间完成此操作,如下所示
/* Add the custom toolbar to the StandardToolbarTray. */
var toolBarProvider = new CustomToolBarProvider();
var toolBar = toolBarProvider.ToolBar;
ShowView(RegionNames.StandardToolBarTray, toolBar, false);
如果 ShowView
方法中的 false
参数设置为 true
,则视图将立即显示。如果设置为 false
,则可能显示也可能不显示。例如,如果视图位于选项卡控件中,如果为 true,则视图将成为选定项。
如果您希望仅当特定视图或视图类型或视图模型存在于工作区区域时才显示工具栏,则可以将其注册到视图服务,如下所示
var viewService = ServiceLocatorSingleton.Instance.GetInstance<IViewService>();
viewService.AssociateVisibility(typeof(ImageDesignerView),
new UIElementAdapter(toolBar), Visibility.Collapsed);
自定义标准工具栏。
您可以覆盖任何 ShellView 类型的默认样式。将 WindowDictionary.xaml 中的默认样式复制到 App.xaml 的应用程序资源中,并自定义 PART_ContentControl_Menu
。在演示应用程序中,我将 ToolBarTray
图标替换为我自己的图标,如以下摘录所示
<!-- Menu -->
<ContentControl x:Name="PART_ContentControl_Menu" Grid.Row="1" Grid.ColumnSpan="3"
Margin="7,0,0,5" Padding="5" >
<StackPanel x:Name="PART_StackPanel_Menu">
<Controls:StandardMenu x:Name="PART_Menu" Margin="0, 0, 0, 0" />
<!--<Controls:StandardToolBarTray x:Name="PART_ToolBar" Margin="0, 5, 0, 0" />-->
<Border x:Name="Menu_Border" Width="Auto">
<ToolBarTray cal:RegionManager.RegionName="{x:Static Calcium:RegionNames.StandardToolBarTray}">
<ToolBar x:Name="ToolBar_Standard">
<Button Command="ApplicationCommands.Save"
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}">
<Image Width="15" Height="15" Source="{x:Static m:SavePng.Source}" />
</Button>
<Button Command="{x:Static Gui:ShellView.SaveAll}"
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}">
<Image Width="15" Height="15" Source="{x:Static m:SaveAllPng.Source}" />
</Button>
</ToolBar>
</ToolBarTray>
</Border>
</StackPanel>
Q13. 如何隐藏标准菜单或标准工具栏?
您可以通过覆盖默认样式并设置这些控件的可见性来隐藏默认的标准工具栏或菜单,如下所示
<!-- Menu -->
<ContentControl x:Name="PART_ContentControl_Menu" Grid.Row="1" Grid.ColumnSpan="3"
Margin="7,0,0,5" Padding="5" >
<StackPanel x:Name="PART_StackPanel_Menu">
<Controls:StandardMenu x:Name="PART_Menu" Margin="0, 0, 0, 0" Visibility="Collapsed" />
<Controls:StandardToolBarTray x:Name="PART_ToolBar" Margin="0, 5, 0, 0" Visibility="Collapsed"/>
</StackPanel>
</ContentControl>
Q14. 如何显示模式对话框?
Calcium 刚刚添加了对 WindowRegionAdapter 的模式支持。它可以通过在 View 或 ViewModel 上实现 IProvider<IViewOptions>
来使用。演示应用程序在 ImageListView 中演示了模式对话框,其中 View 实现了 IProvider<IViewOptions>
接口,而 ViewOptions
类实现了 IViewOptions
,如以下摘录所示
public partial class ImageListView : IProvider<IViewOptions>
{
public ImageListView()
{
InitializeComponent();
}
ViewOptions viewOptions = new ViewOptions();
public IViewOptions ProvidedItem
{
get
{
return viewOptions;
}
}
class ViewOptions : IViewOptions
{
public void PrepareContainer(object container)
{
}
public bool Modal
{
get
{
return true;
}
}
}
}
在 ImageListViewModel 中,我们有一个 UICommand
并附加了一个处理程序,如下面的摘录所示
public static UICommand SelectImageCommand = new UICommand(OnSelectImage);
处理程序从服务定位器检索 IViewService
实例,实例化视图并将其添加到 shell 区域,如下所示
static void OnSelectImage(object obj)
{
var viewService = ServiceLocatorSingleton.Instance.GetInstance<IViewService>();
var content = new ImageSelectorView();
string regionName;
viewService.ShowView(content, RegionNames.Shell, true, out regionName);
}
在 ImageListView 中,我们将 UICommand
绑定到一个按钮以引发模式对话框,如下面的摘录所示
<Button Command="{x:Static Module:ImageListViewModel.SelectImageCommand}"
Content="Add Image" />
Q15. 如何利用 T4 模板?
T4 模板在项目和模块创建期间会自动添加。它们默认禁用,要使用它们,您需要运行“自定义工具”。要运行“自定义工具”,只需右键单击 MetadataGeneration.tt 文件并选择“运行自定义工具”,如图 8 所示。
图 8:运行自定义工具
一旦自定义工具生成元数据,您就可以开始使用它了。例如,在演示项目中,我们使用元数据将图像源指定为静态类型属性,如下所示
<Image Source="{x:Static m:ZoomInPng.Source}" Width="15" Height="15"/>
我们还需要在应用程序元素中声明命名空间,如下所示
xmlns:m="clr-namespace:CalciumSample.FileMetadata.Icons.Metadata”
请注意,为了使用上述代码,您需要将图像的“构建操作”设置为“资源”(右键单击图标,转到“属性”,然后从“构建操作”选项列表中选择“资源”)。
有关 T4 模板的更多一般信息,您可以阅读 Daniel 关于使用 T4 进行项目元数据生成的文章。
Q16. 我可以在哪里提出未来版本的功能请求?
请求新功能的最佳方法是使用 Calcium Codeplex 站点上的问题跟踪器。
结论
在 Calcium FAQ 系列的第一部分中,我们主要关注如何入门,我们学习了模块和视图的创建以及如何修改和重新命名 Calcium 项目。我们还学习了如何利用 Calcium 模式支持和 T4 模板。第二部分将解释更多 Calcium 功能,例如如何利用文件服务或如何添加新的区域名称。我希望我的努力对您有所帮助。如果您喜欢我的文章,请给它评分并分享您的想法。感谢阅读!
历史
2010年4月
首次发布