Здравствуйте, 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
}
}