围绕自定义事件缠绕我的头

玻璃杯

知名会员
已加入
2019年11月22日
留言内容
126
编程经验
Beginner
嗨,这是一种情况,Form1有一个TextBox,Form2有一个ListBox,TextBox将在更新时发送文本字符串(在一秒钟不活动之后),并且ListBox将以文本作为新项进行更新,文本框先前内容的简单历史记录(哈!)。
我还需要另一个事件(相反,当双击列表框项目时,字符串将转到文本框),但我不会再问这个,我会保持简单,我想一旦知道该怎么做,我可以做另一件事。

这是我到目前为止的理解(或者至少是我认为的),如果以下任何错误,请纠正我。

因此,在阅读了我的其他线程中提供的链接之后,我知道有四个步骤可以做到这一点:

1)创建事件参数
2)创建一个使用参数的事件
3)提出引发事件的方法
4)制定处理引发事件的方法

因此,我现在将继续执行第1步,进行澄清,并使用链接中提供的示例作为我自己的示例,并在此示例下对其进行了重命名和粘贴:

C#:
public class TextUpdatedEventArgs : EventArgs
{
    public string NewText { get; set; }
}

因此,eventargs只是一个字符串(文本框中的新文本)。

我想知道的是我既需要设置又需要获取吗?我觉得对吗?引发事件后,它将在文本框中设置新文本作为事件参数,而列表框将获取文本。我了解这个权利吗?

另外,该链接没有告诉我该代码放在哪里,所以我猜它在我的主要表单cs文件的名称空间部分中了吗?

我想补充一点,对于我来说,这第一部分似乎最令人困惑,除了第一点之外的所有内容似乎都更容易理解,但我想我们会发现这是否是正确的 :)
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,605
地点
弗吉尼亚州切萨皮克
编程经验
10+
我想知道的是我既需要设置又需要获取吗?我觉得对吗?引发事件后,它将在文本框中设置新文本作为事件参数,而列表框将获取文本。我了解这个权利吗?
这取决于。如果您希望任何引用您的事件args对象的人弄乱这些值,那么可以,则希望该集合是公共的。有时,事件arg用于传递单向数据(如广播),而其他事件arg用于在事件发送方和事件接收方之间进行双向通信。

如果您希望它本质上是只读的,则将该集合设为私有。由于多线程和并行编程的兴起,现代OOP开始倾向于不可变的对象,而不是1990年代和2000年代的数据传输对象。使用在您之下不会改变的对象来进行推理更容易。

无论如何,如果您希望该设置器是私有的,则必须能够以某种方式设置该值。那是当您将构造函数与事件args对象一起使用时:
C#:
public class TextUpdatedEventArgs : EventArgs
{
    public string NewText { get; private set; }

    public TextUpdatedEventArgs(string newText)
    {
        NewText = newText;
    }
}
 

金西尼

C#论坛主持人
工作人员
已加入
2011年4月23日
留言内容
3,563
地点
悉尼,澳大利亚
编程经验
10+
我想知道的是我既需要设置又需要获取吗?我觉得对吗?
与其他任何财产一样,这是财产。通常何时将属性设为只读?当您希望属性中包含的数据能够被读取但不能被覆盖时。您是否要覆盖该属性中的数据?如果不是,则应为只读。

不知道你是否看过 我的博客文章 是否涉及此主题,但是如果您还没有,可以将其检查一下。
 

约翰·H

C#论坛主持人
工作人员
已加入
2011年4月23日
留言内容
1,076
地点
挪威
编程经验
10+

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,605
地点
弗吉尼亚州切萨皮克
编程经验
10+
嗨,这是一种情况,Form1有一个TextBox,Form2有一个ListBox,TextBox将在更新时发送文本字符串(在一秒钟不活动之后),并且ListBox将以文本作为新项进行更新,文本框先前内容的简单历史记录(哈!)。
这里的关键问题是您正在使用UI作为数据模型。在现代编程技术中,您将数据模型与视图分开。视图(例如您的UI)的工作是向用户反映数据模型的当前状态,并为用户提供一种与数据进行交互的方式。是的,从70年代到90年代初,通常将UI用作数据模型并同时驱动业务逻辑。 (这就是为什么所有的wiz-bang小部件都有一个巨大的市场的原因,所有这些小部件广告都宣称,如果您提供正确的数据,它将为您完成各种花哨的图形和分析,甚至可能在早上为您做早餐。 )之所以通常将数据保留在UI中是因为当时有限的内存和CPU处理能力。内存的每个字节都很重要,因此在两个不同的位置拥有相同的数据没有任何意义。因此,就内存和CPU效率而言,确实如此,实际上很难真正思考和推理数据。

