Styles, Templates, and ResourceDictionaries

半世苍凉 提交于 2019-12-12 20:43:15

问题


I've been doing some WPF projects for a while now, but I've stayed away from templates and resource dictionaries for the most part because they are a cluster#. In order to get to the next level I've done a lot of googling trying to find best practices for applying styles using resource dictionaries and so on, but it seems likes best practices are based on habits and preferences. Most examples I've come across either assume you already know everything or don't include all the different situations. The worst part is that there are some known bugs and situations that affect performance, but understanding what those issues are and correcting them is another hassle all on its own.

Sorry for the rant. Here are situations I need to work out:

Let's say I have a default rd (resource dictionary) that has styles targeting textblocks, buttons, textboxes, etc. Then, I have another similar resource dictionary that applies to something like an Expander (or TabItem) that's a collection of those same controls (button, textboxes,...).

  1. Is it okay to have that sort of setup?

  2. How do you go about overriding the default rd for controls that are located inside a specific expander? I've tried adding a reference to the resource dictionary inside the expander's resources; however, the default rd seems to override that style.

  3. What's the purpose of templates and when would you use those?

Here is my current setup:

App.xaml:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Resources/rdMainStyle.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

rdMainStyle.xaml:

<Style x:Key="baseStyle" TargetType="FrameworkElement">
    <Setter Property="Width" Value="Auto"/>
    <Setter Property="Height" Value="Auto"/>
    <Setter Property="MinWidth" Value="70pt"/>
</Style>

<Style TargetType="{x:Type Button}" BasedOn="{StaticResource baseStyle}">
    <Setter Property="Margin" Value="5, 0, 0, 5"/>
    <Setter Property="HorizontalAlignment" Value="Center"/>
    <Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource baseStyle}">
    <Setter Property="MinWidth" Value="65pt"/>
    <Setter Property="Margin" Value="5, 0, 0, 5"/>
</Style>

rdExpanderStyle.xaml:

<Style x:Key="expanderBaseStyle" TargetType="FrameworkElement">
    <Setter Property="Height" Value="Auto"/>
    <Setter Property="Margin" Value="5, 0, 0, 5"/>
</Style>

<Style TargetType="{x:Type Button}" BasedOn="{StaticResource expanderBaseStyle}">
    <Setter Property="Width" Value="70pt"/>
    <Setter Property="HorizontalAlignment" Value="Center"/>
    <Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource expanderBaseStyle}">
    <Setter Property="Width" Value="65pt"/>
</Style>

Inside the main window:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <Expander Grid.Row="0" Name="expSync" Header="More Options">
        <Expander.Resources>
            <ResourceDictionary Source="Resources/rdExpanderStyle.xaml" />
        </Expander.Resources>
    </Expander>
    <StackPanel Margin="0,5,0,0">
        <TextBlock Text="bluh"/>
        <Button  Content="Test"  />
    </StackPanel>
</Grid>

回答1:


  1. Is it okay to have that sort of setup?

Yes, it's fine. In fact, styling/templates/themes/etc. in WPF are all about this kind of hierarchy. I.e. being able to define a default in one place, but then overriding it with a new definition that takes higher precedence.

  1. How do you go about overriding the default rd for controls that are located inside a specific expander? I've tried adding a reference to the resource dictionary inside the expander's resources; however, the default rd seems to override that style.

The problem is related to the fact that your content for the Expander.Header property is a simple string. This gets mapped to a custom style for the ContentPresenter class (itself being part of the custom style for the Expander class), and so is ignored when you attempt set the style using a locally declared style. Your later attempt to override the style is actually too far down in the hierarchy of styles. The global style works because that style is applied earlier than the custom style being used for the control.

You can work around the issue by providing a more explicit definition for the content of the Expander control. For example:

<Expander Grid.Row="0" Name="expSync">
  <Expander.Resources>
    <ResourceDictionary Source="Resources/rdExpanderStyle.xaml" />
  </Expander.Resources>
  <Expander.Header>
    <TextBlock Text="More Options"/>
  </Expander.Header>
</Expander>

By explicitly providing a TextBlock as the content, you wind up with a control instance that comes after the styles imported from the rdExpanderStyle.xaml file there, and they will be applied as you want.

  1. What's the purpose of templates and when would you use those?

A full discussion of that would be outside the scope of a reasonable Stack Overflow answer. There are lots of resources available on MSDN, Stack Overflow, and elsewhere describing how templates are used in WPF. That said…

The purpose of a template is to provide a pattern for a control to follow when instantiated. In some cases, you are declaring the template for a specific control, in other cases you are declaring the template for a "view model" data type. The former case is about customizing the appearance and behavior of an existing control type; the latter case is about mapping your business logic data structure to a visual appearance in an abstract, decoupled, reusable way.

When would you use them? Well, I would say the purpose answers that question. If you need to customize a templatable control, you would use a template to do that. If you need to present a view model object as a composite of controls, you would use a template to do that (e.g. when your view model represents items in an ItemsControl). I would say that the view model scenario is more prevalent than customizing controls via templates, but that's not based on hard evidence; it's just my general sense of the situation.


Finally, a bit of advice that I mean no offense by:

While your rant is understandable, it's not productive and it is more about your own inexperience than about the actual quality of WPF. I know, because I've been there myself. Yes, WPF is complicated and hard to learn. Yes, it does have a number of places where the behaviors are not immediately intuitive or obvious.

But: a) the state of templates and resources in WPF is not even close to being (in your words…I believe I got the intended meaning) "a cluster#"; to the contrary, these are fundamental elements of WPF, have a design that is logical if very complex, and work quite well when used correctly, and b) there are in fact reasonable, objective "best practices" for dealing with styles and resource dictionaries, based in large part on the same kind of philosophy that underpins design in OOP (i.e. define as much as you can broadly, specialize when necessary to avoid special-case code in the base definitions).

(I should've stopped there, but the unsolicited advice also comes with some unsolicited story-telling… :) )

Over the years, I've found myself ranting about a great number of APIs, platforms, etc. But as I've gotten older, the rants are fewer and farther between, and I've been forced to acknowledge that many of my rants were really about me taking my frustration out on something I didn't fully understand yet. Yes, there are exception…occasionally, one of my rants still hits a fair target. But I've mostly found that learning a new programming environment requires an attitude adjustment, to seek the patterns of thinking that drive the design of that environment, and to retrain myself to follow those same patterns, so that I see the environment the way the designers see it.

Don't worry. You'll still have opportunities to rant. Early in my programming career, I had just learned the lesson that if your program's not working, it's always a bug in your program and not in the OS, when I found myself writing code for an experimental computer that still required hardware engineers to fiddle with it almost daily to keep it running correctly. It took me several days to learn to accommodate the idea that that very important lesson I'd just learned needed to be changed to mean "it's almost always a bug in your program". That was decades ago, and computers have gotten a lot more complicated since then, so there are more opportunities for the programming environment rather than your own program to be flawed.

But don't forget: it's still a lot more common for the person writing the application side of the code to have made a mistake than for the person writing the system side. If nothing else, the system side code is tested a lot better, just by being used by large numbers of people just like you. :)



来源:https://stackoverflow.com/questions/35734912/styles-templates-and-resourcedictionaries

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!