Calcium:利用 PRISM 的模块化应用程序工具集 - 第三部分






4.96/5 (47投票s)
隆重推出 Calcium SDK。Calcium 提供了许多快速构建多方面且复杂的模块化应用程序所需的功能。它包含大量模块和服务,以及一个可立即投入使用的基础设施,可用于您的下一个应用程序。
- 请访问 Calcium SDK 项目网站,获取最新版本和源代码。
目录
通过这个入门视频,了解如何使用新的 Calcium SDK 创建 Calcium 项目。请注意,点击图片将带您前往 Calcium SDK 网站,在那里您可以观看视频。我希望将其内联到文章中,但事实证明这太困难了。

引言
Calcium 是一个利用 Composite Application Library 的 WPF 复合应用程序工具集。它提供了快速构建多功能、复杂的模块化应用程序所需的大部分功能。
图:Calcium 桌面环境
在本系列文章的第一部分中,我们探索了 Calcium 的一些核心基础设施,包括模块管理、区域适配和 Bootstrapper
。在第二部分中,我们研究了位置无关的消息服务,该服务允许从任何地方(无论是服务器端还是客户端)显示一组通用对话框;Web 浏览器模块和输出模块。现在,我们将研究视图服务和文件服务,并讨论自撰写第一篇文章以来代码库的各种更改。
在第一篇文章中,我们看到 Calcium 由客户端应用程序和基于服务器的 WCF 服务组成,它们允许客户端之间的交互和通信。开箱即用,Calcium 提供了大量模块和服务,以及一个可用于您下一个应用程序的基础设施。
Calcium 的组成部分很多,因此我决定将本文分成一系列文章。
- Calcium 简介,模块管理器。
- 消息服务,Web 浏览器模块,输出模块。
- 视图服务,文件服务,重塑 Calcium 品牌(本文)
- 文本编辑器模块,用户亲和性模块,通信模块
- 待定
在本文中,您将学习如何:
- 使用 Calcium SDK 创建 Calcium 项目(视频),
- 使用文件服务抽象从任何源保存和加载数据,
- 并使用类型识别系统显示或隐藏内容。
本系列文章中的一些内容并非高级水平,即使是 Prism 新手也能轻松掌握。而另一些内容,例如消息系统,则适合更高级的读者。希望每个人都能有所收获。
在某些方面,本系列文章是对 Prism 某些领域的一些介绍。尽管如此,如果您是 Prism 的新手,您可能会觉得有时信息量过大,我建议您在学习 Calcium 之前,先查看一些初学者 Prism 文章。
上次更新以来
自上次以来,根据 Calcium 用户收到的反馈,进行了许多增强。最值得注意的是,Calcium 现在可以完全重塑品牌。这包括替换或自定义:启动画面、关于框、Logo 和 Shell 标题文本。我还付出了相当大的努力,为 Visual Studio 2008 和 2010 创建了 VS 模板,包括对 C# 和 VB.NET 的支持。
新的 Visual Studio 项目模板
我创建了一套十二个 Calcium 项目模板。有三个核心模板:启动器项目、模块项目和 Web 应用程序项目,它们已被移植到不同的环境:Visual Studio 2008(C# 和 VB.NET)以及 Visual Studio 2010(C# 和 VB.NET)。
Calcium 模块项目模板
Calcium 模块项目可能是 Calcium 项目模板集中最有趣的部分。此模板生成一套三个类,用于提供 Prism 模块。当您创建新的 Calcium 模块时,项目命名约定 [Namespace].[ModuleName] 会用于派生各种生成项的名称。例如,如果您碰巧将新项目命名为 MyNamespace.Demo,那么结果将是
- 一个名为
MyNamespace.DemoModule
的模块, - 一个名为
MyNamespace.DemoView
的视图, - 以及一个名为
MyNamespace.DemoViewModel
的视图模型。
自定义模板参数的幕后花絮
为了实现项目项的自动命名,使用了名为 ModuleProjectWizard
的 IWizard
实现来插入自定义模板参数,该参数随后用于命名我们的项目输出项。所有 Calcium SDK VS 模板都使用了 IW
izard
,它在每个 VS 模板文件中声明如下:
<VSTemplate Version="2.0.0" Type="Project">
<TemplateData>
…
</TemplateData>
<TemplateContent>
…
</TemplateContent>
<WizardExtension>
<Assembly>DanielVaughan.Calcium.VSIntegration,
Version=1.0.1.0, Culture=Neutral, PublicKeyToken=f32f1bf552288cd5</Assembly>
<FullClassName>DanielVaughan.Calcium.VSIntegration.ModuleProjectWizard</FullClassName>
</WizardExtension>
</VSTemplate>
包含 ModuleProjectWizard
的程序集在安装 Calcium SDK 时被放入 gac。随后,在创建新的 Calcium 模块项目时执行的 ModuleProjectWizard
方法如下所示:
public void RunStarted(object automationObject,
Dictionary<string, string> replacementsDictionary,
WizardRunKind runKind, object[] customParams)
{
string vsVersion;
if (!replacementsDictionary.TryGetValue("$VSVersion$", out vsVersion))
{
MessageBox.Show("Problem determining Visual Studio Version. "
+ "Please reinstall Calcium SDK.");
return;
}
if (runKind == WizardRunKind.AsNewProject
|| runKind == WizardRunKind.AsMultiProject)
{
/* Here we derive a module name using the last part of the project name.
* For example, if the user has entered MyNamespace.TextEditor
* in the new project dialog, then we elect TextEditor as the module name. */
string projectName = replacementsDictionary["$safeprojectname$"];
string lastWord = null;
int lastIndex = projectName.LastIndexOf('.');
if (lastIndex != -1 && lastIndex < projectName.Length - 1)
{
lastWord = projectName.Substring(lastIndex + 1);
}
if (string.IsNullOrEmpty(lastWord))
{
lastWord = "Custom";
}
replacementsDictionary["$CustomModule$"] = lastWord;
string installDir = RegistryUtil.GetCalciumInstallDirectory(vsVersion, "1.0");
replacementsDictionary["$InstallDir$"] = installDir;
}
}
RunStarted
方法在项目创建时由 Visual Studio 调用。我们可以看到预定义的 $safeprojectname$
,它实际上是用户输入的(已删除无效字符的)新项目名称,根据前面提到的命名约定进行拆分。
我们可以通过注册表找到 Calcium 程序集的位置。
如果您想知道为什么我选择不将 Calcium 程序集放在 gac 中,那是因为我希望允许通过源代码以及 SDK 安装程序进行自定义。正如我们所知,CLR 在解析程序集时首先在 gac 中查找,因此如果程序集放在 gac 中,开发人员就需要更新所有程序集和配置文件版本号。如果您刚才想:“我不知道 CLR 会先在 gac 中查找!”,那么避免该方法的另一个原因是,我在这里结束我的论点。
在安装 Calcium SDK 期间,我们会记录安装路径。我们还使用位于 $VSVersion$
自定义参数中的 Visual Studio 版本值来识别要使用的注册表项。$VSVersion$
参数在每个 vstemplate 中定义如下:
<CustomParameters>
<CustomParameter Name="$VSVersion$" Value="9.0"/>
</CustomParameters>
因此,我们可以看到自定义参数既可以在 vstemplate 中定义,也可以在代码(在 IWizard
实现中)中定义。
一旦定义了自定义模板参数,就可以被 VS 模板使用。为了使用自定义参数命名项目项,VS 模块模板包含以下摘录:
C#
<ProjectItem ReplaceParameters="true"
TargetFileName="$CustomModule$Module.cs">TemplateModule.cs</ProjectItem>
<ProjectItem ReplaceParameters="true"
TargetFileName="$CustomModule$View.xaml">TemplateView.xaml</ProjectItem>
<ProjectItem ReplaceParameters="true"
TargetFileName="$CustomModule$View.xaml.cs">TemplateView.xaml.cs</ProjectItem>
<ProjectItem ReplaceParameters="true"
TargetFileName="$CustomModule$ViewModel.cs">TemplateViewModel.cs</ProjectItem>
VB.NET
<ProjectItem ReplaceParameters="true"
TargetFileName="$CustomModule$Module.vb">ModuleName1Module.vb</ProjectItem>
<ProjectItem ReplaceParameters="true"
TargetFileName="$CustomModule$View.xaml">ModuleName1View.xaml</ProjectItem>
<ProjectItem ReplaceParameters="true"
TargetFileName="$CustomModule$View.xaml.vb">ModuleName1View.xaml.vb</ProjectItem>
<ProjectItem ReplaceParameters="true"
TargetFileName="$CustomModule$ViewModel.vb">ModuleName1ViewModel.vb</ProjectItem>
例如,物理的 TemplateModule.cs 文件将被复制到新创建的项目中,并且其名称将从 $CustomModule$Module.cs 转换为 DemoModule.cs。
生成的项目项
我们已经了解了幕后发生了什么;现在让我们将注意力转向实际生成的输出。模块的内容在以下摘录中显示:
C#
[Module(ModuleName = "Demo")]
public class DemoModule : ModuleBase
{
/* The location where the view will be placed. */
const string defaultRegion = RegionNames.Tools;
/* The default view for this module. */
DemoView view;
public DemoModule()
{
Initialized += OnInitialized;
}
void OnInitialized(object sender, EventArgs e)
{
view = new DemoView();
/* The view is registered with the view service so that it appears in the 'view' menu. */
var viewService = ServiceLocatorSingleton.Instance.GetInstance<IViewService>();
viewService.RegisterView("Demo", obj => ShowView(defaultRegion, view, true), null, null, null);
/* Populate the defaultRegion with the view. */
ShowView(defaultRegion, view, false);
}
}
VB.NET
<[Module](ModuleName:="Demo")> _
Public Class DemoModule
Inherits ModuleBase
Public Sub New()
AddHandler MyBase.Initialized, New EventHandler(AddressOf Me.OnInitialized)
End Sub
Private Sub OnInitialized(ByVal sender As Object, ByVal e As EventArgs)
Me.view = New DemoView
Dim viewService As IViewService = ServiceLocatorSingleton.Instance.GetInstance(Of IViewService)()
viewService.RegisterView("Demo", AddressOf ShowView2, Nothing, Nothing, Nothing)
ShowView2(Nothing)
End Sub
Private Sub ShowView2(ByVal obj As Object)
MyBase.ShowView(defaultRegion, Me.view, True)
End Sub
Private Const defaultRegion As String = "Tools"
Private view As DemoView
End Class
默认情况下,生成的模块订阅基类 Initialized
事件。当调用处理程序 (OnInitialized
) 时,模块将实例化一个新视图,并使用 IViewService
注册它。我们将在本文后面介绍视图注册。注册完成后,模块将使用基类 ShowView
方法在默认区域中显示新视图。
接下来我们将检查的项目是视图。以下生成的视图 XAML 演示了如何将 ViewControl
的 DataContext
设置为 DemoViewModel
的新实例。
C#
<Gui:ViewControl x:Class="CalciumWinProject1.Demo.DemoView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Gui="clr-namespace:DanielVaughan.Calcium.Client.Gui;assembly=DanielVaughan.Calcium.Client"
xmlns:Module="clr-namespace:CalciumWinProject1.Demo"
DataContext="{Binding ViewModel, RelativeSource={RelativeSource Self}}">
<Gui:ViewControl.ViewModel>
<Module:DemoViewModel />
</Gui:ViewControl.ViewModel>
<Grid>
</Grid>
</Gui:ViewControl>
VB.NET
VB.NET 模板创建了一个几乎相同的 XAML 文件,除了 x:Class
属性,该属性不包含命名空间前缀。
<Gui:ViewControl x:Class="DemoView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Gui="clr-namespace:DanielVaughan.Calcium.Client.Gui;assembly=DanielVaughan.Calcium.Client"
xmlns:Module="clr-namespace:CalciumWinProjectVB.Demo"
DataContext="{Binding ViewModel, RelativeSource={RelativeSource Self}}">
<Gui:ViewControl.ViewModel>
<Module:DemoViewModel />
</Gui:ViewControl.ViewModel>
<Grid>
</Grid>
</Gui:ViewControl>
生成的 DemoView.xaml.cs 和 DemoView.xaml.vb 代码隐藏文件,缺少任何表示逻辑、视图-视图模型连接。
C#
public class DemoViewModel : ViewModelBase
{
public DemoViewModel()
{
TabHeader = "Demo";
}
}
VB.NET
Public Class DemoViewModel
Inherits ViewModelBase
Public Sub New()
MyBase.TabHeader = "Demo"
End Sub
End Class
生成的 ViewModel
派生自 ViewModelBase
类,并且按照约定,在构造函数中设置了 TabHeader
。
服务回顾
自第一个版本发布以来,Calcium 的 IoC 基础设施已进行了彻底的检修。不再明确使用 Unity 容器,因为它已被 Common Service Locator 库取代,以便可以将 IoC 容器替换为您选择的容器。我仍然选择使用单例模式进行服务定位,因为我发现依赖注入在跟踪解析失败时很麻烦。但请随意使用适合您的方法,例如通过构造函数注入进行 DI。ServiceLocatorSingleton
封装了 Microsoft.Practices.ServiceLocation.ServiceLocator
,因此只需要对我的基库的引用。
Microsoft.Practices.ServiceLocation.ServiceLocator
未提供注册类型关联的方法。为此,我在 ServiceLocatorExtensions
类中创建了一组扩展方法。为了提供注册类型关联(在 Unity 以外的 IoC 容器中),请实现 DanielVaughan.ServiceLocationIServiceRegistrar
接口,并将其注册到您要使用的容器中。
/// <summary>
/// Allows setting of type to type, or type to instance associations
/// in the service container.
/// </summary>
public interface IServiceRegistrar
{
void RegisterInstance<TService>(TService service);
void RegisterInstance(Type serviceType, object service);
void RegisterType<TFrom, TTo>() where TTo : TFrom;
bool IsTypeRegistered(Type type);
}
然后,ServiceLocatorSingleton
将利用该类型来注册类型关联。
视图服务
IViewService
实现允许 UIElements
与工作区内容进行关联和交互,并提供一种能力,使得 UIElement
的可见性根据视图或视图模型类型的可见性或活动状态进行更改。
可见性关联
IViewService
的一项功能是它允许根据活动视图的类型来隐藏或显示 Shell 中的任何 UI 元素。当我们使用已知的类型关联 UIElement
(例如 ToolBar
)时,如果当前活动视图是该类型,则它将可见,否则将被隐藏。我们在 Web 浏览器模块中看到了这一点:告诉服务如果活动视图是 IWebBrowserView
,则隐藏工具栏。
/* Add web browser toolbar. */
var toolBarProvider = new WebBrowserToolBar { Url = startUrl };
var toolBar = toolBarProvider.ToolBar;
regionManager.Regions[RegionNames.StandardToolBarTray].Add(toolBar);
var viewService = UnitySingleton.Container.Resolve<IViewService>();
viewService.AssociateVisibility(typeof(IWebBrowserView),
new UIElementAdapter(toolBar), Visibility.Collapsed);
在这里,我们将工具栏放在一个名为 WebBrowserToolBar
的 WPF UserControl
中,以便我们获得设计器支持。我们添加 ToolBar
,它作为提供程序中的公共属性公开。请注意,有必要从自定义控件的父项中移除工具栏,因为一个 Visual Element 只能有一个父项。
此时我们可以停止,工具栏将始终显示在 Shell 中。但是,为了根据视图隐藏和显示工具栏,我们使用 IViewService
实例。特别是,我们使用 AssociateVisibility
方法将 IWebBrowserView
类型与工具栏相关联。效果是当 Shell 中活动视图实现 IWebBrowserView
接口时显示工具栏,否则隐藏工具栏。
这里有几点需要注意。首先,我们指定隐藏状态将是 Collapsed
。这自然会移除该项的所有痕迹,并允许其他元素占据其空间。它也可以是 Hidden
,这将使该项不可见,但保留该元素所占用的空间。这并非什么高深莫测的技术。
其次,我们使用 UIElementAdapter
来包装 UIElement
。为什么?嗯,为了模拟 System.Windows.UIElement
,我认为创建一个可重用的包装器和相关的接口 IUIElement
是审慎的。您知道,UIElement.Visibility
是一个依赖属性,它使用验证来防止任意设置。因此,我们使用一个可模拟的接口和一个适配器。
以下内容展示了如何使用 IUIElement
接口测试 IViewService
。
[TestMethod]
public void ViewServiceShouldChangeVisibility()
{
var uiElement = MockRepository.GenerateStub<IUIElement>();
uiElement.Visibility = Visibility.Visible;
var viewService = UnityResolver.UnityContainer.Resolve<IViewService>();
viewService.AssociateVisibility(typeof(IViewDummyContent), uiElement, Visibility.Hidden);
/* The first view to test will not implement the associated type,
* and should cause our the uiElement to be hidden. */
var view = MockRepository.GenerateStub<IView>();
/* Raise the event that indicates that the view has changed. */
selectedDocumentViewChangedEvent.Publish(view);
Assert.AreEqual(Visibility.Hidden, uiElement.Visibility);
MockRepository mockRepository = new MockRepository();
/* We create a new view, but this time it will implement
* the associated interface and should cause our ui element to be shown. */
var viewWithInterface = mockRepository.DynamicMultiMock<IView>(typeof(IViewDummyContent));
/* Raise view changed again. */
selectedDocumentViewChangedEvent.Publish(viewWithInterface);
Assert.AreEqual(Visibility.Visible, uiElement.Visibility);
}
视图注册
IViewService
的另一项功能是注册视图的能力,以便在应用程序的视图菜单中显示它。Visual Studio 模块模板会自动为您设置好,如下面的摘录所示,当您在 Visual Studio 中的“新建项目”对话框中选择 Calcium Module 时。
void OnInitialized(object sender, EventArgs e)
{
view = new DemoView();
/* The view is registered with the view service so that it appears in the 'view' menu. */
var viewService = ServiceLocatorSingleton.Instance.GetInstance<IViewService>();
viewService.RegisterView("Demo", obj => ShowView(defaultRegion, view, true), null, null, null);
/* Populate the defaultRegion with the view. */
ShowView(defaultRegion, view, false);
}
IViewService
有两个重载用于 RegisterView 方法。
RegisterView
方法设计用于接受一个 Func getDisplayName
,它应该检索在视图菜单中显示的文本。还有一个执行视图打开的操作,因此当点击视图菜单项或执行键盘快捷键时,将执行操作 showView
。
图:Demo 模块在注册了 ViewService 后自动放置在视图菜单中。
在撰写本文时,我尚未实现显示图标的方法,因此目前它只是一个占位符。IViewService
的默认实现是 ViewService
,以下是显示其中一个视图注册方法的摘录。
public void RegisterView(Func<object, object> getDisplayName,
Action<object> showView, object showViewParam,
KeyGesture showGesture, Func<object, object> getIcon)
{
var regionManager = ServiceLocatorSingleton.Instance.GetInstance<IRegionManager>();
IRegion viewRegion;
try
{
viewRegion = regionManager.Regions[MenuNames.View];
}
catch (KeyNotFoundException ex)
{
/* TODO: Make localizable resource. */
throw new UserMessageException(MenuNames.View
+ " region was not found.", ex);
}
var commandParameter = new OpenViewCommandParameter(showView, showViewParam);
var menuItem = new MenuItem
{
Command = ShowViewCommand,
Header = getDisplayName(showViewParam),
CommandParameter = commandParameter
};
viewRegion.Add(menuItem);
}
class OpenViewCommandParameter
{
public Action<object> EventAction { get; private set; }
public object Param { get; private set; }
public OpenViewCommandParameter(Action<object> eventAction, object param)
{
EventAction = eventAction;
Param = param;
}
}
文件服务
乍一看,IFil
eService
接口似乎只是 OpenFileDialog
和 SaveFileDialog
的抽象。但它远不止于此。它确实以平台无关的方式处理显示对话框和处理常见的 IO 异常。它还允许用户选择文件的其他替代位置,并向用户提供反馈。
文件服务不负责保存或加载文件。它只负责选择文件。我想在某些方面,它可以被称为文件选择服务。由调用者指定处理程序来保存或加载文件是其任务。这样我们就可以在处理常见错误时保持一致性。我预计,未来将进一步抽象对话框过滤器参数,因为使用已知关联管理器来处理它可能会更好。
使用文件服务时,我们可以非常简洁。例如,在以下摘录中,我们看到 TextEditorModule
如何能够接管提供对话框的通用任务。它将尝试读取文件并处理任何 IO 异常,通知用户,在必要时重试,等等。
var fileService = ServiceLocatorSingleton.Instance.GetInstance<IFileService>();
string fileNameUsed = null;
string textFromFile = null;
FileOperationResult operationResult = fileService.Open(
name =>
{
textFromFile = File.ReadAllText(name);
fileNameUsed = name;
}, FileErrorAction.InformOnly, fileFilter);
if (operationResult == FileOperationResult.Successful)
{
/* Create the TextEditorView as we have the content from the file. */
}
理解此 API 的关键在于认识到 FileOperationHandler
负责记录使用的文件名。一旦我们成功打开或保存了文件,FileOperationHandler
就应该保留使用的文件名。将实际 IO 任务等完全委托给处理程序的灵活性不应被低估。赞美 Mike Krüger,他之前是 #SharpDevelop 项目的成员,我多年前就是从他那里了解到这种技术的。
在其他场景中,如果处理程序失败,我们可以允许用户为保存文件选择一个替代位置。
图:文件服务保存文件并进行错误处理。
图:文件服务打开文件并自动显示对话框。
重塑 Calcium 品牌
以前,重塑 Calcium 品牌(移除 Calcium logo 等)只能通过修改源代码版本来实现。此后,我提供了移除/替换应用程序中所有 Calcium 品牌痕迹的方法,而无需弄脏双手。
隐藏 Calcium 横幅 Logo
可以通过从服务定位器检索 IShell
实例,并将 LogoVisible
属性设置为 false 来隐藏 Calcium 横幅 logo,如下面的摘录所示。
var shell = ServiceLocatorSingleton.Instance.GetInstance<IShell>();
shell.LogoVisible = false;
填充横幅区域
Shell 横幅现在是一个 Prism 区域。这意味着您可以检索它并用 UIElements
填充它。
<Border x:Name="border_Banner" Grid.Row="0" Grid.ColumnSpan="3">
<Expander x:Name="expander_Banner" IsExpanded="True" ExpandDirection="Down"
Style="{DynamicResource ExpanderStyle}"
Expanded="Expander_Banner_ExpandChanged"
Collapsed="Expander_Banner_ExpandChanged"
Visibility="{Binding BannerVisible, Converter={StaticResource booleanToVisibilityConverter}}">
<StackPanel cal:RegionManager.RegionName="{x:Static Desktop:RegionNames.Banner}"
Orientation="Horizontal">
<Controls:TitleBanner x:Name="titleBanner" HorizontalAlignment="Stretch" Padding="0, 0, 10, 5"
Visibility="{Binding LogoVisible, Converter={StaticResource booleanToVisibilityConverter}}"/>
</StackPanel>
</Expander>
</Border>
为了实现这一点,我创建了一个自定义 Prism 区域适配器,称为 PanelRegionAdapter
。它在 Bootstrapper
的 ConfigureRegionAdapterMappings
方法中与 Panel 类型相关联,如下面的摘录所示:
var mappings = base.ConfigureRegionAdapterMappings()
?? Container.Resolve<RegionAdapterMappings>();
mappings.RegisterMapping(typeof(Panel), Container.Resolve<PanelRegionAdapter>());
然后使用区域适配器填充 StackPanel
。
隐藏整个横幅
如果您想节省空间,并且不想显示横幅,可以像这样隐藏它:
var mainWindow = ServiceLocatorSingleton.Instance.GetInstance<IMainWindow>();
mainWindow.BannerVisible = false;
切换关于框
现在可以通过在 ServiceLocatorSingleton
中注册 IAboutBox
类型关联来替换关于框。
ServiceLocatorSingleton.Instance.RegisterType<IAboutBox, MyAboutBox>();
当然,MyAboutBox
必须实现 IAboutBox
接口。
更改窗口标题文本
窗口标题现在是可自定义的,并且还支持字符串格式化。因此,我们可以注册一个本地化的字符串格式,当标题设置时,它将被应用。例如:
var mainWindow = ServiceLocatorSingleton.Instance.GetInstance<DanielVaughan.Gui.IMainWindow>();
mainWindow.Title = "project1";
mainWindow.TitleFormat = "{0} - My Application";
这样工作的方式是,Shell 窗口的标题与其视图模型 TabHeader
属性进行数据绑定。
Title="{Binding TabHeader}"
每当格式更改时,我们都会在代码中设置标题绑定的 StringFormat
属性。这是 DesktopShellView
类中的一段摘录:
void SetTitleFormat(string format)
{
Binding tabHeaderBinding = new Binding("TabHeader")
{
Source = DataContext,
StringFormat = format
};
SetBinding(TitleProperty, tabHeaderBinding);
ShellViewModel.TitleFormat = format;
}
更改启动画面图像
通过使用 StartupOptions
,可以替换启动画面图像,用代表您产品的图像。
var starter = new AppStarter();
starter.StartupOptions.SplashImagePackUri
= new Uri("pack://application:,,,/YourAssembly;component/Resources/Images/YourImage.jpg");
starter.Start();
隐藏 Calcium 启动画面宣传语
默认情况下,将在启动画面的左下角显示一段文本。如果您不喜欢这样,则可以通过再次使用 AppStarter
类的 StartupOptions
来移除它,如下所示:
starter.StartupOptions.SplashBlurbVisible = false;
在未来的版本中,我将包含一个复合事件,该事件允许启动画面中的文本字段向用户显示加载信息。
更改 Web 浏览器起始 URL
默认情况下,Web 浏览器将尝试加载默认 URL。要更改此行为,请修改您的启动应用程序的 app.config。
<appSettings>
<add key="WebBrowserStartUrl" value="http://www.example.com/"/>
</appSettings>
Silverlight 版 Calcium
查看代码库时,您可能会发现各种地方有#if Silverlight 预处理器指令。是的,我正在将 Calcium 移植到 Silverlight。我的目标是让 Calcium 中的大部分代码能够同时支持桌面和 Silverlight CLR。因此,Calcium 应用程序中的 Module
或 ViewModel
可以用于同时支持 Silverlight 和 WPF。这一目标的实现一直受到 Silverlight 中缺少菜单和工具栏控件的阻碍。我收到了一些请求,要求在 WPF 版本中提供 Office 2007 功能区控件,并且我偶然发现有一个相当不错的 Silverlight 实现。因此,根据功能区许可的兼容性,我可能会在这两个平台上提供支持。我们将拭目以待。
结论
在本文中,我们回顾了 Calcium 项目自几个月前首次发布以来发生的一些变化。我们已经看到使用自定义项目项如何使使用 Calcium 变得更加容易,并且我们深入研究了为 Calcium 创建 VS 模板的幕后情况。我们了解了服务定位基础设施的变化,并研究了如何使用 IViewService
来隐藏和显示 UIElements
。我们还看到了 IFileService 如何以平台无关的方式处理显示对话框和常见 IO 异常。
在下一篇文章中,我们将转向文本编辑器模块,并观察 TextEditorViewModel
如何使用内容接口方法来处理文件更改。我们还将研究一些其他服务,包括通信服务及其相关模块。
随着 Calcium 开发的继续,我知道我们还有很多内容需要介绍,希望您能与我一起参加下一期。
如果您有功能请求,或发现 bug,请将其发布到 Calcium Codeplex 问题跟踪器,或为您已有的问题投票。
我希望您觉得这个项目有用。如果有用,我将不胜感激您能对其进行评分和/或在下方留下反馈。这将帮助我写出更好的下一篇文章。
历史
2009 年 11 月
- 首次发布。