суббота, 25 декабря 2010 г.

Создание WPF компонента для отображения сети

Возникло две интересные задачки. В этой статье я раскажу о процессе решения первой из них.
Итак, формулировка задачи:
Есть детали (сборки, компоненты), эти детали собираются из других деталей (правда напоминает дерево?), разные детали могут собираются из одних и тех же деталей. Для наглядности, картинка:

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


Определимся с дизайном. Компонент предполагаю сделать такого вида:
Сразу оговорюсь, при решении этой задачи, я пренебрегу описанием работы с базой данных. В рамках данной задачи, у меня уже есть объектная модель следующего вида:

 Два класса вида: "...Collection" - это классы содержащие коллекцию объектов чье имя и будет вместо точек. Детали (Component) связаны друг с другом отношением многие ко многим, вот для описания этих связей и используется mm_ComponentComponent. Имеющий ссылки на соборку в которую входит деталь, и собственно на саму деталь. Как я уже сказал у нас есть две коллекции одна содержит список всех компонентов, вторая список всех связей между компонентами.

Для удобства биндинга, коллекций к пользовательскому интерфейсу я сделю класс "обертку", который будет содержать информацию по компоненту и список ссылок на "обертки" для компонентов в которые входит данный, плюс ссылку на "обертки" компонентов из которых он собирается.
Обертка будет иметь вид:

    class ComponentWrapper : DependencyObject
    {
 
        public Component Component
        {
            get { return (Component)GetValue(ComponentProperty); }
            set { SetValue(ComponentProperty, value); }
        }
 
        // Using a DependencyProperty as the backing store for Component.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ComponentProperty =
            DependencyProperty.Register("Component"typeof(Component), typeof(ComponentWrapper), new UIPropertyMetadata(null));
 
 
 
        public IEnumerable<ComponentWrapper> Parents
        {
            get { return (IEnumerable<ComponentWrapper>)GetValue(ParentsProperty); }
            set { SetValue(ParentsProperty, value); }
        }
 
        // Using a DependencyProperty as the backing store for Parents.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ParentsProperty =
            DependencyProperty.Register("Parents"typeof(IEnumerable<ComponentWrapper>), typeof(ComponentWrapper), new UIPropertyMetadata(null));
 
 
 
        public IEnumerable<ComponentWrapper> Children
        {
            get { return (IEnumerable<ComponentWrapper>)GetValue(ChildrenProperty); }
            set { SetValue(ChildrenProperty, value); }
        }
 
        // Using a DependencyProperty as the backing store for Children.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ChildrenProperty =
            DependencyProperty.Register("Children"typeof(IEnumerable<ComponentWrapper>), typeof(ComponentWrapper), new UIPropertyMetadata(null));
 
        public ComponentWrapper(Component p_component)
        {
            Component = p_component;
            Parents = null;
            Children = null;
        }
    }
 
Итак, теперь ставится задача отразить коллекции ComponentCollection и mm_ComponentComponentCollection в коллекцию ComponentWrapper-ов.
Это можно сделать следующим образом:

        public List<ComponentWrapper> ComponentWrappers
        {
            get { return (List<ComponentWrapper>)GetValue(ComponentWrappersProperty); }
            set { SetValue(ComponentWrappersProperty, value); }
        }
 
        // Using a DependencyProperty as the backing store for ComponentWrappers.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ComponentWrappersProperty =
            DependencyProperty.Register("ComponentWrappers"typeof(List<ComponentWrapper>), typeof(ComponentsViewModel), new UIPropertyMetadata(null));
 
 
        private void RefreshData()
        {
            // Заполнение данными объектной модели
            ComponentCollection components = null;
            ORMService.ClientProxy.u_obj_Component_Sel_All(out components);
            mm_ComponentComponentCollection componentComponents = null;
            ORMService.ClientProxy.u_man_mm_ComponentComponent_Sel_All(out componentComponents);
            // Заполняем коллекцию оберткок объектами
            ComponentWrappers = (from Component comp in components
                                 select new ComponentWrapper(comp)
                                 ).ToList();
            // Заполняем в каждой обертке связи к "родителям" и "детям"
            foreach (ComponentWrapper currentCW in ComponentWrappers)
            {
                List<Guid> parentIDs =
                    (from mm_ComponentComponent cc in componentComponents
                    where cc.ChildComponent.ID == currentCW.Component.ID
                    select cc.ParentComponent.ID
                    ).ToList(); // ТоList для того чтобы в следующем запросе
                                // не приходилось каждый раз заново формировать
                                // коллекцию
                currentCW.Parents =
                    from ComponentWrapper cw in ComponentWrappers
                    where parentIDs.Contains(cw.Component.ID) // Вот здесь
                    select cw;
                List<Guid> childrenIDs =
                    (from mm_ComponentComponent cc in componentComponents
                     where cc.ParentComponent.ID == currentCW.Component.ID
                     select cc.ChildComponent.ID                    
                    ).ToList(); // Вы не поверите, но эта конструкция не Ctrl C, V
                currentCW.Children =
                    from ComponentWrapper cw in ComponentWrappers
                    where childrenIDs.Contains(cw.Component.ID)
                    select cw; // И это я тоже набирал руками ;)
            }
        }

Для показа основного списка компонентов воспользуемся DataGrid-ом. Для этого его привяжем к полю ComponentWrappers. Обратите внимание, что оно же является источником элементов для показа.

        <DataGrid AutoGenerateColumns="False" x:Name="dgMain" Grid.Row="1" Grid.RowSpan="2" ItemsSource="{Binding}" DataContext="{Binding ComponentWrappers}">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Компонент" Binding="{Binding Component.Name}"/>
            DataGrid.Columns>            
        DataGrid>

Для показа списка компонентов в которые входит выбранный, также будем использовать  
DataGrid, для которого источником данных бедет являться выбранный элемент из первого грида.


        <DataGrid Grid.Column="1" Grid.Row="1" ItemsSource="{Binding ElementName=dgMain,Path=SelectedItem.Parents}">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Компонент" Binding="{Binding Component.Name}"/>
            DataGrid.Columns>
        DataGrid>
 Аналогично для показа из чего состоит текущий компонент. Принципиальное отличие заключается только в том, что нам придется применить TreeView и написать для него иерархический шаблон данных.
В ресурсках окна/грида компоновки можем задать шаблон так:
            <HierarchicalDataTemplate x:Key="hdtChildren" ItemsSource="{Binding Children}">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Component.Name}" />                    
                StackPanel>
            HierarchicalDataTemplate>
Ну и соответственно дерево также привязываем к выбранному элементу в первом списке.

        <TreeView Grid.Column="1" Grid.Row="2" ItemsSource="{Binding ElementName=dgMain,Path=SelectedItem.Children}" ItemTemplate="{StaticResource hdtChildren}">            
        TreeView>
Выглядет это все вот так:
 

Все для этой статьи хватит. В следующей раскажу как реализовал добавление и редактирование данных.

P.s. Тестовые данные для примера были сгенерированны ручками. Рекомендую в качестве домашнего задания, попробовать реализовать это все самостоятельно.  Будут вопросы, пишите, чем смогу, помогу.

Комментариев нет:

Отправить комментарий