dev.net.ua

Українська Спільнота Розробникiв
 
Ласкаво просимо до dev.net.ua Увійти | Приєднатися | Допомога | Увійти Live ID
в Пошук

Андрей

Биндинг RichTextBox на FlowDocument

Однажды мне пришлось работать с классом, упрощённое описание которого выглядит так:

public class Test
{

 public string TestText
 {
  get
  {
   if (TestDocument == null) return "Документ не определён"; 
   return new TextRange(TestDocument.ContentStart, TestDocument.ContentEnd).Text;
  }
 }

 public FlowDocument TestDocument { get; set; }

}

Нужно было представить ObservableCollection<Test> таким образом, чтобы в одной части окна в ListBox отображалась некая информация о документах (в нашем случае — это TestText), а в другой части окна в RichTextBox представлялась возможность редактирования документа текущего экземпляра Test.

Упрощённое визуальное представление этой задачи можно описать так:

<DockPanel>

 <ToolBar DockPanel.Dock="Top" Name="toolBar1" Width="Auto">
  <Button Name="Add" Content="Добавить" Click="Add_Click"/>
  <Button Name="Delete" Content="Удалить" Click="Delete_Click"/>
 </ToolBar>

 <Grid DockPanel.Dock="Top" Height="Auto">
  <Grid.ColumnDefinitions>
   <ColumnDefinition/>
   <ColumnDefinition/>
  </Grid.ColumnDefinitions>

  <ListBox Name="listTests" Grid.Column="0" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True">
   <ListBox.ItemTemplate>
    <DataTemplate DataType="local:Test">
     <TextBlock Text="{Binding TestText}"/>
    </DataTemplate>
   </ListBox.ItemTemplate>
  </ListBox>

  <GridSplitter Width="5" Grid.Column="1" HorizontalAlignment="Left"/> 

  <!-- 
  <RichTextBox Name="testBox" Grid.Column="1" Margin="5,0,0,0" Document="{Binding TestDocument}"/>
   -->

 </Grid>
</DockPanel> 

Как видим, для выполнения этой задачи необходимо решить две проблемы:

1. Связать RichTextBox.Document с TestDocument текущего экземпляра Test приблизительно так, как написано в комментарии. Но такое описание недопустимо потому, что RichTextBox.Document не является DependencyProperty.

2. Синхронизировать вводимые в RichTextBox данные с информацией о документе в ListBox. Эта проблема возникает потому, что при редактировании документа в RichTextBox меняется только содержимое документа. Экземпляр документа при этом остаётся прежним.

Решение первой проблемы заключается в создании наследника от RichTextBox, который бы сделал свойство Document свойством зависимости:

public class DocumentBox : RichTextBox
{

 protected override void OnTextChanged(TextChangedEventArgs e)
 {
  if (e.Changes.Count > 0) changedIndicator = true;
  base.OnTextChanged(e);
 }

 public bool ResetChangedIndicator()
 {
  var retValue = changedIndicator;
  changedIndicator =
false;
  return retValue;
 }

 protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
 {
  if (e.Property == DocumentProperty) base.Document = Document;
  base.OnPropertyChanged(e);
 }

 public new FlowDocument Document
 {
  get
  {
  
if ((FlowDocument)GetValue(DocumentProperty) == null) return new FlowDocument();
  
return (FlowDocument)GetValue(DocumentProperty);
  }
  set { SetValue(DocumentProperty, value); }
 }

 public static readonly DependencyProperty DocumentProperty = DependencyProperty.Register("Document", typeof(FlowDocument), typeof(DocumentBox));

 bool changedIndicator;

}

Теперь вы можете вместо RichTextBox использовать DocumentBox. Для этого, вместо закомментированной строчки в XAML нужно написать:

<local:DocumentBox x:Name="testBox" Grid.Column="1" Margin="5,0,0,0" Document="{Binding TestDocument}"/>


Чтобы опробовать это решение, используйте в классе Window1 такой отделённый код:

public Window1()
{
 InitializeComponent();
 DataContext = tests;
}

private void Add_Click(object sender, RoutedEventArgs e)
{
 Test t = new Test();
 t.TestDocument=
new FlowDocument();
 t.TestDocument.FontFamily =
new FontFamilyConverter().ConvertFromString("Times New Roman") as FontFamily;
 t.TestDocument.FontSize = 18.7;
 t.TestDocument.Blocks.Add(
new Paragraph(new Run("Содержимое " + (tests.Count+1) + "-го документа")));
 tests.Add(t);
 listTests.SelectedItem = t;
}

private void Delete_Click(object sender, RoutedEventArgs e)
{
tests.Remove((
Test)listTests.Items.CurrentItem);
}

System.Collections.ObjectModel.ObservableCollection<Test> tests = new System.Collections.ObjectModel.ObservableCollection<Test>();


Попробуйте добавлять и удалять записи, изменять содержимое документа. Как видите, удалось "прибиндить" RichTextBox.Document (точнее DocumentBox.Document) к FlowDocument. Первая проблема решена.

Вторая проблема выражается в том, что ListBox не реагирует на редактирование документов, а хотелось бы.

Решить эту проблему можно двумя способами:

1. Принудительно обновлять список в событии TextChanged DocumentBox-а:

private void DocumentBox_TextChanged(object sender, TextChangedEventArgs e)
{
 if(e.Changes.Count > 0) listTests.Items.Refresh();
}
 

Этот способ даёт красивый эффект, но будет сильно тормозить на больших FlowDocument-ах.


2. Принудительно обновлять список в событии потери клавиатурного фокуса DocumentBox:

private void DocumentBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
if (testBox.ResetChangedIndicator()) listTests.Items.Refresh();
}

Не забудьте обявить соответствующие события в XAML:

<local:DocumentBox x:Name="testBox" Grid.Column="1" Margin="5,0,0,0" Document="{Binding TestDocument}" TextChanged="DocumentBox_TextChanged"/>

или

<local:DocumentBox x:Name="testBox" Grid.Column="1" Margin="5,0,0,0" Document="{Binding TestDocument}" LostKeyboardFocus="DocumentBox_LostKeyboardFocus"/>

Опубліковані Sunday, June 01, 2008 3:08 PM від ServiceDeveloper
Помічено як: ,

Коментарі

Немає коментарів
Анонімні коментарі деактивовані. Увійдіть або Зареєструйтесь щоб мати доступ до ресурсів Спільноти.