2015-08-14 30 views
11

Đây là một thực hành tốt nhất câu hỏi liên quan WPF themeing và đặc biệt hơn lột da. Đây là một câu hỏi dựa trên ý kiến ​​vì tôi không có vấn đề gì với công việc này nhưng nhiều người tự hỏi liệu kết luận của tôi có bao gồm tất cả các kịch bản hay không và liệu có ai khác có cùng suy nghĩ về vấn đề này không cách tiếp cận của họ.WPF themeing thực hành tốt nhất

Một số nền tảng, Nhóm của chúng tôi được yêu cầu xác định cách để cung cấp cho hệ thống của chúng tôi khả năng là có thể sử dụng được.

Chúng tôi đã phá vỡ khả năng này xuống đến 2 loại:

1) Các phong cách kiểm soát của chúng tôi mà chúng tôi chỉ đơn giản gọi là 'Theme'.

2) Các tài nguyên mà họ sử dụng để tùy chỉnh sự xuất hiện của họ gọi là 'Skin' này bao gồm Brushes, và tất cả các loại cấu trúc cỡ như CornerRadius, BorderThickness, vv

Cách đó một Skin được thiết lập cho hệ thống là một trường hợp đơn giản của việc kết hợp từ điển da cuối cùng vào tài nguyên của ứng dụng của chúng tôi.

<Application.Resources> 
    <ResourceDictionary> 
     <ResourceDictionary.MergedDictionaries> 
      <ResourceDictionary Source="Default.skin.xaml" /> 
      <ResourceDictionary Source="Theme.xaml" /> 
     </ResourceDictionary.MergedDictionaries> 
    </ResourceDictionary> 
</Application.Resources> 

Một làn da khác được hợp nhất cuối cùng vào ứng dụng của chúng tôi.

protected override void OnStartup(StartupEventArgs e) 
    { 
     base.OnStartup(e); 

     string skin = e.Args[0]; 
     if (skin == "Blue") 
     {   . 
      ResourceDictionary blueSkin = new ResourceDictionary(); 
      blueSkin.Source = new Uri("Blue.skin.xaml", UriKind.Relative); 

      Application.Current.Resources.MergedDictionaries.Add(blueSkin); 
     } 
    } 

Bên Theme.xaml:

<!-- Region TextBox ControlTemplate --> 

<ControlTemplate TargetType="{x:Type TextBox}" x:Key="TextBoxTemplate"> 
    <Border Background="{TemplateBinding Background}" 
     BorderBrush="{TemplateBinding BorderBrush}" 
     BorderThickness="{TemplateBinding BorderThickness}" 
     CornerRadius="{StaticResource TextBoxCornerRadius}" > 
     <Border x:Name="shadowBorder" BorderBrush="{StaticResource TextBoxShadowBrush}"         
     CornerRadius="{StaticResource TextBoxInnerShadowCornerRadius}" 
     BorderThickness="{StaticResource TextBoxInnerShadowBorderThickness}" 
     Margin="{StaticResource TextBoxInnerShadowNegativeMarginForShadowOverlap}" > 
      <ScrollViewer x:Name="PART_ContentHost" Padding="{TemplateBinding Padding}" 
        VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 
        HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" /> 
     </Border> 
    </Border> 

    <ControlTemplate.Triggers> 
     <Trigger Property="BorderThickness" Value="0"> 
      <Setter TargetName="shadowBorder" Property="BorderThickness" Value="0" /> 
     </Trigger> 
    </ControlTemplate.Triggers> 
</ControlTemplate> 

<!-- EndRegion --> 

<!-- Region TextBox Style --> 

<Style x:Key="{x:Type TextBox}" TargetType="{x:Type TextBox}">  
    <Setter Property="BorderBrush" Value="{StaticResource TextBoxBorderBrush}" /> 
    <Setter Property="Background" Value="{StaticResource TextBoxBackgroundBrush}" /> 
    <Setter Property="BorderThickness" Value="{StaticResource TextBoxBorderThickness}" /> 

    <Setter Property="Padding" Value="{StaticResource TextBoxPadding}" /> 
    <Setter Property="Template" Value="{StaticResource TextBoxTemplate}"/> 
    <Style.Triggers> 

     <Trigger Property="IsMouseOver" Value="True"> 
      <Setter Property="Background" Value="{StaticResource TextBoxIsMouseOverBackgroundBrush}" /> 
      <Setter Property="BorderBrush" Value="{StaticResource TextBoxIsMouseOverBorderBrush}" /> 
     </Trigger> 
     <Trigger Property="IsFocused" Value="True"> 
      <Setter Property="Background" Value="{StaticResource TextBoxIsMouseWithinBackgroundBrush}" /> 
      <Setter Property="BorderBrush" Value="{StaticResource TextBoxIsMouseWithinBorderBrush}" /> 
     </Trigger> 

    </Style.Triggers> 
