You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何阻止ControlTemplate内的TextBlock随控件宽度调整而缩放?

解决WPF ControlTemplate中标签随控件宽度缩放但高度不缩放的问题

首先得搞清楚为什么会出现这种奇怪的差异:你说调整宽度时标签跟着缩放,调整高度却不会,大概率是你的宽度调整逻辑用了ScaleTransform(缩放变换)来放大/缩小整个控件,而高度调整是直接修改控件的Height属性(只是改变尺寸,没有应用缩放)。这样一来,宽度缩放时,ControlTemplate里的整个Grid(包括标签)都会被缩放,标签的Margin、文字大小跟着变大变小;而高度调整时,只是控件的高度变了,标签的布局属性(比如Margin)是固定像素值,所以位置会变但大小不受影响。

要让标签完全不受缩放影响,同时还能跟着控件移动,最靠谱的办法是把标签从被缩放的布局层级里抽出来,放到AdornerLayer中——Adorner是独立于控件本身的渲染层,只会跟随控件的位置,不会受控件的缩放变换影响。下面是具体的实现步骤:

步骤1:创建自定义Adorner类来承载标签

这个类会负责渲染标签,以及处理删除按钮的逻辑:

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Imaging;

public class DesignerItemLabelAdorner : Adorner
{
    private readonly Border _labelBorder;
    private readonly ContentControl _targetItem;

    public DesignerItemLabelAdorner(ContentControl adornedElement) : base(adornedElement)
    {
        _targetItem = adornedElement;
        IsHitTestVisible = true; // 确保按钮可以点击

        // 构建标签的UI结构,和你原来的模板一致
        _labelBorder = new Border
        {
            Background = Brushes.LightGray,
            BorderBrush = Brushes.Black,
            BorderThickness = new Thickness(2),
            CornerRadius = new CornerRadius(3),
            Margin = new Thickness(5, -35, 0, 0)
        };

        var stackPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(5) };
        
        // 绑定标签文字到控件的Name属性
        var textBlock = new TextBlock { Foreground = Brushes.Black };
        textBlock.SetBinding(TextBlock.TextProperty, new Binding("Name") 
        { 
            Source = _targetItem, 
            Mode = BindingMode.TwoWay 
        });

        // 创建删除按钮,样式和原来一致
        var deleteBtn = new Button
        {
            Height = 15,
            MinWidth = 15,
            Width = 15,
            Margin = new Thickness(5, 0, 0, 0),
            Background = Brushes.LightGray
        };
        deleteBtn.Click += (s, e) => DeleteTargetItem();

        var deleteImg = new Image
        {
            Height = 7,
            Width = 7,
            Source = new BitmapImage(new Uri("/Resources/Images/cross_48.png", UriKind.Relative))
        };
        deleteBtn.Content = deleteImg;

        // 设置按钮的样式触发器
        var btnStyle = new Style(typeof(Button));
        btnStyle.Setters.Add(new Setter(Button.BackgroundProperty, Brushes.LightGray));
        btnStyle.Setters.Add(new Setter(Button.TemplateProperty, new ControlTemplate(typeof(Button))
        {
            VisualTree = new FrameworkElementFactory(typeof(Border))
            {
                Bindings = { new Binding("Background") { Source = deleteBtn } },
                Child = new FrameworkElementFactory(typeof(ContentPresenter))
                {
                    Setters =
                    {
                        new Setter(ContentPresenter.FocusableProperty, false),
                        new Setter(ContentPresenter.HorizontalAlignmentProperty, HorizontalAlignment.Center),
                        new Setter(ContentPresenter.VerticalAlignmentProperty, VerticalAlignment.Center)
                    }
                }
            }
        }));
        btnStyle.Triggers.Add(new Trigger { Property = Button.IsMouseOverProperty, Value = true, Setters = { new Setter(Button.BackgroundProperty, Brushes.LightGray) } });
        btnStyle.Triggers.Add(new Trigger { Property = Button.IsPressedProperty, Value = true, Setters = { new Setter(Button.BackgroundProperty, Brushes.DarkGray) } });
        btnStyle.Triggers.Add(new Trigger { Property = Button.IsEnabledProperty, Value = false, Setters = { new Setter(UIElement.OpacityProperty, 0.35) } });
        deleteBtn.Style = btnStyle;

        stackPanel.Children.Add(textBlock);
        stackPanel.Children.Add(deleteBtn);
        _labelBorder.Child = stackPanel;

        AddVisualChild(_labelBorder);
    }

    private void DeleteTargetItem()
    {
        // 复用你原来的删除逻辑
        if (_targetItem.Parent is Panel parentPanel)
        {
            parentPanel.Children.Remove(_targetItem);
        }
    }

    // 必须重写的Adorner方法
    protected override int VisualChildrenCount => 1;
    protected override Visual GetVisualChild(int index) => index == 0 ? _labelBorder : throw new ArgumentOutOfRangeException(nameof(index));

    protected override Size MeasureOverride(Size constraint)
    {
        _labelBorder.Measure(constraint);
        return _labelBorder.DesiredSize;
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        // 把标签定位到控件的左上角上方
        _labelBorder.Arrange(new Rect(new Point(0, -_labelBorder.DesiredSize.Height), _labelBorder.DesiredSize));
        return finalSize;
    }
}

步骤2:修改ControlTemplate,移除原来的标签部分

把你原来模板里的BoxLabelBorder及其子元素全部删掉,简化后的模板如下:

<!-- Designer Item Template-->
<ControlTemplate x:Key="DesignerItemTemplate" TargetType="ContentControl">
    <Grid x:Name="DesignerGrid" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
        <s:MoveThumb x:Name="MoveThumbAdorner" Template="{StaticResource MoveThumbTemplate}" Cursor="SizeAll"/>
        <Control Template="{StaticResource ResizeDecoratorTemplate}"/>
        <ContentPresenter x:Name="rectCP" Content="{TemplateBinding ContentControl.Content}"/>
    </Grid>
</ControlTemplate>

步骤3:在DesignerItem加载时添加Adorner

给你的ContentControl(DesignerItem)添加Loaded事件,在事件里把Adorner绑定到控件上:

private void DesignerItem_Loaded(object sender, RoutedEventArgs e)
{
    if (sender is ContentControl designerItem)
    {
        var adornerLayer = AdornerLayer.GetAdornerLayer(designerItem);
        adornerLayer?.Add(new DesignerItemLabelAdorner(designerItem));
    }
}

别忘了在XAML里给DesignerItem绑定这个事件:

<ContentControl Template="{StaticResource DesignerItemTemplate}" Loaded="DesignerItem_Loaded" .../>

这样处理之后,标签会始终附着在控件的左上角上方,不管你怎么缩放控件的宽度或高度,标签的大小都不会变,而且会跟着控件一起移动。

另外,如果你不想用Adorner,也可以把标签作为Canvas的独立子元素,通过绑定控件的Canvas.Left和Canvas.Top属性来同步位置——但这种方法需要你的控件都放在Canvas布局里,灵活性不如Adorner高。

内容的提问来源于stack exchange,提问作者nwahs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:55:16