2012-05-04 64 views
1

我正在開發中,我在列表視圖中顯示的日誌條目列表一些專門的日誌查看器超鏈接。的FlowDocument/RichTextBox的使用MVVM

應用程序由一個窗口(實際上,使用Catel,所以這是一個數據窗口),並在其中我有UI。由於我使用MVVM,我還創建了相應的VM。我的模型是具有LogEntrys集合的日誌。日誌在用戶交互時加載到VM中。

每個LogEntry具有欲解析成XAML和變換它的部分爲超鏈接消息屬性,它是一些文本(字符串屬性)。當用戶單擊超鏈接時,我想執行一些在主虛擬機中定義的命令(必須在那裏,因爲它使用了屬於虛擬機的一些屬性)。

起初我試圖使用的RichTextBox。由於WPF一個不支持綁定我決定從擴展WPF工具箱(here)使用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; 
} 

這讓我來,我看到在ListView我在RTB格式化內容的階段但它們只是加下劃線,表現爲普通文本並且沒有任何操作。 (發佈的問題here,但沒有答案)。

然後,試圖找到一個解決方案,我偶然發現了FlowDocumentScrollViewer時。我創建了一個IValueConverter解析文本(消息)並用超鏈接構建文檔。這似乎是一個更簡單,更清潔的解決方案。使用這種方法,我得到了格式化的消息顯示,並且超鏈接被識別爲這樣(以藍色和「整個」單位出現),但仍然沒有運行命令來觸發。

因此,我有兩個問題:

  1. 其中控制是更好的選擇,或者有什麼用每個人的優點和缺點? FlowDocumentScrollViewer本質上是隻讀的,可以支持更好的格式(?),但它確實給一些問題,用鼠標滾動ListView(當在FlowDocumentScrollViewer,它不會滾動列表,也許可以是固定的)

  2. 如何我是否需要將命令從VM傳遞到超鏈接並執行?我假設一些綁定應該完成,但不知道如何/在哪裏。我想在這兩個ITextFormatterIValueConverter創建ICommandDependancyProperty和渲染的FlowDocument,但要麼是不合法的(作爲實例爲靜態資源創建)或我不正確綁定它

時使用它的價值我試過(中):

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

我想我可以實例化虛擬機上的格式化/轉換器,但是這不是正確的MVVM ...

順便說一句,我也解析(上CreateHyperLink

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

時,這並沒有爲這兩個控件工作

我除了試圖「硬編碼」的鏈接,我在XAML的Hyperlink.Click設置和Hyperlink.RequestNavigate(?附加屬性()),並讓他們在窗口的代碼背後 - 這不工作(注:在RTB的情況下,你必須設置IsDocumentEnabled="True"IsReadOnly="True"

謝謝,

Tomer

+0

我認爲,一個可能的方向將是一個附加屬性...它的工作 - 更新後。如果這不是一個好主意 - 我很樂意知道 –

回答

0

正如我上面寫的,我試圖用附加的行爲來做到這一點。不幸的是,我無法得到它的工作,但我認爲這是我想念的一些細微差別,也許有人可以想出它 - 也可能這個答案是有用的。

所以,我試圖做的是創建具有AttachedProperty一個新的類,它可以被用來更新FlowDocument的Hyperlink.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> 

當通過代碼加強我看到的超級鏈接的命令被正確設置從虛擬機綁定的命令,但點擊超鏈接時,沒有任何反應。

這是值得什麼,如果我設置的命令裏面TextDnRtfFormatter到被定義在內部它不火,即使它據稱是由該命令從在RichTextBoxHyperlinkHelper設置VM代替了舊的命令的命令。此外,我還嘗試使用行爲來解決此問題(EventToCommand),最終我以非MVVM的方式通過發送來自TextDnRtfFormatter的消息並將其捕獲到VM中(通過使用Catel IMessageMediator