</Style> 

<!-- EndRegion --> 

Trong TextBox ControlTemplate có những yếu tố ràng buộc để DependencyProperties sử dụng TemplateBinding và một số giống như CornerRadius và InnerCornerRadius, InnerBorderThickness và InnerBorderBrush được gán với giá trị của họ từ tài nguyên.

Cách tiếp cận tốt nhất là gì?

tạo điều khiển dẫn xuất với các thuộc tính phụ thuộc có liên quan sẽ tham chiếu các tài nguyên có liên quan và sau đó có các phần tử trong mẫu kiểm soát liên kết với chúng.

Hoặc

có các yếu tố bên trong mẫu tham khảo các nguồn lực bản thân.

Sử dụng phương pháp phụ thuộc tài sản:

Ưu điểm:

1) Clarity, chúng tôi có một API rõ ràng hơn để kiểm soát của chúng tôi và sự hiểu biết tốt hơn về cách nhìn sự kiểm soát của chúng tôi và hành xử theo cách nó .

2) Mẫu không phải thay đổi để có thể tùy chỉnh. Mọi thứ đều được điều khiển thông qua phong cách.

3) Kích hoạt cũng thay đổi giao diện của điều khiển mà không cần phải ghi đè mẫu điều khiển, không cần kích hoạt ControlTemplate.

4) "Blendabilty" sử dụng pha trộn tôi có thể dễ dàng tùy chỉnh kiểm soát của tôi.

5) Bản thân các kiểu có thể kế thừa. vì vậy nếu tôi muốn thay đổi chỉ một khía cạnh của điều khiển tất cả những gì tôi cần làm là kế thừa từ kiểu mặc định.

Nhược điểm:

1) Thực hiện thêm một điều khiển tùy chỉnh.

2) Thực hiện nhiều thuộc tính phụ thuộc, một số trong đó không có nhiều việc phải làm với điều khiển và chỉ ở đó để thỏa mãn điều gì đó chúng tôi có trong mẫu của chúng tôi.

  • Chỉ cần làm rõ điều này có nghĩa là kế thừa từ TextBox một cái gì đó như InnerShadowTextBox và triển khai thuộc tính phụ thuộc với trong đó cho tất cả các bên trên.

Điều này sẽ tăng cường nếu tôi có nhiều yếu tố bên trong mẫu của tôi cần phải tùy chỉnh.

Cái gì đó như con quái vật này:

<Style x:Key="{x:Type cc:ComplexControl}" TargetType="{x:Type cc:ComplexControl}"> 
    <Setter Property="Template"> 
      <Setter.Value> 
       <ControlTemplate TargetType="{x:Type cc:ComplexControl}"> 
        <Grid> 
         <Ellipse Fill="Red" Margin="0" Stroke="Black" StrokeThickness="1"/> 
         <Ellipse Fill="Green" Margin="6" Stroke="Red" StrokeThickness="1"/> 
         <Ellipse Fill="Blue" Margin="12"/> 
         <Ellipse Fill="Aqua" Margin="24" /> 
         <Ellipse Fill="Beige" Margin="32"/> 
         <StackPanel Orientation="Horizontal" Width="25" Height="25" 
            VerticalAlignment="Center" HorizontalAlignment="Center"> 
          <Rectangle Fill="Black" Width="2" /> 
          <Rectangle Fill="Black" Width="2" Margin="2,0,0,0"/> 
          <Rectangle Fill="Black" Width="2" Margin="2,0,0,0"/> 
          <Rectangle Fill="Black" Width="2" Margin="2,0,0,0"/> 
         </StackPanel> 

        </Grid> 
       </ControlTemplate> 
      </Setter.Value> 
    </Setter> 
</Style> 

Trong đó sẽ đòi hỏi nhiều nguồn lực:

<SolidColorBrush x:Key="Ellipse1Fill">Red</SolidColorBrush> 
<SolidColorBrush x:Key="Ellipse2Fill">Green</SolidColorBrush> 
<SolidColorBrush x:Key="Ellipse3Fill">Blue</SolidColorBrush> 
<SolidColorBrush x:Key="Ellipse4Fill">Aqua</SolidColorBrush> 
<SolidColorBrush x:Key="Ellipse5Fill">Beige</SolidColorBrush> 
<SolidColorBrush x:Key="Ellipse1Stroke">Beige</SolidColorBrush> 
<sys:Double x:Key="Ellipse1StrokeThickness>1</sys:Double> 
     ......... and many more 

tôi sẽ có một danh sách lớn các nguồn lực một trong hai cách. Nhưng với các thuộc tính phụ thuộc. Tôi cũng cần phải chỉ định cần phải tìm ý nghĩa trong mọi phần nhỏ, Đôi khi nào không nhiều hơn thì "có vẻ tốt" và không liên quan gì đến điều khiển hoặc Nếu ngày mai tôi muốn thay đổi mẫu .

