c# - WPF TextBlock中的自動垂直滾動條?




.net scrollbar (8)

我在WPF中有一個TextBlock 。 我寫了很多行,遠遠超過它的垂直高度。 我期望一個垂直滾動條在這種情況發生時自動出現,但它沒有。 我試圖在“屬性”窗格中查找滾動條屬性,但找不到一個屬性。

一旦其內容超過其高度,我怎樣才能為我的TextBlock自動創建垂直滾動條?

澄清:我寧願從設計師那裡做,而不是直接寫給XAML。


Answers

不知道是否有其他人有這個問題,但包裝我的TextBlock到一個ScrollViewer有人搞砸了我的用戶界面 - 作為一個簡單的解決方法,我想通過TextBox替換TextBlock這樣的一個

<TextBox  Name="textBlock" SelectionBrush="Transparent" Cursor="Arrow" IsReadOnly="True" Text="My Text" VerticalScrollBarVisibility="Auto">

創建一個TextBox ,它的外觀和行為與帶滾動條的TextBlock相似(並且您可以在設計器中完成)。


將其包含在滾動查看器中:

<ScrollViewer>
    <TextBlock />
</ScrollViewer>

注意,這個答案適用於原始問題中要求的TextBlock (只讀文本元素)。

如果您想在TextBox (可編輯的文本元素)中顯示滾動條,請使用ScrollViewer附加屬性:

<TextBox ScrollViewer.HorizontalScrollBarVisibility="Disabled"
         ScrollViewer.VerticalScrollBarVisibility="Auto" />

這兩個屬性的有效值是“ Disabled ,“ Auto ,“ Hidden和“ Visible


<ScrollViewer MaxHeight="50"  
              Width="Auto" 
              HorizontalScrollBarVisibility="Disabled"
              VerticalScrollBarVisibility="Auto">
     <TextBlock Text="{Binding Path=}" 
                Style="{StaticResource TextStyle_Data}" 
                TextWrapping="Wrap" />
</ScrollViewer>

我以另一種方式將MaxHeight放入ScrollViewer中。

只需調整MaxHeight以顯示更多或更少的文本行。 簡單。


這個答案描述了使用MVVM的解決方案。

如果您想將日誌框添加到窗口中,該解決方案非常棒,每次添加新的日誌消息時都會自動滾動到底部。

一旦添加了這些附加屬性,它們就可以在任何地方重複使用,因此它可以生成非常模塊化和可重用的軟件。

添加這個XAML:

<TextBox IsReadOnly="True"   
         Foreground="Gainsboro"                           
         FontSize="13" 
         ScrollViewer.HorizontalScrollBarVisibility="Auto"
         ScrollViewer.VerticalScrollBarVisibility="Auto"
         ScrollViewer.CanContentScroll="True"
         attachedBehaviors:TextBoxApppendBehaviors.AppendText="{Binding LogBoxViewModel.AttachedPropertyAppend}"                                       
         attachedBehaviors:TextBoxClearBehavior.TextBoxClear="{Binding LogBoxViewModel.AttachedPropertyClear}"                                    
         TextWrapping="Wrap">

添加此附加屬性:

public static class TextBoxApppendBehaviors
{
    #region AppendText Attached Property
    public static readonly DependencyProperty AppendTextProperty =
        DependencyProperty.RegisterAttached(
            "AppendText",
            typeof (string),
            typeof (TextBoxApppendBehaviors),
            new UIPropertyMetadata(null, OnAppendTextChanged));

    public static string GetAppendText(TextBox textBox)
    {
        return (string)textBox.GetValue(AppendTextProperty);
    }

    public static void SetAppendText(
        TextBox textBox,
        string value)
    {
        textBox.SetValue(AppendTextProperty, value);
    }

    private static void OnAppendTextChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs args)
    {
        if (args.NewValue == null)
        {
            return;
        }

        string toAppend = args.NewValue.ToString();

        if (toAppend == "")
        {
            return;
        }

        TextBox textBox = d as TextBox;
        textBox?.AppendText(toAppend);
        textBox?.ScrollToEnd();
    }
    #endregion
}

和這個附加屬性(清除框):

public static class TextBoxClearBehavior
{
    public static readonly DependencyProperty TextBoxClearProperty =
        DependencyProperty.RegisterAttached(
            "TextBoxClear",
            typeof(bool),
            typeof(TextBoxClearBehavior),
            new UIPropertyMetadata(false, OnTextBoxClearPropertyChanged));

    public static bool GetTextBoxClear(DependencyObject obj)
    {
        return (bool)obj.GetValue(TextBoxClearProperty);
    }

    public static void SetTextBoxClear(DependencyObject obj, bool value)
    {
        obj.SetValue(TextBoxClearProperty, value);
    }

    private static void OnTextBoxClearPropertyChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs args)
    {
        if ((bool)args.NewValue == false)
        {
            return;
        }

        var textBox = (TextBox)d;
        textBox?.Clear();
    }
}   

然後,如果您正在使用依賴注入框架(如MEF),則可以將所有特定於日誌記錄的代碼放入其自己的ViewModel中:

public interface ILogBoxViewModel
{
    void CmdAppend(string toAppend);
    void CmdClear();

    bool AttachedPropertyClear { get; set; }

