【问题标题】:Is there a better way to avoid an infinite loop using winforms?有没有更好的方法来避免使用 winforms 的无限循环?
【发布时间】:2025-12-29 20:40:12
【问题描述】:

我现在使用的是 .Net 3.5。

现在我正在使用using 技巧来禁用和启用围绕某些代码部分的事件。用户可以更改天数、小时数、分钟数或总分钟数,这不应导致无限级联的事件(例如,分钟数更改总数、总分钟数更改等)虽然代码可以满足我的要求,但可能会有更好的/ 更直接的方式。你知道吗?

对于强壮点:

这个控件将被多个团队使用 - 我不想让它尴尬。我怀疑在定义一天中的小时数、一周中的天数等时我不需要重新发明*。其他一些标准的 .Net 库必须有它。关于代码的任何其他说明?这个using (EventHacker.DisableEvents(this)) 业务 - 这一定是.Net 中的常见模式......临时更改设置。它叫什么名字?我希望能够在评论中引用它,并阅读更多关于当前实现的信息。在一般情况下,不仅需要记住正在更改的事物的句柄,还需要记住先前的状态(在这种情况下,先前的状态无关紧要 - 事件无条件地打开和关闭)。那么multi-threaded hacking也有可能。人们也可以利用泛型来使代码更干净。弄清楚所有这些可以导致多页博客文章。我很高兴听到一些答案。

附:我好像患有强迫症?有些人喜欢完成任务并继续前进;我喜欢让它们保持开放......总有更好的方法。

// Corresponding Designer class is omitted.
using System;
using System.Windows.Forms;

namespace XYZ // Real name masked
{
    interface IEventHackable
    {
        void EnableEvents();
        void DisableEvents();
    }

    public partial class PollingIntervalGroupBox : GroupBox, IEventHackable
    {
        private const int DAYS_IN_WEEK      = 7;
        private const int MINUTES_IN_HOUR   = 60;
        private const int HOURS_IN_DAY      = 24;
        private const int MINUTES_IN_DAY    = MINUTES_IN_HOUR * HOURS_IN_DAY;
        private const int MAX_TOTAL_DAYS    = 100;

        private static readonly decimal MIN_TOTAL_NUM_MINUTES = 1; // Anything faster than once per minute can bog down our servers.
        private static readonly decimal MAX_TOTAL_NUM_MINUTES = (MAX_TOTAL_DAYS * MINUTES_IN_DAY) - 1; // 99 days should be plenty.
        // The value above was chosen so to not cause an overflow exception.
        // Watch out for it - numericUpDownControls each have a MaximumValue setting.

        public PollingIntervalGroupBox()
        {
            InitializeComponent();

            InitializeComponentCustom();
        }

        private void InitializeComponentCustom()
        {
            this.m_upDownDays.Maximum           = MAX_TOTAL_DAYS    - 1;
            this.m_upDownHours.Maximum          = HOURS_IN_DAY      - 1;
            this.m_upDownMinutes.Maximum        = MINUTES_IN_HOUR   - 1;
            this.m_upDownTotalMinutes.Maximum   = MAX_TOTAL_NUM_MINUTES;
            this.m_upDownTotalMinutes.Minimum   = MIN_TOTAL_NUM_MINUTES;
        }

        private void m_upDownTotalMinutes_ValueChanged(object sender, EventArgs e)
        {
            setTotalMinutes(this.m_upDownTotalMinutes.Value);
        }

        private void m_upDownDays_ValueChanged(object sender, EventArgs e)
        {
            updateTotalMinutes();
        }

        private void m_upDownHours_ValueChanged(object sender, EventArgs e)
        {
            updateTotalMinutes();
        }

        private void m_upDownMinutes_ValueChanged(object sender, EventArgs e)
        {
            updateTotalMinutes();
        }

        private void updateTotalMinutes()
        {
            this.setTotalMinutes(
                MINUTES_IN_DAY * m_upDownDays.Value + 
                MINUTES_IN_HOUR * m_upDownHours.Value + 
                m_upDownMinutes.Value);
        }

        public decimal TotalMinutes
        {
            get
            {
                return m_upDownTotalMinutes.Value;
            }
            set
            {
                m_upDownTotalMinutes.Value = value;
            }
        }

        public decimal TotalHours
        {
            set
            {
                setTotalMinutes(value * MINUTES_IN_HOUR);
            }
        }

        public decimal TotalDays
        {
            set
            {
                setTotalMinutes(value * MINUTES_IN_DAY);
            }
        }

