多个 Windows 表单的事件处理程序内部循环

EventHandler inside loop for several Windows Forms

我的代码正在创建具有不同控件和事件的表单的多个实例。这是在将 i 递增到表单总数的同时完成的。目的是向窗体中的按钮控件添加事件。我的表单是 Ticker 的实例,是我创建的 Form 的子 class。

我遇到的问题是,当我在 运行 时单击按钮时,事件处理程序启动并且 i 超出范围,因为循环已经完成。

下面是一段代码,事件处理程序被集成到一个递增的循环中 i,其中还包含开关。 thisObject 有什么方法可以等于当前的活动代码,同时仍将事件处理程序保持在循环中? 编辑:为了更清楚起见,我提供了完整的 class。相关部分遵循 action_set>>电子邮件>>触发>>点击案例。

   public partial class DesktopTickers : Form
    {
        public DesktopTickers(List<string> tickerArgs)
        {
            InitializeComponent();
            this.Opacity = 0;
            this.ShowInTaskbar = false;
            int totalTickers = tickerArgs.Count();

            Rectangle screenBounds = GetDpiSafeResolution();
            Size iconSizeDefault = this.Size;                                                         
            string iconDirectory = @"\server\Tickers\";
            string iconSuffix = "_icon.png";
            string configDirectory = @"\server\Tickers\";
            string configSuffix = ".txt";
            char paramDelimiter = ':';
            char controlDelimiter = '=';
            char[] controlSeparatorL = {'('};
            char controlSeparatorR = ')';
            char actionDelimiter = '=';
            char[] actionSeparatorL = {'('};
            char actionSeparatorR = ')';
            char propertyDelimiter = '-';
            int maxWidthDefault = iconSizeDefault.Width;                                                                             
            int maxHeightDefault = iconSizeDefault.Height;                                                                             
            Ticker[] tickers = new Ticker[tickerArgs.Count()];
            List<Control> controls = new List<Control>();

            for (int i = 0; i < tickerArgs.Count(); i++)                                                       
            {
                string tickerArg = tickerArgs[i];                                                               
                string tickerConfigPath = configDirectory + tickerArg + configSuffix;                           
                string tickerResourcePath = iconDirectory + @"\" + tickerArg + @"\";
                string tickerIconPath = tickerResourcePath + tickerArg + iconSuffix;
                tickers[i] = new Ticker(screenBounds, tickerArg, i+1, tickerArgs.Count(), iconSizeDefault, 
                                        maxHeightDefault, maxWidthDefault, tickerIconPath);
                string[] tickerConfigContents = File.ReadAllLines(tickerConfigPath);
                for (int j = 0; j < tickerConfigContents.Length; j++)               
                {
                    string thisConfigLine = tickerConfigContents[j];                                                    
                    int configParamEnd = thisConfigLine.IndexOf(paramDelimiter);                                        
                    if (configParamEnd < 0) { configParamEnd = 0; }
                    string tickerConfigParam = thisConfigLine.Substring(0, configParamEnd);                           
                    string tickerConfigValue = thisConfigLine.Substring(configParamEnd + 1);
                    switch (tickerConfigParam.ToLower())                                    
                    {   //TICKER LEVEL PARAMETERS
                        case "icon_width":
                            tickers[i].iconWidth = Convert.ToInt32(tickerConfigValue);
                            break;
                        case "icon_height":
                            tickers[i].iconHeight = Convert.ToInt32(tickerConfigValue);
                            break;
                        case "max_width":                                                    
                            tickers[i].maxWidth = Convert.ToInt32(tickerConfigValue);        
                            break;
                        case "max_height":                                                   
                            tickers[i].maxHeight = Convert.ToInt32(tickerConfigValue);  
                            break;
                        case "control_set":   
                            for (int k = j + 1; k < tickerConfigContents.Length; k++)        
                            {   //CONTROL LEVEL PARAMETERS
                                string thisControlLine = tickerConfigContents[k]; 
                                if(thisControlLine == "end") { break; }
                                int controlParamEnd = thisControlLine.IndexOf(controlDelimiter); 
                                string thisControlType = thisControlLine.Substring(0, controlParamEnd);
                                string thisControlDetails = thisControlLine.Substring(controlParamEnd+ 1);
                                thisControlDetails = thisControlDetails.Replace(controlSeparatorR.ToString(), "");
                                string[] controlProperties = thisControlDetails.Split(controlSeparatorL, StringSplitOptions.RemoveEmptyEntries); 
                                switch (thisControlType.ToLower()) 
                                {   //CONTROL TYPE LEVEL PARAMETERS
                                    case "image":                  
                                        PictureBox thisImage = new PictureBox(); 
                                        for (int l = 0; l < controlProperties.Length; l++)
                                        {
                                            string thisProperty = controlProperties[l];
                                            int propertyParamEnd = thisProperty.IndexOf(propertyDelimiter);
                                            string propertyType = thisProperty.Substring(0, propertyParamEnd - 1);
                                            string propertyValue = thisProperty.Substring(propertyParamEnd + 2);
                                            switch (propertyType.ToLower())
                                            {   //PROPERTY LEVEL PARAMETERS
                                                case "file":
                                                    try { thisImage.BackgroundImage = Image.FromFile(tickerResourcePath + propertyValue); }
                                                    catch { thisImage.BackgroundImage = thisImage.ErrorImage; }
                                                    break;
                                                case "bounds":
                                                    char[] pointDelimiter = { ',' };
                                                    string[] boundsValues = propertyValue.Split(pointDelimiter);
                                                    Rectangle bounds = new Rectangle(
                                                                            new Point(Convert.ToInt32(boundsValues[0]), Convert.ToInt32(boundsValues[1])),
                                                                            new Size(Convert.ToInt32(boundsValues[2]), Convert.ToInt32(boundsValues[3])));
                                                    thisImage.Bounds = bounds;
                                                    break;
                                                case "layout":
                                                    switch(propertyValue.ToLower())
                                                    {
                                                        case "stretch":
                                                            thisImage.BackgroundImageLayout = ImageLayout.Stretch;
                                                            break;
                                                        case "zoom":
                                                            thisImage.BackgroundImageLayout = ImageLayout.Zoom;
                                                            break;
                                                        case "tile":
                                                            thisImage.BackgroundImageLayout = ImageLayout.Tile;
                                                            break;
                                                        case "center":
                                                            thisImage.BackgroundImageLayout = ImageLayout.Center;
                                                            break;
                                                        default:
                                                            thisImage.BackgroundImageLayout = ImageLayout.None;
                                                            break;
                                                    }
                                                    break;
                                                case "id":
                                                    thisImage.Name = propertyValue;
                                                    break;
                                            }
                                            tickers[i].Controls.Add(thisImage);
                                            thisImage.Show();
                                        }
                                        break;
                                    case "label":
                                        Label thisLabel = new Label();
                                        for (int l = 0; l < controlProperties.Length; l++)
                                        {
                                            string thisProperty = controlProperties[l];
                                            int propertyParamEnd = thisProperty.IndexOf(propertyDelimiter);
                                            string propertyType = thisProperty.Substring(0, propertyParamEnd - 1);
                                            string propertyValue = thisProperty.Substring(propertyParamEnd + 2);
                                            thisLabel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
                                            switch (propertyType.ToLower())
                                            {   //PROPERTY LEVEL PARAMETERS
                                                case "text":
                                                    thisLabel.Text = propertyValue;
                                                    break;
                                                case "font":
                                                    char fontDelimiter = ',';
                                                    int fontSplitIndex = propertyValue.IndexOf(fontDelimiter);
                                                    string fontName = propertyValue.Substring(0, fontSplitIndex);
                                                    string fontSize = propertyValue.Substring(fontSplitIndex + 1);
                                                    int fontSizeNum = int.Parse(fontSize);
                                                    thisLabel.Font = new Font(propertyValue, fontSizeNum);
                                                    break;
                                                case "bounds":
                                                    char[] pointDelimiter = {','};
                                                    string[] boundsValues = propertyValue.Split(pointDelimiter);
                                                    Rectangle bounds = new Rectangle(
                                                                            new Point(Convert.ToInt32(boundsValues[0]), Convert.ToInt32(boundsValues[1])),
                                                                            new Size(Convert.ToInt32(boundsValues[2]), Convert.ToInt32(boundsValues[3])));
                                                    thisLabel.Bounds = bounds;
                                                    break;
                                                case "id":
                                                    thisLabel.Name = propertyValue;
                                                    break;
                                            }
                                            thisLabel.Show();
                                            tickers[i].Controls.Add(thisLabel);
                                        }
                                        break;
                                    case "button":
                                        Button thisButton = new Button();
                                        for (int l = 0; l < controlProperties.Length; l++)
                                        {
                                            string thisProperty = controlProperties[l];
                                            int propertyParamEnd = thisProperty.IndexOf(propertyDelimiter);
                                            string propertyType = thisProperty.Substring(0, propertyParamEnd - 1);
                                            string propertyValue = thisProperty.Substring(propertyParamEnd + 2);
                                            switch (propertyType.ToLower())
                                            {
                                                case "text":
                                                    thisButton.Text = propertyValue;
                                                    break;
                                                case "font":
                                                    char fontDelimiter = ',';
                                                    int fontSplitIndex = propertyValue.IndexOf(fontDelimiter);
                                                    string fontName = propertyValue.Substring(0, fontSplitIndex);
                                                    string fontSize = propertyValue.Substring(fontSplitIndex + 1);
                                                    int fontSizeNum = int.Parse(fontSize);
                                                    thisButton.Font = new Font(propertyValue, fontSizeNum);
                                                    break;
                                                case "bounds":
                                                    char[] pointDelimiter = { ',' };
                                                    string[] boundsValues = propertyValue.Split(pointDelimiter);
                                                    Rectangle bounds = new Rectangle(
                                                                            new Point(Convert.ToInt32(boundsValues[0]), Convert.ToInt32(boundsValues[1])),
                                                                            new Size(Convert.ToInt32(boundsValues[2]), Convert.ToInt32(boundsValues[3])));
                                                    thisButton.Bounds = bounds;
                                                    break;
                                                case "id":
                                                    thisButton.Name = propertyValue;
                                                    break;
                                            }
                                            thisButton.Show();
                                            tickers[i].Controls.Add(thisButton);
                                        }
                                        break;
                                    case "textbox":
                                        TextBox thisTextBox = new TextBox();
                                        for (int l = 0; l < controlProperties.Length; l++)
                                        {
                                            string thisProperty = controlProperties[l];
                                            int propertyParamEnd = thisProperty.IndexOf(propertyDelimiter);
                                            string propertyType = thisProperty.Substring(0, propertyParamEnd - 1);
                                            string propertyValue = thisProperty.Substring(propertyParamEnd + 2);
                                            thisTextBox.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
                                            switch (propertyType.ToLower())
                                            {   //PROPERTY LEVEL PARAMETERS
                                                case "text":
                                                    thisTextBox.Text = propertyValue;
                                                    break;
                                                case "font":
                                                    char fontDelimiter = ',';
                                                    int fontSplitIndex = propertyValue.IndexOf(fontDelimiter);
                                                    string fontName = propertyValue.Substring(0, fontSplitIndex);
                                                    string fontSize = propertyValue.Substring(fontSplitIndex + 1);
                                                    int fontSizeNum = int.Parse(fontSize);
                                                    thisTextBox.Font = new Font(propertyValue, fontSizeNum);
                                                    break;
                                                case "bounds":
                                                    char[] pointDelimiter = { ',' };
                                                    string[] boundsValues = propertyValue.Split(pointDelimiter);
                                                    Rectangle bounds = new Rectangle(
                                                                            new Point(Convert.ToInt32(boundsValues[0]), Convert.ToInt32(boundsValues[1])),
                                                                            new Size(Convert.ToInt32(boundsValues[2]), Convert.ToInt32(boundsValues[3])));
                                                    thisTextBox.Bounds = bounds;
                                                    break;
                                                case "id":
                                                    thisTextBox.Name = propertyValue;
                                                    break;
                                            }
                                            thisTextBox.Show();
                                            tickers[i].Controls.Add(thisTextBox);
                                        }
                                        break;
                                }
                            }
                            break;
                        case "action_set":
                            for (int k = j + 1; k < tickerConfigContents.Length; k++)                                        
                            {   //ACTION LEVEL PARAMETERS
                                string thisActionLine = tickerConfigContents[k];                                             
                                if (thisActionLine == "end") { break; }
                                int actionParamEnd = thisActionLine.IndexOf(actionDelimiter);       
                                string thisActionType = thisActionLine.Substring(0, actionParamEnd);
                                string thisActionDetails = thisActionLine.Substring(actionParamEnd + 1);
                                thisActionDetails = thisActionDetails.Replace(actionSeparatorR.ToString(), ""); 
                                string[] actionProperties = thisActionDetails.Split(actionSeparatorL, StringSplitOptions.RemoveEmptyEntries);
                                Control thisObject = new Control();
                                switch (thisActionType.ToLower())                                                          
                                {   //ACTION TYPE LEVEL PARAMETERS
                                    case "email":
                                        //email requires trigger, objectid, action to send email, email action params
                                        for (int l = 0; l < actionProperties.Length; l++)
                                        {
                                            string thisProperty = actionProperties[l];
                                            int propertyParamEnd = thisProperty.IndexOf(propertyDelimiter);
                                            string propertyType = thisProperty.Substring(0, propertyParamEnd - 1);
                                            string propertyValue = thisProperty.Substring(propertyParamEnd + 2);
                                            string emailDomain = "";
                                            string emailServer = "";
                                            int emailPort = 0;
                                            string emailTemplate = "";
                                            string emailRecipient = "";
                                            switch (propertyType.ToLower())
                                            {
                                                case "domain":
                                                    emailDomain = propertyValue;
                                                    break;
                                                case "server":
                                                    emailServer = propertyValue;
                                                    break;
                                                case "port":
                                                    emailPort = Convert.ToInt32(propertyValue);
                                                    break;
                                                case "file":
                                                    emailTemplate = tickerResourcePath + propertyValue;
                                                    break;
                                                case "recipient":
                                                    emailRecipient = propertyValue;
                                                    break;
                                                case "object":
                                                    thisObject = tickers[i].Controls.Find(propertyValue, false).FirstOrDefault() as Control;
                                                    //thisObject = objects[0];
                                                    break;
                                                case "trigger":
                                                    tickers[i].SetEmailProperties(emailDomain, emailServer, emailPort, emailTemplate, emailRecipient);
                                                    switch(propertyValue.ToLower())
                                                    {
                                                        case "click":
                                                            thisObject.MouseDown += new MouseEventHandler((sender, e)
                                                                => tickers[i].SendEmail_Event(sender, e));
                                                            break;
                                                    }
                                                    break;
                                            }
                                        }
                                        break;
                                }
                            }
                            break;
                    }
                }
                tickers[i].Show();
            }
        }


        private Rectangle GetDpiSafeResolution()
        {
            using (Graphics graphics = this.CreateGraphics())
            {
                return new Rectangle(new Point(0, 0), new Size((Screen.PrimaryScreen.Bounds.Width * (int)graphics.DpiX) / 96
                , (Screen.PrimaryScreen.Bounds.Height * (int)graphics.DpiY) / 96));
            }
        }

    }

