Предотвращение многократного повторного выбора синхронизированных элементов управления? - PullRequest
3 голосов
/ 18 марта 2010

Пример рабочего кода здесь синхронизирует (одиночный) выбор в TreeView, ListView и ComboBox посредством использования лямбда-выражений в словаре, где ключом в словаре является элемент управления, а значением каждого ключа является * 1001. *>.

Где я застрял - это то, что я получаю несколько повторений выполнения кода, который устанавливает выбор в различных элементах управления неожиданным образом: это не повторяется: ошибки StackOverFlow не происходит; но я хотел бы выяснить, почему текущая стратегия предотвращения многократного выбора одних и тех же элементов управления не работает.

Возможно, настоящая проблема здесь заключается в различении обновления выбора , запускаемого конечным пользователем , и обновления выбора , запускаемого кодом, синхронизирующим другие элементы управления ?

Примечание: я экспериментировал с использованием делегатов и форм делегатов, таких как Action<T>, для вставки исполняемого кода в словари: я "учусь лучше всего", ставя перед собой задачи "программирования" и реализуя их Кроме того, одновременно изучая «золотые слова» таких светил, как Скит, Макдональд, Либерти, Троелсен, Селлс, Рихтер.

Примечание: к этому вопросу / коду добавлено «глубокое предыстория», это заявление о том, как я привык делать вещи до C # 3.0 дней, когда казалось, что мне нужно было использовать явные меры для предотвращения рекурсия при синхронизации выделения.

Код: Предположим, что стандартное дерево WinForms TreeView, ListView, ComboBox имеют одинаковый набор записей (т. Е. TreeView имеет только корневые узлы; ListView, в View Details, имеет один столбец).

private Dictionary<Control, Action<int>> ControlToAction = new Dictionary<Control, Action<int>>();

private void Form1_Load(object sender, EventArgs e)
{
    // add the Controls to be synchronized to the Dictionary
    // with appropriate Action<int> lambda expressions
    ControlToAction.Add(treeView1, (i => { treeView1.SelectedNode = treeView1.Nodes[i]; }));
    ControlToAction.Add(listView1, (i => { listView1.Items[i].Selected = true; }));
    ControlToAction.Add(comboBox1, (i => { comboBox1.SelectedIndex = i; }));

    // optionally install event handlers at run-time like so :

    // treeView1.AfterSelect += (object obj, TreeViewEventArgs evt) 
       // => { synchronizeSelection(evt.Node.Index, treeView1); };

    // listView1.SelectedIndexChanged += (object obj, EventArgs evt) 
       // => { if (listView1.SelectedIndices.Count > 0)
               // { synchronizeSelection(listView1.SelectedIndices[0], listView1);} };

    // comboBox1.SelectedValueChanged += (object obj, EventArgs evt)
       // => { synchronizeSelection(comboBox1.SelectedIndex, comboBox1); };
}

private void synchronizeSelection(int i, Control currentControl)
{
    foreach(Control theControl in ControlToAction.Keys)
    {
        // skip the 'current control'
        if (theControl == currentControl) continue;

        // for debugging only
        Console.WriteLine(theControl.Name + " synchronized");

        // execute the Action<int> associated with the Control
        ControlToAction[theControl](i);
    }
}

private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
    synchronizeSelection(e.Node.Index, treeView1);
}

private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
    // weed out ListView SelectedIndexChanged firing
    // with SelectedIndices having a Count of #0
    if (listView1.SelectedIndices.Count > 0)
    {
        synchronizeSelection(listView1.SelectedIndices[0], listView1);
    }
}

private void comboBox1_SelectedValueChanged(object sender, EventArgs e)
{
    if (comboBox1.SelectedIndex > -1)
    {
        synchronizeSelection(comboBox1.SelectedIndex, comboBox1);
    }
}   

фон: до C # 3.0

Похоже, еще в дни до C # 3.0 я всегда использовал логический флаг для предотвращения рекурсии при обновлении нескольких элементов управления. Например, у меня обычно был бы такой код для синхронизации TreeView и ListView: предполагая, что каждый элемент в ListView был синхронизирован с узлом корневого уровня TreeView через общий индекс:

    // assume ListView is in 'Details View,' has a single column, 
        //  MultiSelect = false 
        //  FullRowSelect = true
        //  HideSelection = false;

    // assume TreeView 
        //  HideSelection = false 
        //  FullRowSelect = true

    // form scoped variable
    private bool dontRecurse = false;

    private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
    {
        if(dontRecurse) return;

        dontRecurse = true;
            listView1.Items[e.Node.Index].Selected = true;
        dontRecurse = false;
    }

    private void listView1_SelectedIndexChanged(object sender, EventArgs e)
    {
        if(dontRecurse) return

        // weed out ListView SelectedIndexChanged firing
        // with SelectedIndices having a Count of #0
        if (listView1.SelectedIndices.Count > 0)
        {
            dontRecurse = true;
                treeView1.SelectedNode = treeView1.Nodes[listView1.SelectedIndices[0]];
            dontRecurse = false;
        }
    }

