Re: ListBox, многие ко многим и binding - как надо?
От: Just1769 Россия  
Дата: 06.12.05 16:38
Оценка:
Здравствуйте, Just1769, Вы писали:

J>Есть типизированный датасет, две таблицы с объектами и таблица связи.

J>Между ними в дизайнере протянуты рилейшены.
J>Таблицы: Rubrics (Id, Caption), Articles (Id, Title) и ArticlesRubrics (RubricId, ArticleId).
J>Рилейшены: RubricArticles и ArticleRubrics.

J>Вопрос: Как сделать автоматическое создание/удаление строк в таблице ArticlesRubrics?


J>А как задать начальное выделение и обновление таблицы ArticlesRubrics при изменении выделеных пунктов ListBox — не нашёл.


Вариантов я не нашёл, поэтому дописал нужное к стандартному ListBox.
Кому надо, пользуйтесь на здоровье, если кто-то допишет что-то интересное, пишите на email. Если уже есть что-то интересное, то тоже напишите на email =)

/**********************************************************************

    LinksListBox.cs

    $Author: Andrew Ivanov (andrew.ivanov@gmail.com) $
    $Date: 2005/12/06 18:49:00 $
    created at: 2005/12/05 20:13:00

    Copyright (C) 2005 Andrew Ivanov
    License: LGPL

**********************************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Data;
using System.ComponentModel;

namespace AI.Controls
{
    /// <summary>
    /// Класс ListBox'а для реализации связи многие-ко-многим.
    /// </summary>
    /// <remarks>
    /// Работает только с BindingSource и считает, что связь реализована двумя long'ами. Может быть не работает с отдельными таблицами, создавалось для работы с типизированными датасетами.
    /// BindingSource связей должен быть привязан (его DataSource должен быть задан BindingSource'ом primary данных) к primary данным (например, к статьям, которые связываются с рубриками).
    /// Для простоты пояснения есть типизированный датасет Data: таблицы Articles(Id, Caption), Rubrics(Id, Caption), ArticlesRubrics(ArticleId, RubricId) и ключи ArticleRubrics(Articles.Id, ArticlesRubrics.ArticleId), RubricArticles(Rubrics.Id, ArticlesRubrics.RubricId).
    /// На примере статей должно быть три BindingSource: articlesBindingSource, articleRubricsBindingSource и rubricsBindingSource.
    /// Для articlesBindingSource задаются: DataSource = Data, DataMember = "Articles".
    /// Для articleRubricsBindingSource задаются: DataSource = articlesBindingSource, DataMember = "ArticleRubrics".
    /// Для rubricsBindingSource задаются: DataSource = Data, DataMember = "Rubrics".
    /// При этом articleRubricsBindingSource будет содержать только связи активной статьи и можно смело задавать лишь второе значение, RubricId, потому что ArticleId будет браться из Current articlesBindingSource'а.
    /// SelectedValue LinksListBox'а должен быть задан так: articleRubricsBindingSource -> RubricId (выбирается из дерева DataSource'ов).
    /// Из-за проблем биндинга SelectedValue к articleRubricsBindingSource этот биндинг удаляется, но его параметры запоминаются, чтобы поддерживать обновления.
    /// </remarks>
    public class LinksListBox : ListBox, ISupportInitialize
    {
        #region Мемберы и константы

        /// <summary>
        /// Имя свойства SelectedValue в ListBox, куда прописывается биндинг для связей.
        /// </summary>
        protected const string SelectedValuePropertyName = "SelectedValue";

        /// <summary>
        /// BindingSource, куда пишутся связи.
        /// </summary>
        private BindingSource targetSource;

        /// <summary>
        /// Название столбца, куда пишется выбранный в ListBox идентификатор связи.
        /// </summary>
        private string targetMemberName;

        /// <summary>
        /// Флаг для возможности обновления связей при изменении выделения ListBox'а
        /// </summary>
        private bool canUpdateLinks = false;

        /// <summary>
        /// Флаг для определения самоудаления биндинга SelectedValue.
        /// </summary>
        private bool inSelectedValueBindingChanged = false;

        /// <summary>
        /// Флаг манипуляции с данными, чтобы не появилось зацикливания.
        /// </summary>
        private bool isManipulating = false;

        /// <summary>
        /// Флаг нахождения контрола в режиме инициализации.
        /// </summary>
        private bool inInitializingMode = false;

        #endregion  Мемберы и константы

        #region Конструкторы

        public LinksListBox()
            : base()
        {
            DataBindings.CollectionChanged += new CollectionChangeEventHandler(SelectedValueBindingChanged);
        }

        #endregion  Конструкторы

        #region Обработка событий изменения свойств ListBox

        /// <summary>
        /// Обработчик события изменения свойства DataSource.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnDataSourceChanged(EventArgs e)
        {
            CheckForUpdateLinksCapability();
            base.OnDataSourceChanged(e);
        }

        /// <summary>
        /// Обработчик события изменения свойства ValueMember.
        /// </summary>
        /// <param name="e">Параметры события.</param>
        protected override void OnValueMemberChanged(EventArgs e)
        {
            CheckForUpdateLinksCapability();
            base.OnValueMemberChanged(e);
        }

        /// <summary>
        /// Обрабатывает событие добавления биндингов к ListBox'у.
        /// </summary>
        /// <param name="sender">Иницатор события.</param>
        /// <param name="e">Параметры события.</param>
        protected void SelectedValueBindingChanged(object sender, CollectionChangeEventArgs e)
        {
            if (!inSelectedValueBindingChanged && !DesignMode)
            {
                inSelectedValueBindingChanged = true;
                try
                {
                    Binding binding = (Binding)e.Element;
                    switch (e.Action)
                    {
                        case CollectionChangeAction.Add:
                            if (SelectedValuePropertyName == binding.PropertyName)
                            {
                                targetSource = (BindingSource)binding.DataSource;
                                targetMemberName = binding.BindingMemberInfo.BindingMember;
                                DataBindings.Remove(binding);
                                targetSource.ListChanged += new ListChangedEventHandler(targetSourceItemsListChanged);
                            }
                            CheckForUpdateLinksCapability();
                            break;
                        case CollectionChangeAction.Refresh:
                            break;
                        case CollectionChangeAction.Remove:
                            if (SelectedValuePropertyName == binding.PropertyName)
                            {
                                targetSource = null;
                                targetMemberName = null;
                                CheckForUpdateLinksCapability();
                            }
                            break;
                        default:
                            break;
                    }
                }
                finally
                {
                    inSelectedValueBindingChanged = false;
                }
            }
        }

        /// <summary>
        /// Обрабатывает событие изменения списка объектов BindingSource'а связей.
        /// </summary>
        /// <param name="sender">Инициатор события.</param>
        /// <param name="e">Параметры события.</param>
        private void targetSourceItemsListChanged(object sender, ListChangedEventArgs e)
        {
            if (!isManipulating && !inInitializingMode && ListChangedType.ItemMoved != e.ListChangedType)
            {
                LoadSelectedItemsFromTargetSource();
            }
        }

        /// <summary>
        /// Проверяет на возможность обновления ссылок в targetSource из выделенных объектов ListBox'а.
        /// </summary>
        private void CheckForUpdateLinksCapability()
        {
            if (!inInitializingMode && null != targetSource && null != targetMemberName && targetMemberName.Length > 0 && null != DataSource && null != ValueMember && ValueMember.Length > 0)
            {
                //  Если раньше нельзя было обновлять, значит не было ничего выделено (или выделено неправильно) по умолчанию и надо выделить (или выделить правильно)
                if (!canUpdateLinks)
                {
                    LoadSelectedItemsFromTargetSource();
                }
                canUpdateLinks = true;
            }
            else
            {
                canUpdateLinks = false;
            }
        }

        /// <summary>
        /// Выделяет строки в ListBox на основе данных targetSource
        /// </summary>
        private void LoadSelectedItemsFromTargetSource()
        {
            if (!isManipulating)
            {
                isManipulating = true;
                try
                {

                    BeginUpdate();
                    //  Очищаем выделенные базовым конструктором (или ещё кем-нибудь) итемы
                    ClearSelected();
                    //  Получаем список выбранных значений
                    List<long> defaultSelectedValues = new List<long>();
                    foreach (DataRowView defaultSelectedRowView in targetSource)
                    {
                        long defaultSelectedValue = (long)defaultSelectedRowView.Row[targetMemberName];
                        defaultSelectedValues.Add(defaultSelectedValue);
                    }
                    //  Создаём список объектов, которые надо отметить как выделенные
                    List<DataRowView> rowsForSelect = new List<DataRowView>();
                    foreach (DataRowView listRowView in Items)
                    {
                        long listRowValue = (long)listRowView.Row[ValueMember];
                        if (defaultSelectedValues.Contains(listRowValue))
                        {
                            rowsForSelect.Add(listRowView);
                        }
                    }
                    //  Добавляем в список выделенных объектов ListBox'a выбранные ранее объекты
                    foreach (DataRowView rowForSelect in rowsForSelect)
                    {
                        SelectedItems.Add(rowForSelect);
                    }
                    EndUpdate();
                }
                finally
                {
                    isManipulating = false;
                }
            }
        }
        #endregion Обработка событий изменения свойств ListBox

        #region Обработка событий изменения выделения в ListBox

        /// <summary>
        /// Обновляет связи в соответствии с выбранными в ListBox'е значениями.
        /// </summary>
        private void UpdateLinks()
        {
            if (!isManipulating)
            {
                isManipulating = true;
                try
                {

                    Form form = FindForm();
                    //  Если нет формы, где показывается контрол, значит никто не мог там ничего поредактировать
                    if (null != form)
                    {
                        if (canUpdateLinks)
                        {
                            //  Список выбранных идентификаторов
                            List<long> currentSelection = new List<long>();
                            //  Список идентификаторов на удаление
                            List<DataRow> prevoiusDataRowsForDelete = new List<DataRow>();
                            //  Инициализируется список выбранных идентификаторов
                            foreach (DataRowView currentSelectedRowView in SelectedItems)
                            {
                                long currentSelectedId = (long)currentSelectedRowView.Row[ValueMember];
                                currentSelection.Add(currentSelectedId);
                            }
                            //  Определяется, какие выбранные сейчас связи уже были выбраны ранее (и не обрабатываются далее) и какие были выбраны ранее, но отменены сейчас (для удаления)
                            foreach (DataRowView previousSelectedRowView in targetSource)
                            {
                                long previousSelectedId = (long)previousSelectedRowView.Row[targetMemberName];
                                //  Если идентификатор из предыдущего выделения есть в текущем, удаляем из списка текущих выделенных идентификаторов, иначе добавляем в список на удаление.
                                //  При этом после цикла currentSelection будет содержать только новые выбранные значения.
                                if (currentSelection.Contains(previousSelectedId))
                                {
                                    currentSelection.Remove(previousSelectedId);
                                }
                                else
                                {
                                    prevoiusDataRowsForDelete.Add(previousSelectedRowView.Row);
                                }
                            }
                            //BeginUpdate();
                            //  Удаляются ненужные уже связи
                            foreach (DataRow previousSelectedRow in prevoiusDataRowsForDelete)
                            {
                                previousSelectedRow.Delete();
                            }
                            //  Создаются новые связи
                            foreach (long newSelectedId in currentSelection)
                            {
                                DataRowView newLinkRowView = (DataRowView)targetSource.AddNew();
                                newLinkRowView.Row[targetMemberName] = newSelectedId;
                                newLinkRowView.EndEdit();
                            }
                            //EndUpdate();
                        }
                    }
                }
                finally
                {
                    isManipulating = false;
                }
            }
        }

        /// <summary>
        /// Обработчик события изменения выбора в ListBox'е.
        /// </summary>
        /// <param name="e">Параметры события.</param>
        protected override void OnSelectedValueChanged(EventArgs e)
        {
            if (!inInitializingMode)
            {
                UpdateLinks();
            }
            base.OnSelectedValueChanged(e);
        }

        #endregion  Обработка событий изменения выделения в ListBox

        #region ISupportInitialize Members

        /// <summary>
        /// Переводит контрол в режим инициализации свойств.
        /// </summary>
        /// <remarks>
        /// Ускоряет инициализацию свойств, не обрабатывая и не генерируя события.
        /// </remarks>
        public void BeginInit()
        {
            inInitializingMode = true;
            canUpdateLinks = false;
        }

        /// <summary>
        /// Возвращает контрол из режима инициализации в нормальный режим.
        /// </summary>
        public void EndInit()
        {
            inInitializingMode = false;
            CheckForUpdateLinksCapability();
        }

        #endregion
    }
}
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.