        public decimal TotalWeeks
        {
            set
            {
                setTotalMinutes(value * DAYS_IN_WEEK * MINUTES_IN_DAY);
            }
        }

        private void setTotalMinutes(decimal nTotalMinutes)
        {
            if (nTotalMinutes < MIN_TOTAL_NUM_MINUTES)
            {
                setTotalMinutes(MIN_TOTAL_NUM_MINUTES);
                return; // Must be carefull with recursion.
            }
            if (nTotalMinutes > MAX_TOTAL_NUM_MINUTES)
            {
                setTotalMinutes(MAX_TOTAL_NUM_MINUTES);
                return; // Must be carefull with recursion.
            }
            using (EventHacker.DisableEvents(this))
            {
                // First set the total minutes
                this.m_upDownTotalMinutes.Value = nTotalMinutes;

                // Then set the rest
                this.m_upDownDays.Value = (int)(nTotalMinutes / MINUTES_IN_DAY);
                nTotalMinutes = nTotalMinutes % MINUTES_IN_DAY; // variable reuse.
                this.m_upDownHours.Value = (int)(nTotalMinutes / MINUTES_IN_HOUR);
                nTotalMinutes = nTotalMinutes % MINUTES_IN_HOUR;
                this.m_upDownMinutes.Value = nTotalMinutes;
            }
        }

        // Event magic
        public void EnableEvents()
        {
            this.m_upDownTotalMinutes.ValueChanged += this.m_upDownTotalMinutes_ValueChanged;
            this.m_upDownDays.ValueChanged += this.m_upDownDays_ValueChanged;
            this.m_upDownHours.ValueChanged += this.m_upDownHours_ValueChanged;
            this.m_upDownMinutes.ValueChanged += this.m_upDownMinutes_ValueChanged;
        }

        public void DisableEvents()
        {
            this.m_upDownTotalMinutes.ValueChanged -= this.m_upDownTotalMinutes_ValueChanged;
            this.m_upDownDays.ValueChanged -= this.m_upDownDays_ValueChanged;
            this.m_upDownHours.ValueChanged -= this.m_upDownHours_ValueChanged;
            this.m_upDownMinutes.ValueChanged -= this.m_upDownMinutes_ValueChanged;
        }

        // We give as little info as possible to the 'hacker'.
        private sealed class EventHacker : IDisposable
        {
            IEventHackable _hackableHandle;

            public static IDisposable DisableEvents(IEventHackable hackableHandle)
            {
                return new EventHacker(hackableHandle);
            }

            public EventHacker(IEventHackable hackableHandle)
            {
                this._hackableHandle = hackableHandle;
                this._hackableHandle.DisableEvents();
            }

            public void Dispose()
            {
                this._hackableHandle.EnableEvents();
            }
        }
    }
}

【问题讨论】:

  • 我个人不喜欢这种(ab)使用using。重新设计语言结构的方式既可爱又聪明,但这些并不是优秀代码的特征。您以牺牲一般可理解性为代价获得了更简洁的代码(考虑新手程序员正在查看您的代码)。我会简单地使用 try..finally 块,使代码更具自我记录性。
  • 嗯,你可能有一点。我已经看到其他人使用这种(ab)用法,但与其他人不同。其他人喜欢它,因为它可以节省打字。我个人认为,像新手 C# 程序员相比,新手 C++ 程序员需要更长的时间来学习自动指针,而不是像您所说的那样学习这个“可爱”的技巧。我不确定是否应该优化代码以供初级、高级或经验丰富的开发人员进行维护。再说一次,我对你的看法有偏见——我很好奇其他人要说什么。

标签: c# .net winforms events using-statement


【解决方案1】:

我将使用布尔字段来阻止setTotalMinutes 方法多次执行,并将事件处理程序的创建移至InitializeComponentCustom 方法。

类似这样的:

public partial class PollingIntervalGroupBox : GroupBox
{
    private const int DAYS_IN_WEEK = 7;
    private const int MINUTES_IN_HOUR = 60;
    private const int HOURS_IN_DAY = 24;
    private const int MINUTES_IN_DAY = MINUTES_IN_HOUR * HOURS_IN_DAY;
    private const int MAX_TOTAL_DAYS = 100;

    private static readonly decimal MIN_TOTAL_NUM_MINUTES = 1; // Anything faster than once per minute can bog down our servers.
    private static readonly decimal MAX_TOTAL_NUM_MINUTES = (MAX_TOTAL_DAYS * MINUTES_IN_DAY) - 1; // 99 days should be plenty.
    // The value above was chosen so to not cause an overflow exception.
    // Watch out for it - numericUpDownControls each have a MaximumValue setting.
    private bool _totalMinutesChanging;

