1

我正在开发一些专门的日志查看器,其中我在列表视图中显示日志条目列表。

该应用程序由一个 Window 组成(实际上,使用Catel,所以它是一个 DataWindow ),其中我有 UI。由于我使用的是 MVVM,因此我还创建了相应的 VM。我的模型是具有 LogEntrys 集合的日志。日志在用户交互时加载到 VM 中。

每个 LogEntry 都有一个 Message 属性,它是一些我想解析为 XAML 并将其部分转换为超链接的文本(字符串属性)。当用户单击超链接时,我想执行一些在主 VM 中定义的命令(必须在那里,因为它使用了一些属于 VM 的属性)。

最初我尝试使用 RichTextBox。由于 WPF 不支持绑定,因此我决定使用 Extended WPF Toolbox(此处)中的 RTB。

我创建了一个自定义ITextFormatter,它读取文本并构建一个FlowDocument(注意在ITextFormatterFlowDocument文档参数)中传入)。在SetText

foreach (var line in text.Split('\n'))
{
    //some manipulations
    Paragraph para = new Paragraph();
    para.Inlines.Add(new Run(manipulatedText1));
    para.Inlines.Add(CreateHyperLink(manipulatedText2));
    document.Blocks.Add(para);
}

CreateHyperLink函数应该构建Hyperlink并设置它的命令和参数:

private Hyperlink CreateHyperlink(string text)
{
    var hLink = new Hyperlink(new Bold(new Run(text)));
    hLink.TargetName = text;
    //Attach a command and set arguments (target etc)
    hLink.Command = ???
    hLink.TargetName = text;
    //Do some formatting
    return hLink;
}

这使我进入了一个阶段,我在 RTB 中看到了我的格式化内容,ListView但它们只是带下划线,表现为常规文本并且没有任何操作。(在这里发布了问题,但还没有答案)。

然后,当我试图找到解决方案时,我偶然发现了FlowDocumentScrollViewer. 我创建了一个IValueConverter解析文本(消息)并使用超链接构建文档的文件。这似乎是一个稍微简单和更清洁的解决方案。使用这种方法,我得到了格式化的消息显示,并且超链接被识别为这样(以蓝色显示并作为一个“整体”单元出现),但仍然无法让命令触发。

因此我有两个问题:

  1. 哪个控件是更好的选择,或者使用每个控件的优缺点是什么?FlowDocumentScrollViewer本质上是只读的,并且可能支持更好的格式(?),但它确实给ListView用鼠标滚动时带来了一些问题(当超过 a 时FlowDocumentScrollViewer,它不会滚动列表,也许可以修复)

  2. 如何将命令从 VM 传递到超链接并让它执行?我认为应该进行一些绑定,但不确定如何/在哪里。在渲染 FlowDocument 时,我尝试在两者中创建ITextFormatterIValueConverter使用ICommand DependancyProperty它的值,但要么它不合法(因为实例是作为静态资源创建的),要么我没有正确绑定它

我试过(在):

<local:TextToFlowDocumentConverter 
         x:Key="textToFlowDocumentConverter" 
         HyperlinkCommand="{Binding NavigateDnHyperlinkCommand, 
         RelativeSource={RelativeSource FindAncestor, 
         AncestorType={x:Type catel:DataWindow}}, Path=DataContext}"/>

我想我可以在 VM 上实例化格式化程序/转换器,但这不是正确的 MVVM ...

顺便说一句,我也尝试在解析时“硬编码”链接(CreateHyperLink上面)

hLink.RequestNavigate += new System.Windows.Navigation.RequestNavigateEventHandler(hLink_RequestNavigate);

这对两个控件都不起作用

另外,我在 XAML 中设置了Hyperlink.Clickand Hyperlink.RequestNavigate(附加属性(?))并将它们放在后面的 Window 代码中 - 这确实有效(注意:在 RTB 的情况下,您必须设置IsDocumentEnabled="True"and IsReadOnly="True"!)

谢谢,

托默

4

1 回答 1

0

正如我上面所写,我试图通过附加行为来做到这一点。不幸的是,我无法让它工作,但我认为这是我缺少的一些细微差别,也许有人可以想出它 - 而且,也许这个答案可能有用。

所以,我试图做的是创建一个新类,该类AttachedProperty可用于更新FlowDocumentHyperlink.Command:

/// <summary>
/// RichTextBox helper class to allow bind command to hyperlinks in RichTextBox.Document
/// </summary>
public class RichTextBoxHyperlinkHelper
{
    /// <summary>
    /// Get the Command associated with DependencyObject
    /// </summary>
    /// <param name="obj">The DependencyObject</param>
    /// <returns>ICommand associated with this DependencyObject</returns>
    public static ICommand GetHyperlinkCommand(DependencyObject obj)
    {
        return (ICommand)obj.GetValue(HyperlinkCommandProperty);
    }

    /// <summary>
    /// Set the ICommand associated with this DependencyObject (RichTextBox)
    /// </summary>
    /// <param name="obj">The DependencyObject (RichTextBox)</param>
    /// <param name="value">The new ICommand value</param>
    public static void SetHyperlinkCommand(DependencyObject obj, ICommand value)
    {
        obj.SetValue(HyperlinkCommandProperty, value);
    }

    // Using a DependencyProperty as the backing store for HyperlinkCommand.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty HyperlinkCommandProperty =
        DependencyProperty.RegisterAttached("HyperlinkCommand", typeof(ICommand), typeof(RichTextBoxHyperlinkHelper),
        new FrameworkPropertyMetadata((RichTextBox)null, new PropertyChangedCallback(OnHyperlinkCommandSet)));

    /// <summary>
    /// A method to run when command is set initialy
    /// </summary>
    /// <param name="d"></param>
    /// <param name="e"></param>
    private static void OnHyperlinkCommandSet(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var rtb = d as RichTextBox;

        if (rtb == null)
        {
            //TODO: Throw?
            return;
        }

        FixHyperLinks(rtb);
    }

    private static void FixHyperLinks(RichTextBox rtb)
    {
        //Get the command attached to this RichTextBox
        var command = GetHyperlinkCommand(rtb);

        if (command != null && rtb.Document != null)
        {
            //Add event handler for data context changed - in which case the .Document may change as well
            rtb.DataContextChanged += new DependencyPropertyChangedEventHandler(rtb_DataContextChanged);

            //Traverse the document, find hyperlinks and set their command
            Queue<Block> blocks = new Queue<Block>();   //Use queue instead of recursion...
            rtb.Document.Blocks.ToList().ForEach(b => blocks.Enqueue(b));   //Add top level blocks

            while (blocks.Count > 0)    //While still blocks to process
            {
                var currentBlock = blocks.Dequeue();    //Get block

                //If paragraph - check inlines for Hyperlinks
                if (currentBlock is Paragraph)
                {
                    foreach (var item in (currentBlock as Paragraph).Inlines)
                    {
                        //If an Hyperlink - set its command
                        if (item is Hyperlink)
                        {
                            (item as Hyperlink).Command = command;
                        }
                        //TODO: process child inlines etc
                    }                       
                }

                //TODO: Process other types of blocks/child blocks
            }

            //Make sure document is enabled and read only so Hyperlinks work
            rtb.IsDocumentEnabled = true;
            rtb.IsReadOnly = true;
        }
    }

    //On case of data context change - rehook the command (calling FixHyperLinks)
    static void rtb_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var rtb = sender as RichTextBox;

        if (rtb != null)
            FixHyperLinks(rtb);
    }
}

然后,据说,它是这样使用的:

<ext:RichTextBox Text="{Binding Message}" IsDocumentEnabled="True" IsReadOnly="True"
                 local:RichTextBoxHyperlinkHelper.HyperlinkCommand="{Binding RelativeSource=
                 {RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}},
                 Path=DataContext.NavigateDnHyperlinkCommand, NotifyOnSourceUpdated=True}">
    <ext:RichTextBox.TextFormatter>
        <local:TextDnRtfFormatter />
    </ext:RichTextBox.TextFormatter>
    <ext:RichTextBox.Resources>
        <Style TargetType="{x:Type Paragraph}">
            <Setter Property="Margin" Value="2"/>
        </Style>                                    
    </ext:RichTextBox.Resources>
</ext:RichTextBox>

单步执行代码时,我确实看到超链接的命令已正确设置为从 VM 绑定的命令,但单击超链接时没有任何反应。

毫无价值的是,如果我将内部命令设置为内部TextDnRtfFormatter定义的命令,它确实会触发旧命令,即使它应该被设置在RichTextBoxHyperlinkHelper.

此外,我还尝试使用行为来解决这个EventToCommand问题TextDnRtfFormatterIMessageMediator

于 2012-05-04T16:49:03.343 回答