    string AttachedPropertyAppend { get; set; }
}

[Export(typeof(ILogBoxViewModel))]
public class LogBoxViewModel : ILogBoxViewModel, INotifyPropertyChanged
{
    private readonly ILog _log = LogManager.GetLogger<LogBoxViewModel>();

    private bool _attachedPropertyClear;
    private string _attachedPropertyAppend;

    public void CmdAppend(string toAppend)
    {
        string toLog = $"{DateTime.Now:HH:mm:ss} - {toAppend}\n";

        // Attached properties only fire on a change. This means it will still work if we publish the same message twice.
        AttachedPropertyAppend = "";
        AttachedPropertyAppend = toLog;

        _log.Info($"Appended to log box: {toAppend}.");
    }

    public void CmdClear()
    {
        AttachedPropertyClear = false;
        AttachedPropertyClear = true;

        _log.Info($"Cleared the GUI log box.");
    }

    public bool AttachedPropertyClear
    {
        get { return _attachedPropertyClear; }
        set { _attachedPropertyClear = value; OnPropertyChanged(); }
    }

    public string AttachedPropertyAppend
    {
        get { return _attachedPropertyAppend; }
        set { _attachedPropertyAppend = value; OnPropertyChanged(); }
    }

    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion
}

以下是它的工作原理:

  • ViewModel切換附加屬性來控製文本框。
  • 因為它使用“追加”,所以它閃電般快速。
  • 任何其他ViewModel都可以通過調用日誌ViewModel上的方法來生成日誌消息。
  • 當我們使用內置於TextBox中的ScrollViewer時,每次添加新消息時,我們都可以自動滾動到文本框的底部。

<ScrollViewer Height="239" VerticalScrollBarVisibility="Auto">
    <TextBox AcceptsReturn="True" TextWrapping="Wrap" LineHeight="10" />
</ScrollViewer>

這是在XAML中使用滾動文本框並將其用作文本區域的方法。


更好的事情是:

<Grid Width="Your-specified-value" >
    <ScrollViewer>
         <TextBlock Width="Auto" TextWrapping="Wrap" />
    </ScrollViewer>
</Grid>

這可以確保文本塊中的文本不會溢出並覆蓋文本塊下面的元素,如果不使用網格可能會出現這種情況。 即使文本塊已經在其他元素的網格中,我也嘗試了其他解決方案時發生在我身上。 請記住,textblock的寬度應該是Auto,並且您應該在Grid元素中指定所需的值。 我在我的代碼中做到了這一點,並且它的工作非常好。 HTH。


現在可以使用以下內容:

<TextBox Name="myTextBox" 
         ScrollViewer.HorizontalScrollBarVisibility="Auto"
         ScrollViewer.VerticalScrollBarVisibility="Auto"
         ScrollViewer.CanContentScroll="True">SOME TEXT
</TextBox>

問題來源

經過一場持續兩天三夜的輝煌戰鬥(以及評論中的驚人想法和想法),我終於設法解決了這個問題!

我想為遇到類似問題的任何人發布一個答案,其中string.Substring(i, j)函數不是一個可接受的解決方案來獲取字符串的子字符串,因為字符串太大而你買不起由string.Substring(i, j)完成的複制(它必須複製,因為C#字符串是不可變的,沒辦法)或者string.Substring(i, j)被調用了很多次相同的字符串(就像在我的嵌套for循環中)給垃圾收集器一個艱難的時間,或者就像我的情況一樣!

嘗試

我已經嘗試了許多建議的東西,比如StringBuilderStreams ,在unsafe{}塊中使用IntptrMarshal的非託管內存分配,甚至創建一個IEnumerable並且在​​給定位置內通過引用返回字符。 所有這些嘗試都失敗了,因為必須完成某種形式的數據連接,因為沒有簡單的方法讓我逐字逐句地遍歷我的樹,而不會危及性能。 如果只有一種方法可以同時跨越一個數組中的多個內存地址,就像你可以在C ++中使用一些指針算法那樣...除了有...(@Ivan Stoev的評論)

解決方案

解決方案是使用System.ReadOnlySpan<T> (由於字符串不可變而不能是System.Span<T> ),除其他外,它允許我們在不創建副本的情況下讀取現有數組中的內存地址的子數組。

這段代碼發布了:

string _s1 = s1.Substring(i, j);
if (stree.has(_s1))
{
    score += j - i;
    longest = j - i;
}

改為以下內容:

if (stree.has(i, j))
{
    score += j - i;
    longest = j - i;
}

其中stree.has()現在有兩個整數(子串的位置和長度),並且:

ReadOnlySpan<char> substr = s1.AsSpan(i, j);

請注意, substr變量實際上是對初始s1數組的字符子集的引用,而不是副本! (已通過此函數訪問s1變量)

請注意,在撰寫本文時,我使用的是C#7.2和.NET Framework 4.6.1,這意味著要獲取Span功能,我必須轉到Project> Manage NuGet Packages,勾選“Include prerelease”複選框並瀏覽System 。記憶並安裝它。

重新運行初始測試(長度為1百萬字符的字符串,即1MB),速度從2分鐘以上增加(我在2分鐘後放棄等待)到~86毫秒!







c# .net wpf scrollbar textblock