防止多次重复选择同步控件?

时间:2010-03-18 09:00:49

标签: c# winforms controls synchronization selection

这里的工作代码示例通过在字典中使用lambda表达式来同步TreeView,ListView和ComboBox中的(单个)选择,其中字典中的Key是Control,并且每个Key的值是{{ 1}}>

我被困的地方是我多次重复执行代码,以一种意外的方式设置各种控件中的选择:它没有递归:没有发生StackOverFlow错误;但是,我想弄清楚为什么当前防止多次选择相同控件的策略不起作用。

这里真正的问题可能是区分由最终用户触发的选择更新和由同步其他控件的代码触发的选择更新

注意:我一直在尝试使用Delegates和Action<int之类的代表形式,在Dictionaries中插入可执行代码:我通过对自己构建编程“挑战”来“学习最好”,以及实施它们,同时学习像Skeet,McDonald,Liberty,Troelsen,Sells,Richter这样的名人的“金色词汇”。

注意:对于这个问题/代码,对于“深层背景”,是一个陈述我以前在C#3.0天做过的事情似乎我确实需要使用明确的措施来防止同步选择时的递归。

代码:假设一个WinForms标准的TreeView,ListView,ComboBox,都具有相同的条目集(即,TreeView只有根节点; ListView,在Details视图中,有一列)。

Action<T>

背景:前C#3.0

似乎,在C#3.0之前的日子里,我总是使用布尔标志来防止更新多个控件时的递归。例如,我通常有这样的代码用于同步TreeView和ListView:假设ListView中的每个Item通过一个公共索引与TreeView的根级节点同步:

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);
    }
}   

然后看起来,在FrameWork 3~3.5附近,我可以摆脱代码来抑制递归,并且没有递归(至少在同步TreeView和ListView时没有)。到那时,使用布尔标志来防止递归已成为“习惯”,这可能与使用某个第三方控件有关。

2 个答案:

答案 0 :(得分:1)

我相信你的方法完全没问题。如果您想要更高级的内容,请参阅Rein in runaway events with the "Latch",其中包含

void TabControl_TabSelected(object sender, TabEventArgs args)
{
    _latch.RunLatchedOperation(
        delegate
        {
            ContentTab tab = (ContentTab)TabControl.SelectedTab;
            activatePresenter(tab.Presenter, tab);                       
        });
}

答案 1 :(得分:0)

注意:我总是认为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之前触发其Click事件AfterSelect Events分别为:所以不得不试验一下,发现使用'MouseDown会在所有三个控件中都能正常工作。

  2. 一种“肠道水平”的感觉,我在这里“走得太远”了“某种肢体”:感觉可能是一种更简单的解决方案。