2012-05-04 6 views
1

Я разрабатываю специализированный просмотрщик журналов, в котором я отображаю список записей журнала в виде списка.FlowDocument/RichTextBox с гиперссылками с использованием MVVM

Приложение состоит из окна (фактически, используя Catel, так что это DataWindow), и в нем у меня есть интерфейс. Поскольку я использую MVVM, я также создал соответствующую виртуальную машину. Моя модель - это журнал, в котором есть коллекция LogEntrys. Журнал загружается в виртуальную машину при взаимодействии с пользователем.

Каждый LogEntry имеет свойство Message, которое представляет собой некоторый текст (свойство string), который я хочу проанализировать в XAML и преобразовать его части в гиперссылки. Когда пользователь нажимает на гиперссылку, я хочу выполнить некоторую команду, которая определена в главной виртуальной машине (она должна быть там, так как она использует некоторые свойства, которые принадлежат VM).

Первоначально я пытался использовать RichTextBox. Поскольку WPF не поддерживает привязку, я решил использовать RTB из Extended WPF Toolbox (here).

Я создал обычай 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 но они только подчеркнуты, ведут себя как обычный текст и не действуют. (Добавлено вопрос here, но ответа еще нет).

Затем, пытаясь найти решение, я наткнулся на FlowDocumentScrollViewer. Я создал IValueConverter, которые анализируют текст (сообщение) и строят документ с помощью гиперссылки. Это кажется немного более простым и чистым решением. Используя этот подход, я получил отформатированный дисплей сообщений, и Гиперссылки распознаются как таковые (отображаются синим цветом и как единое целое), но все равно не удастся запустить команду.

Таким образом, у меня есть два вопроса:

  1. Какой контроль является лучшим выбором, или каковы плюсы и минусы использования каждого из них? FlowDocumentScrollViewer неотъемлемо только для чтения и может поддерживать лучшее форматирование (?), Но она дает некоторые проблемы с прокруткой ListView с помощью мыши (когда над FlowDocumentScrollViewer, не прокручивать список, возможно, может быть исправлено)

  2. Как мне нужно передать команду с виртуальной машины на гиперссылку с ее выполнением? Я предполагаю, что некоторые привязки должны быть выполнены, но не уверены, как/где. Я пытался создать в обоих ITextFormatter и IValueConverterICommandDependancyProperty и использовать его значение при визуализации FlowDocument но либо он не является законным (как экземпляр создается как статический ресурс), или я не привязывая его правильно

Я пытался (в):

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

Я предполагаю, что я могу создать экземпляр форматера/конвертер на виртуальной машине, но это не собственно MVVM ...

BTW, я также пытался «жесткий код» ссылку при разборе (CreateHyperLink выше)

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

и это не сработало, как для управления

Я дополнение, я поставил в XAML в Hyperlink.Click и Hyperlink.RequestNavigate (? присоединенные свойства()) и иметь их в коде окна позади - и это делает работу (Примечание: в случае RTB вы должны установить IsDocumentEnabled="True" и IsReadOnly="True")

Спасибо,

Томер

+0

Я думаю, что возможным направлением будет присоединенное свойство ... работающее над ним - обновляется позже. Если это не очень хорошая идея - я был бы рад узнать –

ответ

0

Как я уже писал выше, я пытался сделать это с прикрепленным поведения. К сожалению, мне не удалось заставить его работать, но я думаю, что это некоторые нюансы, которые мне не хватает, и, возможно, кто-то может придумать это, а также, может быть, этот ответ может быть полезен.

Итак, что я пытался сделать, это создать новый класс с AttachedProperty который может быть использован для обновления FlowDocument «s 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 команде, которая определена внутри, она запускает старую команду, даже если она якобы была заменена командой из VM, которая установлена ​​в RichTextBoxHyperlinkHelper.

Кроме того, я также пытаюсь использовать поведение, чтобы решить эту проблему (EventToCommand), и в конечном итоге я решил его не методом MVVM, отправив сообщение от TextDnRtfFormatter и поймав его на виртуальной машине (используя Catel IMessageMediator)

Смежные вопросы