这似乎是一个捕获问题。您需要在创建表单时捕获 i 的值,并在创建事件处理程序时保留此值。这可以简单地通过在创建表单时创建一个新变量 iValuetickerInd 并在事件处理程序代码中使用此变量而不是 i 来完成。

我写这个答案有点投机。请提供更大的代码片段,以便我们查看表单和处理程序的创建代码。但我相信您当前的代码如下。我标记为"code based on i"的部分是您当前提供的代码片段。

for (var i = 0; i < formCount; ++i)
{
    var form = new Ticker();
    form.Button.OnClicked += () =>
        {
            //code based on i
            doSomething(i);
        };
}

需要如下:

for (var i = 0; i < formCount; ++i)
{
    var formInd = i;
    var form = new Ticker();
    form.Button.OnClicked += () =>
        {
            //code based on i
            doSomething(formInd);
        };
}

编辑:您需要更改以下代码:

case "click":
    //replace i with tickerInd, this will capture the current value of i
    var tickerInd = i;
    thisObject.MouseDown += new MouseEventHandler((sender, e)
      => tickers[tickerInd].SendEmail_Event(sender, e));
break;

注意:您的代码似乎需要重构。首先,你并不是真的需要Ticker[] tickers,你可以简单地执行以下操作:

public DesktopTickers(List<string> tickerArgs)
{
    for (var i = 0; i < tickerArgs.Count; ++i)
    {
        var tickerArg = tickerArgs[i];
        var ticker = new Ticker(...);
        //your whole initialization code goes here
        //replace all "tickers[i]" with "ticker"
        ticker.Show();
    }
}

之后,您可以通过将此初始化移动到 Ticker 构造函数中,然后将其进一步划分为初始化方法来进一步重构。

我不认为我完全理解这个问题,但看起来你在范围界定方面遇到的问题不仅仅是循环。

在 C# 4 中,编译器中存在一个错误,该错误限定了 foreachfor 变量的范围,因此当它们关闭时,它始终是最后分配的值。这已在 C# 5 中修复。

如果 (sender, e) => tickers[i].SendEmail_Event(sender, e) 中的 ticker[i] 总是超出范围,可能是因为您的迭代器变量声明在循环范围之外。

int i = 0;
var funcs = new Func<int>[100];
do 
{
    funcs[i] = () => i;
}while(++i < 100);

Assert.That(funcs[0](), Is.EqualTo(0));

这个测试会失败,因为它们都关闭了 i,当循环结束时它实际上是 100。要解决此问题,您需要一个位于内部范围内的变量(因此在这种情况下对每次迭代都是唯一的):

int i = 0;
var funcs = new Func<int>[100];
do 
{
  int closedInt = i;
  funcs[i] = () => closedInt;
}while(++i < 100);

Assert.That(funcs[0](), Is.EqualTo(0));