自动缩放 WPF TextBox/RichTextBox 内容以适应可用空间

发布于 2024-12-18 05:37:45 字数 3735 浏览 4 评论 0原文

我有以下问题:

我想设置一个 RichTextBox,它可以自动调整内部内容的大小以适应最大的可用空间,同时不更改内容的布局(例如字体大小、缩进等)。

我已经看到很多有关缩放文本框内容的问题,但所有问题都与某种缩放滑块有关。我想要的是真正计算缩放比例,以便它自动最适合文本框。

到目前为止,我已经在 RichTextBox 模板的 AdornerDecorator 内建立了一个 LayoutTransform,该模板包装在 ScrollViewer 内,以便我可以触发代码隐藏方法来计算缩放。

最初,当所有内容都适合 ViewPort 时,RichTextBox 不应缩放。一旦需要启用垂直滚动条,我就会更改 ScaleFactor。

只要不涉及 TextWrapping(因此 X 和 Y 的缩放不会导致文本在不同位置换行并另外更改高度),这种方法就非常有效。 有什么想法吗?

我创建了一个小演示以使事情更加清晰:

<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.Resources>
    <ScaleTransform x:Key="ScaleTransform" ScaleY="{Binding ScaleFactor}"
                                           ScaleX="{Binding ScaleFactor}"/>
</Window.Resources>
<Grid>
    <RichTextBox AcceptsTab="True" 
                 Background="Transparent" 
                 BorderBrush="Transparent" 
                 VerticalScrollBarVisibility="Auto"
                 HorizontalScrollBarVisibility="Disabled"
                 HorizontalAlignment="Stretch" 
                 VerticalAlignment="Stretch">
        <RichTextBox.Template>
            <ControlTemplate TargetType="{x:Type TextBoxBase}">
                <Border CornerRadius="2" 
                        Background="{TemplateBinding Background}" 
                        BorderThickness="{TemplateBinding BorderThickness}" 
                        BorderBrush="{TemplateBinding BorderBrush}">
                    <ScrollViewer ScrollChanged="ScrollViewer_ScrollChanged">
                        <AdornerDecorator x:Name="PART_ContentHost" Focusable="False"
                                          LayoutTransform="{StaticResource ScaleTransform}"/>
                    </ScrollViewer>
                </Border>
            </ControlTemplate>
        </RichTextBox.Template>
    </RichTextBox>
</Grid>

以及隐藏的代码:

public partial class Window1 : Window, INotifyPropertyChanged
{
    public Window1()
    {
        InitializeComponent();
        this.DataContext = this;
    }

    private double scaleFactor = 1.0;
    public double ScaleFactor
    {
        get { return scaleFactor; }
        set
        {
            scaleFactor = value;
            OnPropertyChanged("ScaleFactor");
        }
    }

    private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        if (e.ExtentHeightChange == 0)
            return;

        if (e.Source.GetType() == typeof(RichTextBox))
            return;

        var missingHeight = e.ExtentHeightChange;
        var heightWithoutScaling = e.ExtentHeight / ScaleFactor;

        if (e.ViewportHeight <= heightWithoutScaling)
        {
            ScaleFactor = ((e.ViewportHeight / heightWithoutScaling));
        }

    }

    #region INotifyPropertyChanged Members

    /// <summary>
    /// Raised when a property on this object has a new value
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Raises this object's PropertyChanged event.
    /// </summary>
    /// <param name="propertyName">The property that has a new value.</param>
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            var e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
        }
    }

    #endregion

I have the following problem:

I want to setup a RichTextBox that automatically can resize the content inside to fit maximum of the available space while not change the layout of the content (e.g. Font sizes, Indent, etc.).

I saw already many questions about scaling the content of a TextBox but all questions was related to some kind of zoom slider. What I want is to really calculate the scaling so that it automatically best-fit into the TextBox.

What I have established so far is a LayoutTransform inside the AdornerDecorator of the RichTextBox template that is wrapped inside a ScrollViewer so that I can trigger a code-behind method to calculate the Scaling.

Initially the RichTextBox should not Scale when all content fit into the ViewPort. As soon as there is a need to enable the vertical ScrollBar, I change the ScaleFactor.

This works pretty good as long as it not comes to TextWrapping (so that scaling of X and Y will not cause the Text to wrap at different positions and additionally change the height).
Any ideas?

I created a small demo to make things a little bit more clearer:

<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.Resources>
    <ScaleTransform x:Key="ScaleTransform" ScaleY="{Binding ScaleFactor}"
                                           ScaleX="{Binding ScaleFactor}"/>
</Window.Resources>
<Grid>
    <RichTextBox AcceptsTab="True" 
                 Background="Transparent" 
                 BorderBrush="Transparent" 
                 VerticalScrollBarVisibility="Auto"
                 HorizontalScrollBarVisibility="Disabled"
                 HorizontalAlignment="Stretch" 
                 VerticalAlignment="Stretch">
        <RichTextBox.Template>
            <ControlTemplate TargetType="{x:Type TextBoxBase}">
                <Border CornerRadius="2" 
                        Background="{TemplateBinding Background}" 
                        BorderThickness="{TemplateBinding BorderThickness}" 
                        BorderBrush="{TemplateBinding BorderBrush}">
                    <ScrollViewer ScrollChanged="ScrollViewer_ScrollChanged">
                        <AdornerDecorator x:Name="PART_ContentHost" Focusable="False"
                                          LayoutTransform="{StaticResource ScaleTransform}"/>
                    </ScrollViewer>
                </Border>
            </ControlTemplate>
        </RichTextBox.Template>
    </RichTextBox>
</Grid>

And the code-behind:

public partial class Window1 : Window, INotifyPropertyChanged
{
    public Window1()
    {
        InitializeComponent();
        this.DataContext = this;
    }

    private double scaleFactor = 1.0;
    public double ScaleFactor
    {
        get { return scaleFactor; }
        set
        {
            scaleFactor = value;
            OnPropertyChanged("ScaleFactor");
        }
    }

    private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        if (e.ExtentHeightChange == 0)
            return;

        if (e.Source.GetType() == typeof(RichTextBox))
            return;

        var missingHeight = e.ExtentHeightChange;
        var heightWithoutScaling = e.ExtentHeight / ScaleFactor;

        if (e.ViewportHeight <= heightWithoutScaling)
        {
            ScaleFactor = ((e.ViewportHeight / heightWithoutScaling));
        }

    }

    #region INotifyPropertyChanged Members

    /// <summary>
    /// Raised when a property on this object has a new value
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Raises this object's PropertyChanged event.
    /// </summary>
    /// <param name="propertyName">The property that has a new value.</param>
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            var e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
        }
    }

    #endregion

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文