因此,针对您在上面遇到的情况的正确的现代方法是,您的数据模型应由历史项目列表组成。显示Form1时,它将采用列表中的最后一项并将其显示在文本框中。每当在文本框中键入新文本时,该文本就会添加到列表中。显示Form2时,它将在其列表框中显示列表的项目。
 

玻璃杯

知名会员
已加入
2019年11月22日
留言内容
126
编程经验
Beginner
与其他任何财产一样,这是财产。通常何时将属性设为只读?当您希望属性中包含的数据能够被读取但不能被覆盖时。您是否要覆盖该属性中的数据?如果不是,则应为只读。

不知道你是否看过 我的博客文章 是否涉及此主题,但是如果您还没有,可以将其检查一下。

我已经读过几次您的博客,这是一个非常清楚的说明,对我有帮助 很多 了解自定义事件,感谢您的链接。

在C#6.0中,您可以仅具有getter来拥有只读自动属性,这需要构造函数来设置它们: C#6的新增功能-C#指南

事实证明,这正是我所需要的。谢谢。

这里的关键问题是您正在使用UI作为数据模型。在现代编程技术中,您将数据模型与视图分开。视图(例如您的UI)的工作是向用户反映数据模型的当前状态,并为用户提供一种与数据进行交互的方式。是的,从70年代到90年代初,通常将UI用作数据模型并同时驱动业务逻辑。 (这就是为什么所有的wiz-bang小部件都有一个巨大的市场的原因,所有这些小部件广告都宣称,如果您提供正确的数据,它将为您完成各种花哨的图形和分析,甚至可能在早上为您做早餐。 )之所以通常将数据保留在UI中是因为当时有限的内存和CPU处理能力。内存的每个字节都很重要,因此在两个不同的位置拥有相同的数据没有任何意义。因此,就内存和CPU效率而言,确实如此,实际上很难真正思考和推理数据。

因此,针对您在上面遇到的情况的正确的现代方法是,您的数据模型应由历史项目列表组成。显示Form1时,它将采用列表中的最后一项并将其显示在文本框中。每当在文本框中键入新文本时,该文本就会添加到列表中。显示Form2时,它将在其列表框中显示列表的项目。

您应该做的是添加来自 Form1 到某种列表,然后填充 列表框 从那时开始 Form2 创建并显示。

我以前从未用这些术语考虑过它,这非常有趣,我将以这种方式重做,谢谢您的建议。

编辑:快速提问,以避免我的复仇(范围问题),将在哪里创建这样的列表的正确位置?
 

羊皮

退休程序员
已加入
2018年9月5日
留言内容
1,982
地点
英国
编程经验
10+
如果您有两种形式,并且想要添加一个列表,则将其放在何处?

让我们这样说:
C#:
    public partial class Form1 : Form
    {
        public List<string> Lst_A = new List<string>();
        public Form1()
        {
            InitializeComponent();
        }
    }
您将如何以相反的形式访问这些列表中的每一个?
C#:
    public partial class Form2 : Form
    {
        public List<string> Lst_B = new List<string>();
        public Form2()
        {
            InitializeComponent();
        }
    }
还是在任何地方都可以在自己的类中访问静态列表会更好?

例如 :
C#:
    public static class ListBoxList
    {
        static ListBoxList()
        {

        }
        public static List<string> Form2_ListBoxItems { get; set; }
    }
 

玻璃杯

知名会员
已加入
2019年11月22日
留言内容
126
编程经验
Beginner
如果您有两种形式,并且想要添加一个列表,则将其放在何处?

