如何创建一个通用的WPF基础窗口样式?

3
有没有在WPF中创建一个通用窗口样式的推荐方法,可以在整个应用程序中使用?我有几个对话框出现在我的应用程序中,我希望它们都具有相同的样式(相同的窗口边框,确定/取消按钮位置等),并根据情况在每个对话框中具有不同的“内容”。 因此,一个对话框可能会在其中放置一个列表框,另一个可能会有一个文本框,依此类推。
我知道如何制作基本的.cs用户控件文件,但是我无法想出一种好的方法来创建一个可以在启动时承载不同内容的单个窗口?
谢谢, rJ
5个回答

19

有一种方法是创建一个新的自定义控件,让我们称其为DialogShell

namespace Test.Dialogs
{
    public class DialogShell : Window
    {
        static DialogShell()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(DialogShell), new FrameworkPropertyMetadata(typeof(DialogShell)));
        }
    }
}

现在需要一个模板,通常应该在Themes/Generic.xaml中定义,你可以在那里创建默认的结构并绑定Content

这句话意思是,需要创建一个模板来定义一个控件的外观和行为,模板通常会在Themes/Generic.xaml文件中定义。你可以在模板中定义控件的默认结构,并将Content属性与其它控件或数据进行绑定。

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Test.Dialogs">
    <Style TargetType="{x:Type local:DialogShell}" BasedOn="{StaticResource {x:Type Window}}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:DialogShell}">
                    <Grid Background="{TemplateBinding Background}">
                        <Grid.RowDefinitions>
                            <RowDefinition />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <!-- This ContentPresenter automatically binds to the Content of the Window -->
                        <ContentPresenter />
                        <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="5" HorizontalAlignment="Right">
                            <Button Width="100" Content="OK" IsDefault="True" />
                            <Button Width="100" Content="Cancel" IsCancel="True" />
                        </StackPanel>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

这只是一个例子,您可能希望将这些按钮与您在cs文件中定义的自定义事件和属性进行连接。

然后可以像这样使用此 shell:

<diag:DialogShell x:Class="Test.Dialogs.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:diag="clr-namespace:Test.Dialogs"
        Title="Window1" Height="300" Width="300">
    <Grid>
        <TextBlock Text="Lorem Ipsum" />
    </Grid>
</diag:DialogShell>
namespace Test.Dialogs
{
    public partial class Window1 : DialogShell
    {
        public Window1()
        {
            InitializeComponent();
        }
    }
}
事件绑定示例(不确定这是否是“正确”的方法)
<Button Name="PART_OKButton" Width="100" Content="OK" IsDefault="True" />
<Button Name="PART_CancelButton" Width="100" Content="Cancel" IsCancel="True" />
namespace Test.Dialogs
{
    [TemplatePart(Name = "PART_OKButton", Type = typeof(Button))]
    [TemplatePart(Name = "PART_CancelButton", Type = typeof(Button))]
    public class DialogShell : Window
    {
        //...

        public DialogShell()
        {
            Loaded += (_, __) =>
                {
                    var okButton = (Button)Template.FindName("PART_OKButton", this);
                    var cancelButton = (Button)Template.FindName("PART_CancelButton", this);
                    okButton.Click += (s, e) => DialogResult = true;
                    cancelButton.Click += (s, e) => DialogResult = false;
                };
        }
    }
}

谢谢,这似乎让我朝着正确的方向前进了。我该如何在DialogShell.XAML中为按钮设置事件处理程序?由于无法将x:class应用于ResourceDictionary,所以该怎么办? - RJ Lohan
@RJLohan:你可以给按钮分配名称,并通过在代码后台中再次查找它们来连接它们(构造函数太早了,Loaded事件可行)。如果只有OK和Cancel,您可能甚至不想公开这些按钮的事件,因为它们应该只将DialogResult设置为true或false。(如果您这样做,请将这些按钮作为template-parts,以便创建主题的人知道按钮应该如何命名) - H.B.
1
@RJLohan:大多数主题没有为窗口定义样式,我建议您放弃“BasedOn”,并在“Template”之外使用setter将“Background”设置为“White”等。 - H.B.
@RJLohan:实际上,像Aero这样的主题确实为窗口定义了一种样式,您可以使用此Setter更改背景色:<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/> - H.B.
考虑将 AdornerDecorator 添加到新控件模板中(每个普通窗口都有一个),否则验证覆盖和总体上的装饰效果就不会显示。 - user3391859
显示剩余3条评论

2

