none
ContentControl一项属性改变,Control内的元素的样式也改变。用触发器? RRS feed

  • 问题

  • 我有一个自定义控件,名叫InfoBox,继承自ContentControl,内含很多元素和控件,用来显示一个人的姓名、年龄等等。

    InfoBox有一个依赖项属性IsImportant(或者你觉得没必要用依赖项属性,也可改成CLR属性)。当IsImportant==true,InfoBox或里面的控件要改变样式,以显示这个人很重要。

    <ContentControl x:Class="WpfApplication1.InfoBox"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 mc:Ignorable="d" 
                 d:DesignHeight="300" d:DesignWidth="300">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            
            <TextBlock Text="姓名" Grid.Column="0" Grid.Row="0" Name="nameLabel"/>
            <TextBlock Text="年龄" Grid.Column="0" Grid.Row="1" Name="ageLabel"/>
            
            <TextBox Grid.Column="1" Grid.Row="0" Name="nameTextBox"/>
            <TextBox Grid.Column="1" Grid.Row="1" Name="ageTextBox"/>
        </Grid>
    </ContentControl>
    


    当前的想法是当IsImportant==true,nameTextBox.Foreground=Red && InfoBox.BorderThickness=1 && InfoBox.BorderBrush=Black。

    请问这种“父元素属性改变,自己和子元素也相应改变”的逻辑怎么做?

    以后我可能听从美工的建议,当IsImportant==true时,InfoBox.Background=Red && nameTextBox.FontSize=20 之类的。所以我想要把这种逻辑放在一个地方,而不是

    InfoBox.Background="{Binding IsImportant ...}"

    nameTextBox.FontSize="{Binding IsImportant ...}"

    这样子分散在各处。

    我WPF学得还不是很懂,是不是可以用触发器,那么应该怎么写呢?

    2012年1月26日 7:12

答案

  • 按照你现有的代码逻辑,你应该用定义样式的方式, 比如下面的写法:

    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                        xmlns:local="clr-namespace:WpfApplication3">
      <Style x:Key="TextBoxImportant" TargetType="{x:Type TextBox}">
        <Style.Triggers>
          <DataTrigger Binding="{Binding IsImportant, RelativeSource={RelativeSource AncestorType={x:Type local:InfoBox}}}" Value="True">
            <Setter Property="Foreground" Value="Red"/>
          </DataTrigger>
        </Style.Triggers>
      </Style>
      <Style x:Key="InfoBoxImportant" TargetType="{x:Type local:InfoBox}">
        <Style.Triggers>
          <Trigger Property="IsImportant" Value="True">
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="BorderBrush" Value="Black"/>
          </Trigger>
        </Style.Triggers>
      </Style>
    </ResourceDictionary>
    


    然后在你的InfoBox中合并并且引用你的样式:

    <ContentControl x:Class="WpfApplication3.InfoBox"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 mc:Ignorable="d" 
                 d:DesignHeight="300" d:DesignWidth="300"
                 Style="{DynamicResource InfoBoxImportant}">
      <ContentControl.Resources>
        <ResourceDictionary>
          <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="StyleDictionary.xaml"/>
          </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
      </ContentControl.Resources>
      <Grid>
        <Border BorderBrush="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType={x:Type ContentControl}}}"
                BorderThickness="{Binding BorderThickness, RelativeSource={RelativeSource AncestorType={x:Type ContentControl}}}">
          <TextBox x:Name="nameTextBox" Style="{DynamicResource TextBoxImportant}"/>
        </Border>
      </Grid>
    </ContentControl>
    

    最后使用如下:

      <StackPanel>
        <local:InfoBox IsImportant="False" Margin="2"/>
        <local:InfoBox IsImportant="True" Margin="2"/>
      </StackPanel>
    


    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    不过,我不建议你用类似UserControl的方式来创建较灵活,需要以后修改模板样式的自定义控件,而是要用CustomControl的形式。如下:你只需要维护这个Themes/Generic.xaml文件就可以使用不同的样式模板了

    <ResourceDictionary
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:CustomControlSample">
    
    
      <Style TargetType="{x:Type local:InfoBox}">
        <Setter Property="Template">
          <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:InfoBox}">
              <Border Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}">
                <TextBox x:Name="nameTextBox"/>
              </Border>
              <ControlTemplate.Triggers>
                <Trigger Property="IsImportant" Value="True">
                  <Setter TargetName="nameTextBox" Property="Foreground" Value="Red"/>
                </Trigger>
              </ControlTemplate.Triggers>
            </ControlTemplate>
          </Setter.Value>
        </Setter>
    
        <Style.Triggers>
          <Trigger Property="IsImportant" Value="True">
            <Setter Property="BorderBrush" Value="Black"/>
            <Setter Property="BorderThickness" Value="1"/>
          </Trigger>
        </Style.Triggers>
      </Style>
    </ResourceDictionary>
    
    


    你同时还可以在Window中给他再附上新的模板和样式:

      <StackPanel>
        <local:InfoBox IsImportant="False" Margin="3"/>
        <local:InfoBox IsImportant="True" Margin="3"/>
    
        <local:InfoBox IsImportant="True" Margin="3">
          <local:InfoBox.Style>
            <Style TargetType="{x:Type local:InfoBox}">
              <Style.Triggers>
                <Trigger Property="IsImportant" Value="True">
                  <Setter Property="BorderBrush" Value="Orange"/>
                  <Setter Property="BorderThickness" Value="2"/>
                </Trigger>
              </Style.Triggers>
            </Style>
          </local:InfoBox.Style>
        </local:InfoBox>
      </StackPanel>
    


    当然,用CustomControl,你也可以分别建立多个ResourceDictionary,然后合并在一起。维护起来你就可以独立的维护各自的样式模板,这样比较灵活。

    -------------------

    完整的例子下载:https://skydrive.live.com/?cid=51b2fdd068799d15#cid=51B2FDD068799D15&id=51B2FDD068799D15%21924

    Sincerely,
     

     


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    2012年1月27日 5:49
    版主
  • 哈哈,我当然知道为什么。

    原因就在于为什么一个有CustomControl的项目他知道去载入和引用Themes/Generic.xaml的资源。答案是: AssemblyInfo.cs 里面的

    [assembly: ThemeInfo(
        ResourceDictionaryLocation.None,
        ResourceDictionaryLocation.SourceAssembly
    )]
    
    

    你看下MSDN文档,上面有详细解释,这个特性不能少,不然你的项目就找不到Themes/Generic.xaml了。

    http://msdn.microsoft.com/zh-cn/library/system.windows.themeinfoattribute.aspx


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    2012年1月28日 12:38
    版主