让我们这样说:
C#:
    public partial class Form1 : Form
    {
        public List<string> Lst_A = new List<string>();
        public Form1()
        {
            InitializeComponent();
        }
    }
您将如何以相反的形式访问这些列表中的每一个?
C#:
    public partial class Form2 : Form
    {
        public List<string> Lst_B = new List<string>();
        public Form2()
        {
            InitializeComponent();
        }
    }
还是在任何地方都可以在自己的类中访问静态列表会更好?

例如 :
C#:
    public static class ListBoxList
    {
        static ListBoxList()
        {

        }
        public static List<string> Form2_ListBoxItems { get; set; }
    }

好吧,静态列表类似乎绝对是最好的选择。我从没想过列表类,只有常规列表。我还没有像程序员一样思考:/

非常感谢您的建议,以及与此主题相关的其他问题(如果有的话),我将添加一个新主题并在此处保留我的自定义事件问题。
 

玻璃杯

知名会员
已加入
2019年11月22日
留言内容
126
编程经验
Beginner
所以我终于到了尝试写一个事件的地步。当有人单击列表框中的文本项时,会引发evnt,以便可以将文本复制到另一个表单文本框中。

eventargs将只是单击的文本字符串,这是我的eventargs类:

C#:
使用 System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WindowsFormsApp1
{
    // this event argument will contain the string clicked
    // in the history listbox
    public class ListBoxIndexChangedEventArgs : EventArgs
    {
        private readonly string _clickedText = "";

        public string ClickedText { get; }

        public ListBoxIndexChangedEventArgs(string str)
        {
            this._clickedText = str;
        }
    }
}

由于事件将在Form2上引发,因此将事件声明放入Form2类中:

C#:
public event EventHandler<ListBoxIndexChangedEventArgs> ListBoxIndexChanged;

这是引发事件的方法,我在Form2类中声明了该事件,因为这是从那里调用它的地方:

C#:
// method to raise the ListBoxIndexChanged event (user clicked a new item in the list
        protected virtual void OnListBoxIndexChanged(ListBoxIndexChangedEventArgs e)
        {
            if (ListBoxIndexChanged != null)
            {
                ListBoxIndexChanged(this, e);
            }
        }

假设我的代码一切都很好,有人可以帮助我进行下一步,即处理事件的方法,我不知道它应该去哪里。我的猜测是在Form1上的某个地方,因为这是目标文本框所在的位置。
 

玻璃杯

知名会员
已加入
2019年11月22日
留言内容
126
编程经验
Beginner
我想我有:

C#:
OnListBoxIndexChanged(new ListBoxIndexChangedEventArgs(lstbHistory.Text));

这应该放在列表框的SelectedIndexChanged事件中吗?
 

金西尼

C#论坛主持人
工作人员
已加入
2011年4月23日
留言内容
3,563
地点
悉尼,澳大利亚
编程经验
10+
I think I have it:

C#:
OnListBoxIndexChanged(new ListBoxIndexChangedEventArgs(lstbHistory.Text));

这应该放在列表框的SelectedIndexChanged事件中吗?
在里面 SelectedIndexChanged 事件处理程序。该事件是引发。处理事件的方法是事件处理程序。这 列表框 引发它的事件,您的窗体处理它,然后引发它自己的事件。该事件是 列表框 而事件处理程序是您表单的一部分。
 

玻璃杯

知名会员
已加入
2019年11月22日
留言内容
126
编程经验
Beginner
很抱歉延迟,我被VB跟踪。

因此,我想到目前为止我所做的一切都是正确的,但是下一步是什么?该事件是通过字符串引发的,所以我需要让TextBox监听事件并获取字符串吗?我已经读过几次您的博客文章JM,但很难"translate"从您的榜样到我的问题,如果您知道我的意思。
 

玻璃杯

知名会员
已加入
2019年11月22日
留言内容
126
编程经验
Beginner
我终于设法实现了自己想做的。在Google上进行自学并非易事,因此,如果有任何自学者为此而苦苦挣扎,我将向您展示如何教会我。

