How to create a common WPF base window style?

2019-01-14 20:16发布

问题:

Is there any recommended way with WPF to create a common window style to be used across an application? I have several dialogs that appear in my app, and I would like them all to be styled the same (same window border, ok/cancel button position, etc) and simply have different 'content' in each, depending on the situation. So, one dialog might have a list box in it, one might have a textbox, and so on.

I understand how to make base .cs usercontrol files, but I can't for the life of me work out a good way to create a single window which can host different content when launched?

Cheers, rJ

回答1:

One way to do it would be a new custom control, let's call it DialogShell:

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

This now needs a template which would normally be defined in Themes/Generic.xaml, there you can create the default structure and bind the 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>

This is just an example, you probably want to hook up those buttons with custom events and properties you need to define in the cs-file.

This shell then can be used like this:

<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();
        }
    }
}

Event wiring example (not sure if this is the "correct" approach though)

<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;
                };
        }
    }
}


回答2:

To add to H.B.'s very helpful post, you may want to connect your event handlers in the loaded event as he's done but instead of using anonymous methods or lambda expressions, consider connecting them to protected virtual methods which can be overridden in the derived class should the functionality need to vary. In my case, I created a base data entry form which has buttons for saving and cancelling:

    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) { }

The save functionality is then overridden in each derived class to save the specific entity:

    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;
        }            
    }


回答3:

You can use define a style in App.Xaml that targets all windows.

This is a sample of how your App.Xaml may look like:

<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>

Then for more advanced scenarios you may need to set the ControlTemplate for your Window.



回答4:

Creating a cusotm object , which is derived from Window Class..

http://maffelu.net/wpf-window-inheritance-problems-and-problems/



回答5:

Create a Xaml Form template and add the template to the VS Installed ItemTemplates directory.

1) create a wpf xaml and xaml.cs file that has all the desired components wanted for a new form added to your application. In my case I wanted the title and toolbar buttons.

2) test the new xaml files through the current system flow.

3) copy xaml / xaml.cs to temp location and rename both the filenames to something you want to be recognized as a good template name. a) Change first line within xaml file to -- Window x:Class="$rootnamespace$.$safeitemname$"

b) Make 3 changes within xaml.cs file to ensure the new name will be copied when using the template - -- namespace $rootnamespace$ (//dynamic namespace name) -- public partial class $safeitemname$ (//dynamic class name) -- public $safeitemname$() (//dynamic constructor name)

4) Now create a vstemplate file: ie. MyTemplate.vstemplate with the following content:

<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) Once you have all these files, zip the files and place the zip file under the ....\Documents\Visual Studio 2012\Templates\ItemTemplates\WPF directory. Now you can go into VS2012 and use the ADD\New feature to see the template, select and rename as in the normal process. The template can be used in the same way for VS2010 by placing the zip file under the 2010 Templates Wpf directory.

The Logo file should be included in the zip file as well or if you don't have a file then remove that line from the MyTemplate.vstemplate file.



标签: wpf xaml