Тогда, кажется, где-то около FrameWork 3 ~ 3.5 я мог бы избавиться от кода для подавления рекурсии, и рекурсии не было (по крайней мере, при синхронизации TreeView и ListView). К тому времени это стало «привычкой» использовать логический флаг для предотвращения рекурсии, и это, возможно, было связано с использованием определенного стороннего элемента управления.

Ответы [ 2 ]

1 голос
/ 18 марта 2010

Я считаю, что ваш подход полностью в порядке. Если вы хотите что-то более продвинутое, посмотрите Rein в побеге событий с «Latch» , который учитывает

void TabControl_TabSelected(object sender, TabEventArgs args)
{
    _latch.RunLatchedOperation(
        delegate
        {
            ContentTab tab = (ContentTab)TabControl.SelectedTab;
            activatePresenter(tab.Presenter, tab);                       
        });
}
0 голосов
/ 20 марта 2010

Примечание: я всегда предполагал, что пользователь SO никогда не должен отвечать на свой вопрос. Но после прочтения SO-Meta по этому вопросу я обнаружил, что это действительно поощряется. Лично я бы никогда не проголосовал за свой собственный ответ как "принятый".

Это «новое решение» использует стратегию, основанную на различении элемента управления, обновляемого в результате действия конечного пользователя, и элемента управления, обновляемого посредством синхронизации кода: эта проблема упоминалась как своего рода «риторический вопрос», "в исходном вопросе.

Я считаю это улучшением: оно работает; предотвращает множественные вызовы обновления; но я также «подозреваю», что это все еще «не оптимально»: к этому примеру кода добавлен список «подозрений».

// VS Studio 2010 RC 1, tested under Framework 4.0, 3.5

using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace SynchronizationTest_3
{
    public partial class Form1 : Form
    {
        private readonly Dictionary<Control, Action<int>> ControlToAction = new Dictionary<Control, Action<int>>();

        // new code : keep a reference to the control the end-user clicked
        private Control ClickedControl;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            ControlToAction.Add(treeView1, (i => { treeView1.SelectedNode = treeView1.Nodes[i]; }));
            ControlToAction.Add(listView1, (i => { listView1.Items[i].Selected = true; }));
            ControlToAction.Add(comboBox1, (i => { comboBox1.SelectedIndex = i; }));

            // new code : screen out redundant calls generated by other controls 
            // being updated

            treeView1.AfterSelect += (obj, evt)
            =>
            {
             if (treeView1 == ClickedControl) SynchronizeSelection(evt.Node.Index);
            };

            listView1.SelectedIndexChanged += (obj, evt)
            =>
            {
              if (listView1.SelectedIndices.Count > 0 && listView1 == ClickedControl)
              {
                  SynchronizeSelection(listView1.SelectedIndices[0]);
              }
            };

            comboBox1.SelectedValueChanged += (obj, evt)
            =>
            {
              if (comboBox1 == ClickedControl) SynchronizeSelection(comboBox1.SelectedIndex);
            };

            // new code here : all three controls share a common MouseDownHandler
            treeView1.MouseDown += SynchronizationMouseDown;

            listView1.MouseDown += SynchronizationMouseDown;

            comboBox1.MouseDown += SynchronizationMouseDown;

            // trigger the first synchronization
            ClickedControl = treeView1;
            SynchronizeSelection(0);
        }

        // get a reference to the control the end-user moused down on
        private void SynchronizationMouseDown(object sender, MouseEventArgs e)
        {
            ClickedControl = sender as Control;
        }

        // revised code using state of ClickedControl as a filter

        private void SynchronizeSelection(int i)
        {
            // we're done if the reference to the clicked control is null
            if (ClickedControl == null) return;

            foreach (Control theControl in ControlToAction.Keys)
            {
                if (theControl == ClickedControl) continue;

                // for debugging only
                Console.WriteLine(theControl.Name + " synchronized");

                ControlToAction[theControl](i);
            }

            // set the clicked control to null
            ClickedControl = null;
        }
    }
}

Почему я "подозреваю", что это не оптимально:

  1. необходимо учитывать своеобразное поведение элементов управления WinForms: например, элемент управления ListView запускает свои выбранные события ### до того, как он запускает событие Click: ComboBox и TreeView запускают свои события нажатия до их SelectedValueChanged и События AfterSelect соответственно: поэтому пришлось поэкспериментировать, чтобы обнаружить, что использование MouseDown будет работать одинаково для всех трех элементов управления.

  2. ощущение "кишечного уровня", что я зашел "слишком далеко" на "какую-то конечность" здесь: ощущение, что гораздо более простое решение может быть возможным.

...