    public PollingIntervalGroupBox()
    {
        InitializeComponent();
        InitializeComponentCustom();
    }

    private void InitializeComponentCustom()
    {
        this.m_upDownDays.Maximum = MAX_TOTAL_DAYS - 1;
        this.m_upDownHours.Maximum = HOURS_IN_DAY - 1;
        this.m_upDownMinutes.Maximum = MINUTES_IN_HOUR - 1;
        this.m_upDownTotalMinutes.Maximum = MAX_TOTAL_NUM_MINUTES;
        this.m_upDownTotalMinutes.Minimum = MIN_TOTAL_NUM_MINUTES;

        this.m_upDownTotalMinutes.ValueChanged += this.m_upDownTotalMinutes_ValueChanged;
        this.m_upDownDays.ValueChanged += this.m_upDownDays_ValueChanged;
        this.m_upDownHours.ValueChanged += this.m_upDownHours_ValueChanged;
        this.m_upDownMinutes.ValueChanged += this.m_upDownMinutes_ValueChanged;
    }

    private void m_upDownTotalMinutes_ValueChanged(object sender, EventArgs e)
    {
        setTotalMinutes(this.m_upDownTotalMinutes.Value);
    }

    private void m_upDownDays_ValueChanged(object sender, EventArgs e)
    {
        updateTotalMinutes();
    }

    private void m_upDownHours_ValueChanged(object sender, EventArgs e)
    {
        updateTotalMinutes();
    }

    private void m_upDownMinutes_ValueChanged(object sender, EventArgs e)
    {
        updateTotalMinutes();
    }

    private void updateTotalMinutes()
    {
        this.setTotalMinutes(
            MINUTES_IN_DAY * m_upDownDays.Value +
            MINUTES_IN_HOUR * m_upDownHours.Value +
            m_upDownMinutes.Value);
    }

    public decimal TotalMinutes { get { return m_upDownTotalMinutes.Value; } set { m_upDownTotalMinutes.Value = value; } }

    public decimal TotalHours { set { setTotalMinutes(value * MINUTES_IN_HOUR); } }

    public decimal TotalDays { set { setTotalMinutes(value * MINUTES_IN_DAY); } }

    public decimal TotalWeeks { set { setTotalMinutes(value * DAYS_IN_WEEK * MINUTES_IN_DAY); } }

    private void setTotalMinutes(decimal totalMinutes)
    {
        if (_totalMinutesChanging) return;
        try
        {
            _totalMinutesChanging = true;
            decimal nTotalMinutes = totalMinutes;
            if (totalMinutes < MIN_TOTAL_NUM_MINUTES)
            {
                nTotalMinutes = MIN_TOTAL_NUM_MINUTES;
            }
            if (totalMinutes > MAX_TOTAL_NUM_MINUTES)
            {
                nTotalMinutes = MAX_TOTAL_NUM_MINUTES;
            }
            // First set the total minutes
            this.m_upDownTotalMinutes.Value = nTotalMinutes;

            // Then set the rest
            this.m_upDownDays.Value = (int)(nTotalMinutes / MINUTES_IN_DAY);
            nTotalMinutes = nTotalMinutes % MINUTES_IN_DAY; // variable reuse.
            this.m_upDownHours.Value = (int)(nTotalMinutes / MINUTES_IN_HOUR);
            nTotalMinutes = nTotalMinutes % MINUTES_IN_HOUR;
            this.m_upDownMinutes.Value = nTotalMinutes;
        }
        finally
        {
            _totalMinutesChanging = false;
        }
    }
}

【讨论】:

  • 谢谢,这成功了!我最初尝试使用 bool 但搞砸了。任何改进的指针?我在哪里可以获取 7、24、60 等常量? InitializeComponentCustom seem right这个名字吗?还有什么?
  • @Hamish,我认为可以自己定义这些常量。根据您使用控件的内容,您可能希望添加一个事件以在轮询间隔更改时进行通知。我可能会从UserControl 继承以便能够更轻松地定位上下控件。否则我认为没关系。
【解决方案2】:

这看起来不太好。客户端代码不知道为什么它会禁用它看不到的私有控件上的事件。它也没有任何方法可以看到公共财产发生了变化,也没有任何事件可以说公共财产改变了价值。如果它想忽略更改事件,那么它会简单地取消订阅或忽略那些(丢失的)事件。

将 .NET 框架控件视为指南。它们都没有类似于 IEventHackable 的东西。

【讨论】:

  • 嗯,很高兴我问了这个问题。
最近更新 更多