你应该去的第一个地方是 JM的博客文章 关于该主题,它没有介绍如何将事件从一种形式发送到另一种形式,而是先阅读它并理解他显示的示例。

因此,假设我们在窗体1上有一个文本框,在窗体2上有一个列表框,这就是如何使用事件使您能够单击列表框中的项目并将其文本发送到文本框的方法。
首先创建您的自定义eventargs类,它将需要一个属性来保存要发送给另一个控件的信息,在这种情况下,它是一个字符串。

C#:
public class CustomEventArgs
{
    protected string _clickedText;
    public CustomEventArgs(string listBoxText) => _clickedText = listBoxText;
    public string ClickedText => _clickedText;
}

在表单2的类中,是需要从中发送文本的表单,声明您的事件,并使其使用自定义eventargs类。在这种情况下,该事件称为ListBoxSelectionChanged:

C#:
public partial class Form2 : Form
{
    public event EventHandler<CustomEventArgs> ListBoxSelectionChanged;
      
    // the rest of your code
      
}

在表单2的构造函数中,附加表单的"shown"它的事件处理程序"shown"事件,我们这样做是因为在显示的事件处理程序中,我们将列表框的click事件处理程序附加到列表框的click事件(在下一步中):

C#:
public Form2()
{
    InitializeComponent();
    Shown += Form2_Shown;
}

在表格2中"form shown"事件处理程序,将列表框的click事件处理程序附加到列表框的click事件:

C#:
private void Form2_Shown(object sender, EventArgs e)
{
    ListBox1.Click += ListBox1_Click;
}

然后,在列表框的click事件处理程序中,引发事件,并将您要发送的数据传递给另一个表单的控件:

C#:
private void ListBox1_Click(object sender, EventArgs e)
{
    ListBoxSelectionChanged?.Invoke(this, new CustomEventArgs(ListBox1.Text));
}

在此示例中,我使用一个按钮打开窗体2。无论打开该窗体的事件处理程序如何,此代码都将放在其中(此代码位于窗体1上)。创建了Form 2的实例,下一行将刚刚创建的表单的事件处理程序OnListBoxSelectionChanged(在下一步中编写)订阅到事件ListBoxSelectionChanged,然后显示该事件:

C#:
private void Button_Click(object sender, EventArgs e)
        {
            var form2 = new Form2();

            form2.ListBoxSelectionChanged += OnListBoxSelectionChanged;
            form2.ShowDialog();
        }

在OnListBoxSelectionChanged事件处理程序中(仍以表格1的形式),文本框的文本将使用来自自定义eventargs的字符串数据进行更新:

C#:
private void OnListBoxSelectionChanged(object sender, CustomEventArgs myArgs)
{
    TextBox1.Text = myArgs.ClickedText;
}

中提琴就完成了。希望我使用正确的术语并正确地解释了事情,但这是可行的。希望它能在某个时间点帮助某人。
 
Last edited:

金西尼

C#论坛主持人
工作人员
已加入
2011年4月23日
留言内容
3,563
地点
悉尼,澳大利亚
编程经验
10+
不要忘记注销事件处理程序。完成后,对于每个“ + =”都应有一个对应的“-=”。此外,这些天也无需声明自己的代表。只需使用通用 事件处理程序<TEventArgs> 如果要使用自定义事件args。此外,您的活动名称不应以"On"。适当的事件名称可能是 列表框点击 然后您将有一个引发该事件的方法,名为 OnListBoxClick。该方法的原因是您可以声明它 受保护的 然后派生类可以重写它以提供事件的自定义行为。
 

玻璃杯

知名会员
已加入
2019年11月22日
留言内容
126
编程经验
Beginner
哦,我知道,这是我第一次听到有关取消注册事件的信息,我需要对此进行调查。

我再次弄乱了命名约定,我应该再看一次您的博客文章以确保,我只是没有意识到我做错了。我将尝试编辑帖子,重命名事件和处理程序,并按照您的描述进行声明。
 

金西尼