全部回复

  • 用触发器,然后写两套模板。

    就像

    <Style x:Key="ScrollBarStyle" TargetType="{x:Type ScrollBar}">
    		<Setter Property="Stylus.IsPressAndHoldEnabled" Value="false"/>
    		<Setter Property="Stylus.IsFlicksEnabled" Value="false"/>
    		<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlLightBrushKey}}"/>
    		<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    		<Setter Property="BorderThickness" Value="0"/>
    		<Setter Property="Width" Value="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"/>
    		<Setter Property="MinWidth" Value="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"/>
    		<Setter Property="Template">
    			<Setter.Value>
    				<ControlTemplate TargetType="{x:Type ScrollBar}">
    					<Grid Background="{TemplateBinding Background}">
    						<Grid.RowDefinitions>
    							<RowDefinition MaxHeight="{DynamicResource {x:Static SystemParameters.VerticalScrollBarButtonHeightKey}}"/>
    							<RowDefinition Height="0.00001*"/>
    							<RowDefinition MaxHeight="{DynamicResource {x:Static SystemParameters.VerticalScrollBarButtonHeightKey}}"/>
    						</Grid.RowDefinitions>
    						<Viewbox Stretch="Fill">
    							<RepeatButton Content="M 0 4 L 7 4 L 3.5 0 Z" Command="{x:Static ScrollBar.LineUpCommand}" Height="{DynamicResource {x:Static SystemParameters.VerticalScrollBarButtonHeightKey}}" MinWidth="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" Style="{StaticResource ScrollBarLineButtonStyle}"/>
    						</Viewbox>
    						<Rectangle Fill="{StaticResource ScrollBarPageButtonBrush}" Grid.Row="1"/>
    						<Track x:Name="PART_Track" IsDirectionReversed="true" Grid.Row="1">
    							<Track.DecreaseRepeatButton>
    								<RepeatButton Command="{x:Static ScrollBar.PageUpCommand}" Style="{StaticResource VerticalScrollBarPageButtonStyle}"/>
    							</Track.DecreaseRepeatButton>
    							<Track.IncreaseRepeatButton>
    								<RepeatButton Command="{x:Static ScrollBar.PageDownCommand}" Style="{StaticResource VerticalScrollBarPageButtonStyle}"/>
    							</Track.IncreaseRepeatButton>
    							<Track.Thumb>
    								<Thumb Style="{StaticResource ScrollBarThumb}"/>
    							</Track.Thumb>
    						</Track>
    						<Viewbox Grid.Row="2" Stretch="Fill">
    							<RepeatButton Content="M 0 0 L 3.5 4 L 7 0 Z" Command="{x:Static ScrollBar.LineDownCommand}" Height="{DynamicResource {x:Static SystemParameters.VerticalScrollBarButtonHeightKey}}" MinWidth="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" Style="{StaticResource ScrollBarLineButtonStyle}"/>
    						</Viewbox>
    					</Grid>
    				</ControlTemplate>
    			</Setter.Value>
    		</Setter>
    		<Style.Triggers>
    			<Trigger Property="Orientation" Value="Horizontal">
    				<Setter Property="Width" Value="Auto"/>
    				<Setter Property="MinWidth" Value="0"/>
    				<Setter Property="Height" Value="{DynamicResource {x:Static SystemParameters.HorizontalScrollBarHeightKey}}"/>
    				<Setter Property="MinHeight" Value="{DynamicResource {x:Static SystemParameters.HorizontalScrollBarHeightKey}}"/>
    				<Setter Property="Template">
    					<Setter.Value>
    						<ControlTemplate TargetType="{x:Type ScrollBar}">
    							<Grid Background="{TemplateBinding Background}">
    								<Grid.ColumnDefinitions>
    									<ColumnDefinition MaxWidth="{DynamicResource {x:Static SystemParameters.HorizontalScrollBarButtonWidthKey}}"/>
    									<ColumnDefinition Width="0.00001*"/>
    									<ColumnDefinition MaxWidth="{DynamicResource {x:Static SystemParameters.HorizontalScrollBarButtonWidthKey}}"/>
    								</Grid.ColumnDefinitions>
    								<Viewbox Stretch="Fill">
    									<RepeatButton Content="M 0 4 L 4 8 L 4 0 Z" Command="{x:Static ScrollBar.LineLeftCommand}" MinHeight="{DynamicResource {x:Static SystemParameters.HorizontalScrollBarHeightKey}}" Style="{StaticResource ScrollBarLineButtonStyle}" Width="{DynamicResource {x:Static SystemParameters.HorizontalScrollBarButtonWidthKey}}"/>
    								</Viewbox>
    								<Rectangle Grid.Column="1" Fill="{StaticResource ScrollBarPageButtonBrush}"/>
    								<Track x:Name="PART_Track" Grid.Column="1">
    									<Track.DecreaseRepeatButton>
    										<RepeatButton Command="{x:Static ScrollBar.PageLeftCommand}" Style="{StaticResource HorizontalScrollBarPageButtonStyle}"/>
    									</Track.DecreaseRepeatButton>
    									<Track.IncreaseRepeatButton>
    										<RepeatButton Command="{x:Static ScrollBar.PageRightCommand}" Style="{StaticResource HorizontalScrollBarPageButtonStyle}"/>
    									</Track.IncreaseRepeatButton>
    									<Track.Thumb>
    										<Thumb Style="{StaticResource ScrollBarThumb}"/>
    									</Track.Thumb>
    								</Track>
    								<Viewbox Grid.Column="2" Stretch="Fill">
    									<RepeatButton Content="M 0 0 L 0 8 L 4 4 Z" Command="{x:Static ScrollBar.LineRightCommand}" MinHeight="{DynamicResource {x:Static SystemParameters.HorizontalScrollBarHeightKey}}" Style="{StaticResource ScrollBarLineButtonStyle}" Width="{DynamicResource {x:Static SystemParameters.HorizontalScrollBarButtonWidthKey}}"/>
    								</Viewbox>
    							</Grid>
    						</ControlTemplate>
    					</Setter.Value>
    				</Setter>
    			</Trigger>
    		</Style.Triggers>
    	</Style>
    



    野老
    2012年1月26日 8:24
  • 按照你现有的代码逻辑,你应该用定义样式的方式, 比如下面的写法:

    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                        xmlns:local="clr-namespace:WpfApplication3">
      <Style x:Key="TextBoxImportant" TargetType="{x:Type TextBox}">
        <Style.Triggers>
          <DataTrigger Binding="{Binding IsImportant, RelativeSource={RelativeSource AncestorType={x:Type local:InfoBox}}}" Value="True">
            <Setter Property="Foreground" Value="Red"/>
          </DataTrigger>
        </Style.Triggers>
      </Style>
      <Style x:Key="InfoBoxImportant" TargetType="{x:Type local:InfoBox}">
        <Style.Triggers>
          <Trigger Property="IsImportant" Value="True">
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="BorderBrush" Value="Black"/>
          </Trigger>
        </Style.Triggers>
      </Style>
    </ResourceDictionary>
    


    然后在你的InfoBox中合并并且引用你的样式:

    <ContentControl x:Class="WpfApplication3.InfoBox"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 mc:Ignorable="d" 
                 d:DesignHeight="300" d:DesignWidth="300"
                 Style="{DynamicResource InfoBoxImportant}">
      <ContentControl.Resources>
        <ResourceDictionary>
          <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="StyleDictionary.xaml"/>
          </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
      </ContentControl.Resources>
      <Grid>
        <Border BorderBrush="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType={x:Type ContentControl}}}"
                BorderThickness="{Binding BorderThickness, RelativeSource={RelativeSource AncestorType={x:Type ContentControl}}}">
          <TextBox x:Name="nameTextBox" Style="{DynamicResource TextBoxImportant}"/>
        </Border>
      </Grid>
    </ContentControl>
    

    最后使用如下:

      <StackPanel>
        <local:InfoBox IsImportant="False" Margin="2"/>
        <local:InfoBox IsImportant="True" Margin="2"/>
      </StackPanel>
    


    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    不过,我不建议你用类似UserControl的方式来创建较灵活,需要以后修改模板样式的自定义控件,而是要用CustomControl的形式。如下:你只需要维护这个Themes/Generic.xaml文件就可以使用不同的样式模板了

    <ResourceDictionary
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:CustomControlSample">
    
    
      <Style TargetType="{x:Type local:InfoBox}">
        <Setter Property="Template">
          <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:InfoBox}">
              <Border Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}">
                <TextBox x:Name="nameTextBox"/>
              </Border>
              <ControlTemplate.Triggers>
                <Trigger Property="IsImportant" Value="True">
                  <Setter TargetName="nameTextBox" Property="Foreground" Value="Red"/>
                </Trigger>
              </ControlTemplate.Triggers>
            </ControlTemplate>
          </Setter.Value>
        </Setter>
    
        <Style.Triggers>
          <Trigger Property="IsImportant" Value="True">
            <Setter Property="BorderBrush" Value="Black"/>
            <Setter Property="BorderThickness" Value="1"/>
          </Trigger>
        </Style.Triggers>
      </Style>
    </ResourceDictionary>
    
    


    你同时还可以在Window中给他再附上新的模板和样式:

      <StackPanel>
        <local:InfoBox IsImportant="False" Margin="3"/>
        <local:InfoBox IsImportant="True" Margin="3"/>
    
        <local:InfoBox IsImportant="True" Margin="3">
          <local:InfoBox.Style>
            <Style TargetType="{x:Type local:InfoBox}">
              <Style.Triggers>
                <Trigger Property="IsImportant" Value="True">
                  <Setter Property="BorderBrush" Value="Orange"/>
                  <Setter Property="BorderThickness" Value="2"/>
                </Trigger>
              </Style.Triggers>
            </Style>
          </local:InfoBox.Style>
        </local:InfoBox>
      </StackPanel>
    


    当然,用CustomControl,你也可以分别建立多个ResourceDictionary,然后合并在一起。维护起来你就可以独立的维护各自的样式模板,这样比较灵活。

    -------------------

    完整的例子下载:https://skydrive.live.com/?cid=51b2fdd068799d15#cid=51B2FDD068799D15&id=51B2FDD068799D15%21924

    Sincerely,
     

     


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    2012年1月27日 5:49
    版主
  • 非常有用。。


    第一种方法比较好懂。实际上我还有几个属性,比如IsAdult之类的。利用Style里的触发器,可以对IsImportant和IsAdult同时设置,不错了。

    第二种方法有个缺点呃,那就是访问Template里的元素没有那么方便了。

    不过我新开项目测试法二时没问题,当应用到我的正式项目中就有问题了。我像你那样设置Themes\Generic.xaml,改写了实际项目中的ProfileBox。

    Generic.xaml里设置了Style TargetType="{x:Type UIComponent:ProfileBox}">

    ProfileBox里设置了

            static ProfileBox()
            {
                DefaultStyleKeyProperty.OverrideMetadata(typeof(ProfileBox), new FrameworkPropertyMetadata(typeof(ProfileBox)));
            }

    但显示时似乎Template没应用上。

    您能不能给看看? http://www.box.com/s/rbe43a6fa8nuxyxitvtv

    2012年1月28日 3:47
  • 我操。。。我创建了一份可以显示的项目,用文件夹比较工具一个文件一个文件地与楼上不能显示的项目作比较,结果发现原因是楼上的项目没有把AssemblyInfo.cs包括在文件中!!!!

    包括进去就好了。。

    真是很奇怪。有人知道为什么吗?

    2012年1月28日 12:25
  • 哈哈,我当然知道为什么。

    原因就在于为什么一个有CustomControl的项目他知道去载入和引用Themes/Generic.xaml的资源。答案是: AssemblyInfo.cs 里面的

    [assembly: ThemeInfo(
        ResourceDictionaryLocation.None,
        ResourceDictionaryLocation.SourceAssembly
    )]
    
    

    你看下MSDN文档,上面有详细解释,这个特性不能少,不然你的项目就找不到Themes/Generic.xaml了。

    http://msdn.microsoft.com/zh-cn/library/system.windows.themeinfoattribute.aspx


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    2012年1月28日 12:38
    版主
  • 非常感谢,老鲍威武!
    2012年1月28日 12:54
  • 哈哈, AssemblyInfo是个好东西,我经常会漏掉。然后F5了几次之后总会忽然想起来。
    野老
    2012年1月29日 13:36