dev.net.ua

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

Андрей

  • Реализация биндинга во FlowDocument

    Известно, что подключить прямой биндинг на свойство Run.Text нельзя из-за того, что оно не является свойством зависимости. Поэтому народ как только не изощряется, чтобы организовать биндинг во FlowDocument.

    Одно, из таких изощрений, которым пользуюсь я — подключение обратного биндинга (в режиме OneWayToSource или TwoWay).

    Пример того, как это делается можно посмотреть здесь.

  • В WPF появился DataGrid

    Приятный сюрприз после отпуска, с выходом NET 3.5 SP1 появилась возможность использовать WPF-ный DataGrid.

    Насколько мне известно, Выход SP1 состоялся 11 августа, а 12-го выложили WPFToolkit.dll, правда ещё только CTP. Но MS божится, что скоро будет окончательный релиз.

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

    Далее приводится короткое описание использования  этого контрола полученное мною методом тыка Smile.

    1. Скачиваем DLL.
    2. Разархивируем и суём куда-нибудь.
    3. Создаем тестовое WPF приложение (обязательно SP1).
    4. В Solution Explorer -> References добавляем ссылку на либу.
    5. Создаём источник для DataGrid:

    public class MyClass
    {
    public string Field1 { get; set; }
    public string Field2 { get; set; }
    public string Field3 { get; set; }
    }

    public class MyClasses : System.Collections.ObjectModel.ObservableCollection<MyClass>
    {
    }

    6. Создаём простенькую разметку:

    <Window x:Class="WpfApplicationSP1test.Window1"
    xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
    xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
    xmlns:l="clr-namespace:WpfApplicationSP1test"
    xmlns:dg="clr-namespace:Microsoft.Windows.Controls;assembly=WpfToolkit"
    Title="Тестовое приложение SP1" Height="275" Width="474" WindowStartupLocation="CenterScreen" Name="window1">
     <Window.Resources>
      <l:MyClasses x:Key="dgSource"/>
     </Window.Resources>
     <DockPanel>
      <ToolBar DockPanel.Dock="Top">
       <Button Name="Load" Content="Загрузить" Click="Load_Click"/>
       <Button Name="Save" Content="‘Сохранить" Click="Save_Click"/>
       <Separator Margin="4,0,4,0"/>
       <Button Name="Add" Content="Добавить" Click="Add_Click"/>
      </ToolBar>
     
    <dg:DataGrid DockPanel.Dock="Top" Name="dataGrid" DataContext="{DynamicResource dgSource}" ItemsSource="{Binding}" AutoGenerateColumns="True"/>
     </DockPanel>
    </
    Window>

    7. Пишем обработчики событий в отделённом коде:

    private void Load_Click(object sender, RoutedEventArgs e)
    {
     
    var fs = new System.IO.FileStream("dgSource.XAML", System.IO.FileMode.Open);
     
    var dgSource = System.Windows.Markup.XamlReader.Load(fs) as MyClasses;
     
    if (dgSource == null) return;
     window1.Resources.Remove(
    "dgSource");
     window1.Resources.Add(
    "dgSource", dgSource);
    }

    private void Save_Click(object sender, RoutedEventArgs e)
    {
     var dgSource = window1.FindResource("dgSource") as MyClasses;
     if (dgSource == null) return;
     var fs = new System.IO.FileStream("dgSource.XAML", System.IO.FileMode.Create);
     System.Windows.Markup.
    XamlWriter.Save(dgSource, fs);
     fs.Close();
    }

    private void Add_Click(object sender, RoutedEventArgs e)
    {
     var dgSource = dataGrid.ItemsSource as MyClasses;
     if (dgSource == null) return;
     dgSource.Add(
    new MyClass());
    }

    8. Компилируем и запускаем на выполнение.

    Что понравилось:
    1. С источниками данных работает также, как и ListBox (может быть есть и другие варианты, пока ещё не смотрел).
    2. Без лишних телодвижений работает сортировка столбцов (в том числе и по нескольким столбцам при нажатом Shift).
    3. Нормально работает прямой и обратный (на источник) биндинг, поэтому легко управлять данными, манипулируя непосредственно источником. Не требуются всякие там AcceptText...

    Не понравилось:
    Удаление строк клавишей <Del>. По моему мнению это не очень удобно и может привести к путанице при редактировании ячеек. К примеру, пользователь при вводе данных ошибся, при помощи <Del> что-то удалил, нажал <Enter>, а потом решил что-то доудалить и ещё раз нажал <Del>, после чего исчезнет вся редактируемая строка. Надеюсь, что это можно как-то переопределить, попробую поколдовать с туннелированными событиями.

    Но в целом впечатление очень даже приятное, планирую в ближайшее время серьёзно заняться изучением этого контрола. Если сообщество проявит интерес, кое-что могу запостить и сюда.

    PS. Если хотите проинициализировать столбцы вручную, нужно перевести AutoGenerateColumns="False" и прописать:

    <dg:DataGrid DockPanel.Dock="Top" Name="dataGrid" DataContext="{DynamicResource dgSource}" ItemsSource="{Binding}" AutoGenerateColumns="False">
     <dg:DataGrid.Columns>
      <dg:DataGridTextColumn Header="Первое поле" DataFieldBinding="{Binding Field1}"/>
      <dg:DataGridTextColumn Header="Второе поле" DataFieldBinding="{Binding Field2}"/>
      <dg:DataGridTextColumn Header="Третье поле" DataFieldBinding="{Binding Field3}"/>
     </dg:DataGrid.Columns>
    </dg:DataGrid>

     

    PS. Внимание всем, кто до сих пор просматривает этот блог!

    Вышел октябрьский релиз WPFToolkit. В отличие от CTP, релиз инсталлируется через MSI. После инсталляции в тулбоксе VS 2008 SP1 появляется вкладка WPF Toolkit.

  • Биндинг 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 | 0 коментарів
    Помічено як: ,
  • Знакомимся с бесплатной объектной базой db4o. Клиент/Сервер.

    В этом посте мы рассмотрим возможности, которые Db4o предоставляет для поддержания технологии Клиент/Сервер (C/S). C/S подразумевает взаимодействие приложений двух видов — сервера, который инициирует обслуживание хранилища, и клиента, который работает с хранилищем через сервер. То что мы делали до сих пор никак не напоминало C/S. Приложение работало непосредственно с файлом контейнера:

    objectContainer = Db4oFactory.OpenFile(config, @"D:\Test.yap");

    Такой режим работы принято называть embedded — встраиваемый режим. Преимуществами embedded являются максимальное быстродействие и предельная простота. Недостаток — невозможность одновременной работы нескольких приложений с одним контейнером.

    Чтобы организовать C/S, нужен сервер. Но в стандартной поставке Db4o нет никакого сервера. Взамен этого, разработчики Db4o предоставляют несколько API методов для реализации C/S. Это значит, что сервер мы должны написать самостоятельно. Сейчас мы этим и займёмся.

    Тип проекта может быть любым, но я выбираю WPF из-за возможности лёгкой реализации консоли при помощи FlowDocumentScrollViewer. Если у вас есть возможность разместить новый проект на удалённом компьютере (например, при помощи удалённого рабочего стола), советую воспользоваться этим для испытания работы Db4o в сети (сеть должна поддерживать протокол TCP/IP). Если такой возможности нет, придется всё делать на одном компьютере, "эмулируя" сеть через localhost. Но, в любом случае нужно придерживаться золотого правила, о котором мы говорили раньше — каждое приложение, работающее с контейнером должно иметь доступ к сборке с описанием классов этого контейнера. Эта сборка должна быть единой для всех приложений, использующих данный контейнер. И сервер здесь не исключение. Поэтому в В Solution Explorer -- References обязательно должна быть добавлена ссылка на наше WindowsFormsApplication, которое использовалось для создания и модификации контейнера. Сделайте это так, как мы делали в посте Знакомимся с бесплатной объектной базой db4o. Запросы. Не забудьте также добавить ссылку на Db4objects.Db4o.dll.

    В XAML проекта вставьте в грид такой код:

    <FlowDocumentScrollViewer Name="flowDocumentScrollViewer1">

     <FlowDocument Name="Reporter">

      <Paragraph></Paragraph>

     </FlowDocument>

    </FlowDocumentScrollViewer>

    В Window1.xaml.cs объявите:

    using Db4objects.Db4o;

    using Db4objects.Db4o.Messaging;

    using WindowsFormsApplicationDB; //У вас – ваше WindowsFormsApplication


    В классе Window1 создайте два поля:

    string containerFile = @"D:\Test.yap";

    IObjectServer server;


    Назначьте событие Window_Loaded:

    server = Db4oFactory.OpenServer(containerFile, 4488);

    ToReporter("Запущен сервер Db4o");

    ToReporter("Открыт контейнер " + containerFile);

    server.GrantAccess("user1", "password1");

    server.GrantAccess("user2", "password2");

    ToReporter("Открыт доступ пользователям user1, user2");

    и событие Window_Closed:

    server.Close();

    метод ToReporter нужен для стекового вывода информации на консоль:

    void ToReporter(string msg)

    {

     Paragraph p = new Paragraph(new Run(DateTime.Now.ToString() + " -- " + msg));

     Reporter.Blocks.InsertBefore(Reporter.Blocks.FirstBlock, p);

    }

    Для начала этого вполне достаточно. Можете запускать свой сервер.


    Теперь, пора подумать о клиенте. Как вы уже догадались, клиентами будут WindowsFormsApplication и WpfApplication, которые мы создавали и модифицировали в предыдущих постах.

    Начнём с нашего WindowsFormsApplication. В событии Form1_Load закомментируйте строчку:

    //objectContainer = Db4oFactory.OpenFile(config, @"D:\Test.yap");

    и вместо неё впишите:

    objectContainer = Db4oFactory.OpenClient(config, "server", 4488, "user1", "password1");

    где "server" — имя или IP адрес компьютера, на котором запущен сервер, если всё делается на одном компьютере, напишите "localhost".

    Приятным фактом является то, что клиент реализует интерфейс IObjectContainer, поэтому всё что мы дописывали в ObjectContainerExtend или могли бы реализовать в классе-обёртке остаётся в силе. Это вовсе не означает, что принцип построения приложений embedded и C/S одинаков, в этом мы скоро убедимся. Но в построенную нами объектную модель никаких изменений вносить не надо.

    Теперь вы можете запустить несколько копий приложения WindowsFormsApplication. Внесите изменения в одной копии и нажмите кнопку Save. Нажмите Load в другой копии и... увидите, что ничего не изменилось. Для того, чтобы понять, почему данные не обновляются, нужно иметь представление о механизме транзакций в C/S приложениях. Если у вас есть познания о транзакциях, реализуемых RDBMS, этого вполне достаточно, Db4o здесь не предлагает ничего нового. Первая транзакция начинается при открытии клиента и завершается методами Commit() или Rollback(), которые начинают следующую транзакцию. Commit утверждает изменения, внесённые в контейнер в течение завершаемой транзакции (после чего эти изменения могут видеть другие приложения), Rollback отвергает изменения, выполняя "откат" на начало завершаемой транзакции. Commint выполняется также методом IObjectContainer.Close(); В Db4o нет понятия аutocommit, хотя ничто не мешает вам реализовать это, перегрузив методы Set и Delete.

    Итак, если мы хотим видеть внесённые в контейнер изменения из другой копии приложения, нужно завершать транзакцию в событиях toolStripButtonSave_Click и toolStripButtonSave2_Click. Для этого допишите там строчку:

    objectContainer.Commit();


    Давайте ещё раз попробуем внести изменения в одной копии приложения и увидеть их в другой. И опять вы будете разочарованы. Несмотря на то, что транзакция завершилась и все изменения в контейнере утверждены, вторая копия, скорее всего их не отобразит. Виною тому — кэш. Логика кзширования объектов в Db4o приблизительно такова — если объект уже есть в оперативной памяти, зачем его тянуть туда ещё раз? В этом случае объект не извлекается из контейнера, а запросу передается ссылка на существующий объект. При помощи такого подхода развивается максимальная скорость обмена данными с контейнером. В embedded это не вызывает особых проблем, кроме проблемы активации, которую мы подробно рассмотрели в прошлом посте. Но в C/S возникает проблема — так называемое "старение объектов". Находящийся в контейнере, объект может в любой момент измениться, в то время, как копии этого объекта в памяти клиентов остаются неизменными (стареют). В конечном итоге может возникнуть ситуация, которая в OOBD называется несоответствием моделей, когда модель, отображаемая вашим компьютером критически отличается от модели в хранилище. Для борьбы с этим явлением существует два различных подхода. Первый подход состоит в принудительном обновлении объектов, не зависимо от их наличия в оперативной памяти. Это несколько снизит производительность, но увеличит надёжность вашего приложения. В Db4o для этого существует специальный метод Refresh. Чтобы обновление в нашем WindowsFormsApplication осуществлялось без проблем, измените метод RefreshGrid1:

    bindingSource1.DataSource = new EditList<Country>(objectContainer.Query<Country>().ToList());

    foreach( var obj in ((EditList<Country>)bindingSource1.DataSource)) objectContainer.Ext().Refresh(obj, int.MaxValue);

    bindingSource1.ResetBindings(false);


    и метод RefreshGrid2:

    if (country==null) bindingSource2.DataSource = new EditList<Region>(objectContainer.Query<Region>().ToList());

    else bindingSource2.DataSource = new EditList<Region>(country);

    foreach (var obj in ((EditList<Region>)bindingSource2.DataSource)) objectContainer.Ext().Refresh(obj, int.MaxValue);

    bindingSource2.ResetBindings(false);

    Второй подход состоит в открытии клиента только на время операций чтения-записи, с последующим его закрытием, после чего кеш сбрасывается автоматически. Этот подход мы чуть позже используем в нашем WpfApplication, с которым мы работали в прошлом посте.


    А сейчас давайте рассмотрим ещё одну возможность, которую Db4o предоставляет для поддержания C/S технологии. Это работа сервера в режиме RECIPIENT — когда сервер может принимать сообщения от клиентов (Out-of-band signalling). Для демонстрации этой возможности мы будем передавать в консоль сервера данные о клиенте. Дополним наше WindowsFormsApplication:

    В файле Entities.cs создадим новый класс:

    public class ConnectInfo

    {

     public ConnectInfo(string userName, bool connect)

     {

      User = userName;

      ConnectStatus = connect;

     }

     public string User;

     public bool ConnectStatus;

     public string AppName;

     public string Host;

     public string Address;

    }


    Теперь дополним класс ObjectContainerExtend ещё одним методом:

    public static void ConnectMessage(this IObjectContainer container, string user, bool status)

    {

     Db4objects.Db4o.Messaging.IMessageSender sender = container.Ext().Configure().ClientServer().GetMessageSender();

     ConnectInfo ci = new ConnectInfo(user, status);

     ci.Host = System.Net.Dns.GetHostName();

     ci.Address = System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName())[0].ToString();

     ci.AppName = System.Reflection.Assembly.GetEntryAssembly().GetName().Name;

     sender.Send(ci);

    }


    Использовать этот метод будем после открытия клиента в событии Form1_Load:

    ..............

    objectContainer = Db4oFactory.OpenClient(config, "server", 4488, "user1", "password1");

    objectContainer.ConnectMessage("user1", true);

    ..............


    и перед закрытием клиента в событии Form1_FormClosed:

    objectContainer.ConnectMessage("user1", false);

    objectContainer.Close();

    Конечно, это не очень удобно, лучше было бы перегрузить методы OpenClient и Close в классе-обёртке.

    Для того, чтобы сообщения принимались сервером, необходимо "настроить" сервер в режиме recipient. Откройте проект сервера и допишите в событие Window_Loaded строку:

    server.Ext().Configure().ClientServer().SetMessageRecipient(this);

    где — this это любой класс, реализующий интерфейс ImessageRecipient. В нашем случае, был использован класс Window1:

    public partial class Window1 : Window, IMessageRecipient

    Интерфейс IMessageRecipient требует реализации всего лишь одного метода void ProcessMessage(IObjectContainer container, object message). Этот метод будет вызываться каждый раз, когда кто-нибудь из клиентов отошлёт сообщение. В нашем случае он будет выглядеть так:

    ConnectInfo ci = message as ConnectInfo;

    if (ci == null) return;

    Action InvokeMethod = () =>

     {

      string msginfo = "клиент " + ci.User + "; Приложение " + ci.AppName + "; Компьютер " + ci.Host + "; IP адрес " + ci.Address;

      ToReporter((ci.ConnectStatus ? "Подключен " : "Отключен ") + msginfo);

     };

    Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background, InvokeMethod);

    Откомпилируйте проект и запустите сервер. Теперь запустите своё WindowsFormsApplication.


    И в заключении "переведём на C/S" наше WpfApplication, которое мы использовали в прошлом посте. Для этого заккоментируйте везде, где это встречается (flowDocumentScrollViewer1_Loaded, Window_Closed, button1_Click) строки открывающие контейнер:

    //objectContainer = Db4oFactory.OpenFile(@"D:\Test.yap");

    и закрывающие контейнер:

    //objectContainer.Close();


    В начале события button1_Click впишите:

    objectContainer = Db4oFactory.OpenClient("server", 4488, "user2", "password2");

    objectContainer.ConnectMessage("user2", true);

    и завершите это событие строками:

    objectContainer.ConnectMessage("user2", false);

    objectContainer.Close();


    Теперь запустите сервер и два клиентских приложения: WindowsFormsApplication и WpfApplication. Вносите изменения в WindowsFormsApplication и смотрите, как "отрабатывает" Query в WpfApplication.

    Надіслане Friday, April 04, 2008 3:53 PM від ServiceDeveloper | 0 коментарів
    Помічено як:
  • Знакомимся с бесплатной объектной базой db4o. Активация.

    Для демонстрации принципов активации объектов в Db4o, мы будем использовать WpfApplication из поста Знакомимся с бесплатной объектной базой db4o. Запросы. Но перед этим давайте немного изменим содержимое нашего контейнера в приложении WindowsFormsApplication, которое мы модифицировали в прошлом посте Знакомимся с бесплатной объектной базой db4o. Реализация сущностей.

    Если помните, тогда мы добавили новый файл Entities.cs и разместили в нём сущности Country и Region. Теперь добавим новую сущность City и рефлекторно свяжем её с Region. То, что должно в итоге получиться, можно увидеть здесь.

    В Analyse.cs перепишем событие toolStripButtonRestructuring_Click:

    var regions = new string[] { "Киевская область", "Черкасская область" };

    var cities1 = new string[] { "Киев", "Боярка", "Обухов", "Фастов", "Ржищев" };

    var cities2 = new string[] { "Черкассы", "Смела", "Чигирин", "Умань", "Канев" };

    Country country = new Country();

    country.Name = "Украина";

    IObjectSet os = objectContainer.Get(country);

    if (os.Count < 1) return;

    AddRegions((Country)os[0], regions, new string[][] { cities1, cities2 });

    infoSource1.Load(objectContainer);

    bindingSource1.ResetBindings(true);


    а также добавим два метода:

    void AddRegions(Country country, string[] names, string[][]citynames)

    {

     for (int ii = 0; ii < names.Length; ii++)

     {

      Region region = new Region();

      region.Name = names[ii];

      objectContainer.Link(country, region);

      AddCities(region, citynames[ii]);

     }

    }

    и

    void AddCities(Region region, string[] names)

    {

     foreach (var name in names)

     {

      City city = new City();

      city.Name = name;

      objectContainer.Link<City>(region, city);

     }

    }

    Запустите проект на выполнение и сделайте так, чтобы в списке стран была Украина. Перейдите в анализ и удалите все регионы. Нажмите на кнопку Restructuring только один раз. Вернитесь на форму Form1 и введите код Украины, а так же заполните чем-нибудь поля Прим. её регионов. Сохраните изменения и выйдите из приложения.


    Теперь всё готово для наших экспериментов с активацией. Откройте WpfApplication, о котором я говорил в начале поста. Первое, что нужно сделать — добавить в дизайнере вертикальный слайдер с такими свойствами:

    Maximum = 5

    Minimum = 1

    SmallChange = 1

    LargeChange = 1

    TickFrequency = 1

    Value = 5

    Расположите его под кнопкой Query. При помощи этого контрола мы будем менять глубину активации объектов.

    Замените в событии flowDocumentScrollViewer1_Loaded:

    System.IO.FileStream fs = new System.IO.FileStream(@"d:\CountryTemplate.xaml", System.IO.FileMode.Open);

    на

    System.IO.FileStream fs = new System.IO.FileStream(@"d:\CountryInfo.xaml", System.IO.FileMode.Open);

    Файл CountryInfo.xaml можно взять здесь.

    В этом же событии закомментируйте строку:

    //objectContainer = Db4oFactory.OpenFile(@"D:\Test.yap");

    а в событии Window_Closed:

    //objectContainer.Close();


    Теперь перепишем событие button1_Click:

    objectContainer = Db4oFactory.OpenFile(@"D:\Test.yap");

    Country country = new Country();

    country.Name = "Украина";

    objectContainer.Ext().Configure().ActivationDepth((int)Math.Round(slider1.Value));

    var os = objectContainer.Get(country);

    if (os.Count < 1) return;

    FlowDocument fd = new FlowDocument();

    fd.Blocks.AddRange(template.Blocks.CloneDeep());

    flowDocumentScrollViewer1.Document = fd;

    ToFlowDocument((Country)os[0]);

    objectContainer.Close();

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

    Метод ToFlowDocument будет иметь такой вид:

    void ToFlowDocument(Country country)

    {

     var fd = flowDocumentScrollViewer1.Document;

     var paragraphs = fd.Blocks.Paragraphs();

     var tables = fd.Blocks.Tables();

     paragraphs[0].Inlines.RunsTree()[1].Text = country.Name + string.Empty;

     paragraphs[2].ContentEnd.InsertTextInRun(country.Code + string.Empty);

     paragraphs[3].ContentEnd.InsertTextInRun(country.Description + string.Empty);