C#论坛主持人
工作人员
已加入
2011年4月23日
留言内容
3,563
地点
悉尼,澳大利亚
编程经验
10+
哦,我知道,这是我第一次听到有关取消注册事件的信息,我需要对此进行调查。
注册事件处理程序会在两个对象之间创建一个链接,这将防止垃圾回收尽快发生,因此注销该链接非常重要。
 

玻璃杯

知名会员
已加入
2019年11月22日
留言内容
126
编程经验
Beginner
我已经编辑了帖子,我将事件重命名为ListBoxSelectionChanged,将处理程序重命名为OnListBoxSelectionChanged,这与您在我认为的博客帖子中的命名方式匹配。我也摆脱了代表。

我认为要取消订阅此事件的时间是在关闭form2时对不对?但是,由于该语句必须进入表单1的​​代码中,因此form1将如何知道form2正在关闭?
 

金西尼

C#论坛主持人
工作人员
已加入
2011年4月23日
留言内容
3,563
地点
悉尼,澳大利亚
编程经验
10+
我已经编辑了帖子,我将事件重命名为ListBoxSelectionChanged,将处理程序重命名为OnListBoxSelectionChanged,这与您在我认为的博客帖子中的命名方式匹配。
没有。以事件名称为前缀的方法"On"是引发事件的方法,而不是处理事件的方法。这 OnListBoxSelectionChanged 方法应与 列表框SelectionChanged 事件。应该引发事件的唯一位置是在该方法中,并且当您要引发事件时可以调用该方法。这样,任何派生类都可以覆盖该方法并添加要在该事件上执行的自定义功能,然后从基类引发该事件将执行该功能。如果包含事件的类从不被继承,那么它并不重要,但是我更喜欢每次都遵循约定。这样,我绝不会在我应该使用的场合偶然不使用该约定。
我认为要取消订阅此事件的时间是在关闭form2时对不对?但是,由于该语句必须进入表单1的​​代码中,因此form1将如何知道form2正在关闭?
你在打电话 显示对话框。该方法返回后,窗体将关闭。这才是重点。如果你在打电话 展示 那么您可以简单地处理 表格已关闭 event as well.

顺便说一句,通过调用显示的表单 显示对话框 不会自动处理,因此您应该自己进行处理。做到这一点的方法是使用 使用 语句,因此将其放置在块的末尾。
 

玻璃杯

知名会员
已加入
2019年11月22日
留言内容
126
编程经验
Beginner
好的,让我确保将其记下来,以确保撰写本文时的命名约定:

C#:
form2.ListBoxSelectionChanged += OnListBoxSelectionChanged;

private void OnListBoxSelectionChanged(object sender, CustomEventArgs myArgs)
{
    TextBox1.Text = myArgs.ClickedText;
}

我正在使用"On"在错误的位置,所以我需要重命名"OnListBoxSelectionChanged"因为这是我处理引发事件的方法。此方法是否有命名约定?您觉得这有什么好名声?

同样在这个主题上,我使用了非常通用的名称"CustomEventArgs"就像自定义事件args类的明确示例名称一样,显然这实际上是一个不好的名字。我读到(我认为是在您的博客上),只要您需要利用事件数据就可以使用事件"carry"。因此,在我的示例中(即使我现在只有一个用途),我的自定义事件args类将只包含一个字符串,因此,如果我只需要传递一个字符串,则无需编写新的eventargs类,我总是可以为此重用此事件。
这使我想知道最好的命名方式是什么。我是否根据数据命名?就像是"SingleStringEventArgs"因为这清楚地描述了我眼中的事件参数,并根据它的功能对其进行了命名,例如"ListBoxSelectionEventArgs"就其携带的实际数据而言,似乎不太清楚。这些名称中的任何一个都会是一个不错的选择吗?还是会有其他更好的选择?
 

金西尼

C#论坛主持人
工作人员
已加入
2011年4月23日
留言内容
3,563
地点
悉尼,澳大利亚
编程经验
10+
记录下来,这只是一个约定,不需要任何人遵守,但是我建议遵循它,因为一致性始终是一件好事,然后您也将以与框架代码中相同的方式进行操作。我将整理一个完整的示例,但可能要花几个小时,因为现在这里接近1.00 AM。
 
最佳 底部