首页 > 解决方案 > 有没有办法设置一个 TileBrush 让它总是 1 瓦高,但不同数量的瓦宽?

问题描述

我正在尝试制作一个TileBrush,当使用时,它始终是一个瓷砖高和可变数量的瓷砖宽(取决于它所应用的元素有多少空间)。

为了帮助说明我的问题,我编写了一些代码。在所述代码中, a 中有两个TextBlocks Grid。两者TextBlock的背景相同,但字体大小不同。目前,每个TextBlock的背景总是 1 格高和 4 格宽。

我需要进行哪些更改才能使每个TextBlock的背景始终始终为 1 块高,但块的宽度可变(取决于 的字体大小TextBlock及其包含Grid列的宽度)?

<Window x:Class="WpfTestApp01.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfTestApp01"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.Resources>

        <DrawingBrush x:Key="TestBrush"
                      TileMode="Tile"
                      Stretch="Uniform"
                      Viewbox="0,0,1,1"
                      ViewboxUnits="RelativeToBoundingBox"
                      Viewport="0,0,0.25,1"
                      ViewportUnits="RelativeToBoundingBox">
            <DrawingBrush.Drawing>
                <GeometryDrawing>
                    <GeometryDrawing.Geometry>
                        <GeometryGroup>
                            <EllipseGeometry Center="50,50"
                                             RadiusX="20"
                                             RadiusY="45"/>
                            <EllipseGeometry Center="50,50"
                                             RadiusX="45"
                                             RadiusY="20"/>
                        </GeometryGroup>
                    </GeometryDrawing.Geometry>
                    <GeometryDrawing.Brush>
                        <SolidColorBrush Color="Gray" />
                    </GeometryDrawing.Brush>
                    <GeometryDrawing.Pen>
                        <Pen Thickness="2"
                             Brush="Black"/>
                    </GeometryDrawing.Pen>
                </GeometryDrawing>
            </DrawingBrush.Drawing>
        </DrawingBrush>

    </Window.Resources>

    <Grid>

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

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <GridSplitter Grid.Row="0"
                      Grid.Column="1"
                      Grid.RowSpan="2"
                      Width="3"
                      VerticalAlignment="Stretch"
                      HorizontalAlignment="Center"
                      Background="Black"/>

        <TextBlock Text="Left text here."
                   FontSize="12"
                   FontFamily="Consolas"
                   Grid.Row="0"
                   Grid.Column="0"
                   Background="{StaticResource TestBrush}"/>

        <TextBlock Text="Right text here."
                   FontSize="30"
                   FontFamily="Consolas"
                   Grid.Row="1"
                   Grid.Column="2"
                   Background="{StaticResource TestBrush}"/>

    </Grid>
</Window>

这是上面的代码产生的:

截屏

标签: c#wpfxaml

解决方案


因为您已将视口精确设置为输出边界框相对大小的 1/4,所以您的画笔平铺了四个。只要视口与边界框有这种固定的关系,除了四个之外,你不能得到任何东西。

所以,解决这个问题的方法是删除这种关系并用更合适的东西代替它。最简单的方法当然是将视口模式设置为Absolute而不是RelativeToBoundingBox. 但问题是,使用什么绝对值?在您发布的代码中,如果您将绝对大小设置为任何特定的固定值,您将在使用画笔的任何地方获得相同的大小,并且它不适合TextBlock您想要的。

您需要的是不同的尺寸,具体取决于TextBlock. 这意味着每个不同的刷子TextBlock。但是,您可能不想为TextBlock您声明的每个声明一个新画笔,硬编码大小以适应。就此而言,也许您甚至不想声明一个新的TextBlock. 当您使用模板来排列可视化树时,WPF 工作得更好。使用模板可以硬编码要在任何地方重复的方面,同时仍然允许视觉元素的上下文控制需要改变的特定方面。所以,让我们这样做吧!

首先,您需要一个视图模型:

class TextAndSizeModel
{
    public string Text { get; set; }
    public double Size { get; set; }
}

这是一个退化模型,仅包含您的示例中不同的两条数据,没有属性更改通知。我们不会更改这个简单示例中的值,所以这很好。

现在,您需要的不太明显的东西之一是转换器。这是因为我们希望在每种情况下都有不同的视口大小,而视口大小是 a Rect,它是一个简单的值,而不是依赖对象。所以我们需要一个转换器来Rect根据需要做一个合适的值:

class SquareRectConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        double doubleValue = System.Convert.ToDouble(value);

        return new Rect(0, 0, doubleValue, doubleValue);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