Sử dụng cách tiếp cận nơi tài nguyên được tham chiếu từ bên trong mẫu kiểm soát.

Ưu điểm:

1) Dễ sử dụng, bên bước sự xấu xí mô tả trong những nhược điểm nêu trên trong cách tiếp cận Dp khi cung cấp một "hack" cho phép một chủ đề.

Nhược điểm:

1) Nếu tôi muốn tuỳ chỉnh thêm kiểm soát của tôi như thêm một kích hoạt mà ảnh hưởng đến biên giới bên trong của TextBox của tôi, tôi sẽ chỉ cần có để tạo ra một mẫu điều khiển mới.

2) Không phải API rõ ràng, Cho phép nói rằng tôi muốn thay đổi BorderBrush của đường viền bên trong trong một chế độ xem cụ thể.

  <TextBox> 
      <TextBox.Resources> 
        <SolidColorBrush x:Key="InnerBorderBrush" Color="Red" /> 
      </TextBox.Resources> 
      </TextBox> 

nào mà không phải là xấu đến suy nghĩ về nó ... đôi khi chúng ta làm điều này với việc triển khai Selector mà trong nội bộ sử dụng các nguồn lực cụ thể khi nhận được thoát khỏi sự lựa chọn không hoạt động và làm nổi bật màu sắc như vậy:

<ListBox> 
     <ListBox.Resources> 
      <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent"/> 
      <SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Transparent"/> 
      <SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="Transparent"/> 
      <SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}" Color="Transparent"/> 
     </ListBox.Resources> 
</ListBox> 

Kết luận:

các lai được mô tả trong Style TextBox trên là con đường để đi.

1) Thuộc tính phụ thuộc sẽ chỉ được giới thiệu cho các khía cạnh của điều khiển có liên quan đến logic của điều khiển bao gồm phần mẫu cụ thể.

2) Tên tài nguyên sẽ bao gồm một quy ước đặt tên rõ ràng và được phân tách trong các tệp dựa trên kiểm soát mà chúng liên quan và sử dụng chung trong chế độ xem, như bàn chải chung được sử dụng trong chế độ xem trong ứng dụng của chúng tôi.

3) Mẫu kiểm soát cần được tối giản hóa và sử dụng các thuộc tính phụ thuộc hiện có. Giống như Nền, Foreground, BorderBrush, v.v.

Tôi đánh giá cao ý kiến ​​đóng góp và suy nghĩ của bạn về vấn đề này, cảm ơn trước.

+0

Do tính chất của câu hỏi này, bạn có thể nên đăng nó lên trang web [Code Review] (http://codereview.stackexchange.com). – Xavier

Trả lời

3

Như Xavier đã nói, đây có thể là câu hỏi hay hơn cho Code Review. Nhưng tôi sẽ truyền đạt một số suy nghĩ quan trọng về câu hỏi của bạn, mặc dù rất nhiều suy nghĩ sẽ đến với phong cách và yêu cầu cá nhân (hoặc nhóm).

Sau khi tạo vài chục chủ đề, tôi khuyên bạn nên chống lại các điều khiển tùy chỉnh bất cứ khi nào có thể. Theo thời gian, khả năng bảo trì giảm đi một chút.

Nếu bạn yêu cầu sửa đổi nhỏ đối với một kiểu, tốt hơn là sử dụng DataTemplates và Trình kích hoạt dữ liệu nếu tình huống cho phép. Bằng cách này bạn đang thay đổi phong cách một cách rõ ràng.

Ngoài ra, bạn có thể tận dụng thuộc tính BasedOn. Tạo phong cách "cơ sở" của bạn và có nhiều kiểu có thuộc tính BasedOn = "{myBaseStyle}. Điều này sẽ cho phép bạn có nhiều tùy chọn mà không làm lộn xộn mã của bạn.

Theo nguyên tắc, tôi luôn khuyên bạn nên có nhiều cọ vẽ/màu sắc/tài nguyên như trái ngược với nhiều phong cách hoặc mẫu.Chúng tôi thường có hệ thống phân cấp của chúng tôi thiết lập cho màu sắc-> brushes-> styles-> mẫu.Điều này giúp tái sử dụng các màu sắc trong khi vẫn duy trì sự tách biệt thông qua bàn chải

Sử dụng DynamicResource như trái ngược với StaticResource cũng hữu ích trong một số tình huống mà bạn tải các tài nguyên động một cách tự động

Hy vọng điều này sẽ giúp ích nhiều hơn, nhưng một số tham số để viết một chủ đề vững chắc rất cụ thể về ngữ cảnh. Nếu bạn có thêm ví dụ, tôi rất vui khi thêm thông tin.