补充H.B.非常有帮助的帖子,您可能希望像他一样在已加载的事件中连接事件处理程序,但不要使用匿名方法或lambda表达式,考虑将它们连接到受保护的虚拟方法中,如果功能需要变化,则可以在派生类中重写它们。在我的情况下,我创建了一个基本的数据输入表单,其中包含保存和取消按钮:

    public DataEntryBase()
    {
        Loaded += (_, __) =>
        {
            var saveButton = (Button)Template.FindName("PART_SaveAndCloseButton", this);
            var cancelButton = (Button)Template.FindName("PART_CancelButton", this);
            saveButton.Click += SaveAndClose_Click;
            cancelButton.Click += Cancel_Click;
        };
    }

    protected virtual void SaveAndClose_Click(object sender, RoutedEventArgs e) { DialogResult = true; }

    protected virtual void Cancel_Click(object sender, RoutedEventArgs e) { }

保存功能在每个派生类中被覆盖以保存特定实体:
    protected override void SaveAndClose_Click(object sender, RoutedEventArgs e)
    {
        if (Save())
        {
            base.SaveAndClose_Click(sender, e);
        }
    }

    private bool Save()
    {
        Contact item = contactController.SaveAndReturnContact((Contact)DataContext);
        if (item!=null) 
        {
            DataContext = item;
            return true; }
        else 
        {
            MessageBox.Show("The contact was not saved, something bad happened :(");
            return false;
        }            
    }

顺便说一下,Close() 是多余的,设置 DialogResult 就可以关闭窗口了。 - H.B.
是的,我后来发现了这一点... 例如被修改。 - Erikest

0

你可以在 App.Xaml 中定义一个针对所有窗口的样式。

下面是一个 App.Xaml 的示例:

<Application x:Class="ES.UX.App"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         StartupUri="Views/MainWindow.xaml">
<Application.Resources>
    <Style TargetType="Window">
        <Setter Property="WindowStyle" Value="ToolWindow" />
    </Style>
</Application.Resources>

对于更高级的场景,您可能需要为窗口设置ControlTemplate。


谢谢,但在我的情况下,“样式”也包括布局,因此这种方法并不能真正实现太多,因为我仍然需要重新设计控件以在适当的位置提供内容。 - RJ Lohan

0

创建一个Xaml表单模板并将其添加到VS安装的ItemTemplates目录中。

1)创建一个WPF Xaml和Xaml.cs文件,其中包含您想要添加到应用程序中的新表单所需的所有组件。在我的情况下,我想要标题和工具栏按钮。

2)通过当前系统流程测试新的xaml文件。

3)将xaml / xaml.cs复制到临时位置,并将两个文件名重命名为您想要识别为良好模板名称的内容。 a)更改xaml文件中的第一行为 - Window x:Class =“$ rootnamespace $。$ safeitemname $”

b)在xaml.cs文件中进行3个更改,以确保在使用模板时将复制新名称 - -- namespace $rootnamespace$ (//动态命名空间名称) -- public partial class $safeitemname$ (//动态类名) -- public $safeitemname$() (//动态构造函数名称)

4)现在创建一个vstemplate文件:即MyTemplate.vstemplate,其中包含以下内容:

<VSTemplate Version="3.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Item">
  <TemplateData>
    <DefaultName>WpfFormTemplate.xaml</DefaultName>
    <Name>WpfFormTemplate</Name>
    <Description>Wpf/Entities form</Description>
    <ProjectType>CSharp</ProjectType>
    <SortOrder>10</SortOrder>
    <Icon>Logo.ico</Icon>
  </TemplateData>
  <TemplateContent>
    <References>
        <Reference>
            <Assembly>System.Windows.Forms</Assembly>
        </Reference>
        <Reference>
            <Assembly>Workplace.Data.EntitiesModel</Assembly>
        </Reference>
        <Reference>
            <Assembly>Workplace.Forms.MainFormAssemb</Assembly>
        </Reference>
    </References>
    <ProjectItem SubType="Designer" TargetFileName="$fileinputname$.xaml" ReplaceParameters="true">WpfFormTemplate.xaml</ProjectItem>
    <ProjectItem SubType="Code" TargetFileName="$fileinputname$.xaml.cs" ReplaceParameters="true">WpfFormTemplate.xaml.cs</ProjectItem>
  </TemplateContent>
</VSTemplate>

5) 一旦您拥有了所有这些文件,请将它们压缩并将压缩文件放置在....\Documents\Visual Studio 2012\Templates\ItemTemplates\WPF目录下。现在,您可以进入VS2012并使用ADD\New功能查看模板,选择并重命名,就像正常流程一样。通过将zip文件放置在2010 Templates Wpf目录下,也可以以同样的方式在VS2010中使用该模板。

Logo文件也应包含在zip文件中,如果没有该文件,则从MyTemplate.vstemplate文件中删除该行。


我不确定这是否与共享/公共基础窗口完全相同。这听起来只是一个模板,可以将代码有效地复制粘贴到新窗口中? - RJ Lohan

0

2
你能否详细阐述一下你的答案?链接往往会随着时间而失效。 - orique

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接