现在我们准备好做标记了。它看起来像这样:

<Window x:Class="TestSO61021208TileBrush.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestSO61021208TileBrush"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
  <Window.Resources>
    <GeometryDrawing x:Key="geometryDrawing1">
      <GeometryDrawing.Geometry>
        <GeometryGroup>
          <EllipseGeometry Center="50,50"
                                             RadiusX="20"
                                             RadiusY="45"/>
          <EllipseGeometry Center="50,50"
                                             RadiusX="45"
                                             RadiusY="20"/>
        </GeometryGroup>
      </GeometryDrawing.Geometry>
      <GeometryDrawing.Brush>
        <SolidColorBrush Color="Gray" />
      </GeometryDrawing.Brush>
      <GeometryDrawing.Pen>
        <Pen Thickness="2"
                             Brush="Black"/>
      </GeometryDrawing.Pen>
    </GeometryDrawing>

    <local:SquareRectConverter x:Key="squareRectConverter1"/>

    <DataTemplate DataType="{x:Type local:TextAndSizeModel}">
      <TextBlock Text="{Binding Text}"
                  FontSize="{Binding Size}"
                  FontFamily="Consolas">
        <TextBlock.Background>
          <DrawingBrush
                  TileMode="Tile"
                  Stretch="Uniform"
                  Viewbox="0,0,1,1"
                  ViewboxUnits="RelativeToBoundingBox"
                  Viewport="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=TextBlock}, Converter={StaticResource squareRectConverter1}}"
                  ViewportUnits="Absolute"
                  Drawing="{StaticResource geometryDrawing1}"/>
        </TextBlock.Background>
      </TextBlock>                   
    </DataTemplate>

  </Window.Resources>

  <Grid>

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

    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="*"/>
      <ColumnDefinition Width="Auto"/>
      <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <GridSplitter Grid.Row="0"
                      Grid.Column="1"
                      Grid.RowSpan="2"
                      Width="3"
                      VerticalAlignment="Stretch"
                      HorizontalAlignment="Center"
                      Background="Black"/>

    <ContentControl Grid.Row="0" Grid.Column="0">
      <ContentControl.Content>
        <local:TextAndSizeModel Text="Left text here." Size="12"/>
      </ContentControl.Content>
    </ContentControl>

    <ContentControl Grid.Row="1" Grid.Column="2">
      <ContentControl.Content>
        <local:TextAndSizeModel Text="Right text here." Size="30"/>
      </ContentControl.Content>
    </ContentControl>
  </Grid>
</Window>

ContentControl当然,用于将视图模型对象映射到模板。您将在模板中看到三个绑定:

  1. 文本值。
  2. 字体大小值。
  3. ActualHeight包含TextBlock元素的大小Viewport值,通过转换器

这会将视口的高度设置为TextBlock. 但它也允许视口是一个与边界框无关的正方形,这样它就可以在整个背景上正确平铺。

结果如下所示: 在此处输入图像描述

现在水平刷平铺以适应背景,无论TextBlock.

当然,在这个特定示例中,画笔会根据TextBlock. 我推断这就是您想要的,因为这就是它在您的原始示例中的工作方式,并且您没有写任何东西来暗示您想要它。但是,您应该能够修改上面的基本技术以获得您想要的。您需要以不同的方式进行数学运算,以便您决定画笔的固定宽度,而不是使高度和宽度与包含元素的高度完全不同,使用传入的高度仅用于高度视口。这样,画笔将在元素中垂直居中,图形元素具有固定大小,同时仍水平平铺。

看起来像这样:

转换器:

class CenteredRectConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        double height = System.Convert.ToDouble(value);
        double width = System.Convert.ToDouble(parameter);

        return new Rect(0, 0, width, height);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

数据模板:

<DataTemplate DataType="{x:Type local:TextAndSizeModel}">
  <TextBlock Text="{Binding Text}"
              FontSize="{Binding Size}"
              FontFamily="Consolas">
    <TextBlock.Background>
      <DrawingBrush
              TileMode="Tile"
              Stretch="Uniform"
              Viewbox="0,0,1,1"
              ViewboxUnits="RelativeToBoundingBox"
              Viewport="{Binding ActualHeight,
                RelativeSource={RelativeSource AncestorType=TextBlock},
                Converter={StaticResource centeredRectConverter1},
                ConverterParameter=14}"
              ViewportUnits="Absolute"
              Drawing="{StaticResource geometryDrawing1}"/>
    </TextBlock.Background>
  </TextBlock>
</DataTemplate>

看起来像这样: 在此处输入图像描述


推荐阅读