C#事件具体实现步骤

作者:小菜 更新时间:2025-03-16 点击数:
简介:定义一个事件成员,表示该类型提供了如下功能:1.能够在事件中注册方法 2.能够在事件中注销方法 3.当事件发生时,注册的方法会被通知(事件内部维护了一个注册方法

【菜科解读】

定义一个事件成员,表示该类型提供了如下功能:

1.能够在事件中注册方法 2.能够在事件中注销方法 3.当事件发生时,注册的方法会被通知

(事件内部维护了一个注册方法列表)

CLR的事件模型是基于委托的,它可以通过类型安全的方式调用回调方法。

而回调方法是订阅事件的对象接收通知的方式。

通过一个例子来说明:

①Fax对象的方法注册到MailManager事件 ②Pager对象的方法注册到MailManager事件 ③新的邮件到达MailManager ④MailManager对象向注册的方法发出通知,接收通知的方法可以随意处理。

具体实现步骤如下:

1.定义一个类型,能够hold住任何发送到事件通知接收者的信息

当一个事件被触发,触发事件的对象可能希望发送一些额外的信息给事件通知的接收对象。

这些额外的信息需要封装在它自己的类中,根据约定该类需要从System.EventArgs类派生,并且命名以EventArgs结尾。

这里定义一个NewMailEventArgs类:

public class NewMailEventArgs : EventArgs private readonly String m_from, m_to, m_subject; public NewMailEventArgs(String from, String to, String subject) m_from = from; m_to = to; m_subject = subject; public String From { get { return m_from; } } public String To { get { return m_to; } } public String Subject { get { return m_subject; } } }

关于EventArgs

[ComVisible(true)][Serializable]public class EventArgs public readonly static EventArgs Empty; static EventArgs() EventArgs.Empty = new EventArgs(); public EventArgs()}

这个类没有实际的用途,只是作为一个基类让其他对象继承。

很多对象不需要传递额外的信息,例如按钮事件,只是调用一个回调方法就够了。

当我们定义的事件不需要传递额外的信息时,这时调用EventArgs.Empty就行了,不需要重新构建一个EventArgs对象。

2.定义事件成员

public class MailManager { ... //NewMail事件名, //EventHanlder,所有的事件通知接收对象必须提供给该委托类型匹配的回调方法 public event EventHandler NewMail; }

System.EventHandler委托的定义为:public delegate void EventHandler(Object sender, TEventArgs e) where TEventArgs: EventArgs;

为什么这里第一个参数sender的类型是Object?毕竟MailManager类型是唯一触发这个事件的,所以可以设计成这样:void MethodName(MailManager sender,NewMailEventArgs e)这种情况会有一个弊端,当sender是SmtpMailManager时,回调方法也需要改变,使用Object能够很好的兼容。

定义回调方法的参数名约定为e,这样做主要是为了保持一致性。

方便开发人员。

事件机制要求所有的事件处理方法必须返回void,这是必要的,因为一个事件可能触发很多的回调方法,没有办法获取所有的返回值,索性就不允许返回值,全部为void。

有些FCL里面的事件处理程序没有遵循,而是返回了一个Assembly类型。

3.定义一个方法来响应事件的发生

按照惯例,这个类应该定义一个protected,virtual的方法供内部的代码调用。

这个方法接收一个NewMailEventArgs对象,这个对象包含要传递给消息接收方的一些信息。

如下:

protected virtual void OnNewMail(NewMailEventArgs e) { //复制一个委托的引用到临时字段temp,这样确保线程安全 EventHandler temp = Interlocked.CompareExchange(ref NewMail, null, null); //任何注册到事件里面的方法,通知它们 if (temp != null) { temp(this, e); } }

Tips:使用线程安全的方式触发事件(①——>④为不断改进的过程)

①当.NET第一次推出的时候,给开发者推荐的事件触发方式如下:

//v1.0protected virtual void OnNewMail(NewMailEventArgs e) { if (NewMail != null) { NewMail(this, e); } }

弊端:这里检查了NewMail不为null才触发,但是当检查完之后,在调用NewMail之前,有其他的线程从委托链中移除了一个委托,使得NewMail为null,此时会抛出异常。

②先将NewMail用一个临时变量存起来,这时就不会因为调用时被其他线程修改而抛出异常。

之所以能够这样做,是因为委托类型跟字符串类型一样是不可变的。

//v2.0protected void OnNewMail(NewMailEventArgs e) { EventHandler temp = NewMail; if (temp != null) { temp(this, e); } }

弊端:可能被编译器优化掉本地temp变量,如果发生这种情况,就回到了第一种了。

③修复上面的bug,如下:

//v3.0protected void OnNewMail(NewMailEventArgs e) { EventHandler temp = Thread.VolatileRead(ref NewMail); if (temp != null) { temp(this, e); } }

这里使用VolatileRead会强制读取temp的值,但是这里不能这样写,编译不通过。

但是有一个Interlocked.CompareExchange可以使用:

//v4.0 protected virtual void OnNewMail(NewMailEventArgs e) { //复制一个委托的引用到临时字段temp,这样确保线程安全 EventHandler temp = Interlocked.CompareExchange(ref NewMail, null, null); //任何注册到事件里面的方法,通知它们 if (temp != null) { temp(this, e); } }

如果NewMail为null,CompareExchange将NewMail的值改变为null,如果不为null则返回原值。

换句话说,CompareExchange不会改变NewMail的值,只是以线程安全的方式返回NewMail的值,这里是一个原子操作。

第④个版本是最佳的,技术上最正确的版本。

实际开发中还是可以使用第②个版本,因为JIT编译器能够识别这种模式而不去优化本地的temp变量。

特别地,所有微软的JIT编译器都遵循不会对堆引入新的读取,因此缓存一个引用在本地变量可以确保堆引用只被访问一次(这是没有写入文档的,理论上,还是可能发生变化,所以最好选用第④版本。

为了方便可以定义一个扩展方法来封装:

public static class EventArgExtensions { public static void Raise(this TEventArgs e, Object sender, ref EventHandler eventDelegate) where TEventArgs : EventArgs { EventHandler temp = Interlocked.CompareExchange(ref eventDelegate, null, null); if (temp != null) { temp(sender, e); } } }

然后可以重写OnNewMail:

protected virtual void OnNewMail(NewMailEventArgs e) e.Raise(this, ref NewMail); }

4.定义一个方法用来传递一些输入到事件

public void SimulateNewMail(String from, String to, String subject) NewMailEventArgs e = new NewMailEventArgs(from, to, subject); OnNewMail(e); }123在本页阅读全文 本文导航 第1页: 首页 第2页: 编译器是怎么实现事件的? 第3页: 定义类型监听事件 事件,具体,实现,步骤,定义,一个,事件,成员,

实现仿Win8Metro风格的按钮交换和拖动删除功能

仿Win8 Metro风格如何实现两个按钮拖动交换位置,包括同一个页面按钮交换或者两个页面之间的按钮交换。

另外就是如何拖动删除界面上的快捷方式。

按钮交换和拖动删除,这两个功能基本上是现在智能手机的标准功能,不管是IOS或者Android都有类似功能。

我实现的功能,主要是参考Android的功能实现。

下面这个就是动态交换按钮效果图:还是先把逻辑关系图放出来:1、按钮拖动怎么样才能实现拖动一个按钮到另外一个按钮位置上,实现交换?这个首先一个需要做的就是拖动按钮的操作。

按钮拖动我放到封装的DUIButton里面实现。

下面我们看看DUIButton里面如何把按钮拖动出来。

int CDUIButton::OnMouseMove(POINT point, CDC * pDC, CDC * backDC)//printf("mythou------->enter the Page::omMouseMove");//判断拖动的条件,按下按钮并且移动的距离大于30像素的时候,认为是拖动按钮 if( abs(point.x - m_iEndSlide) > 30 || abs(m_clickY - point.y) > 30 || m_mouseMove)//拖动快捷键 if(m_ClickState) m_mouseMove = TRUE; CRect rect = CRect(0, 0, ScreenWidth, ScreenHeight); //恢复保存的背景,主要是提高绘画效率 CDC destDC; destDC.CreateCompatibleDC(backDC); CBitmap CompatibleBmp; CompatibleBmp.CreateCompatibleBitmap(backDC,rect.Width(),rect.Height()); CBitmap *pOlddestBmp = destDC.SelectObject(&CompatibleBmp); destDC.FillSolidRect(&rect,RGB(0,0,0)); CDC srcDC; srcDC.CreateCompatibleDC(backDC); HBITMAP hOldBmp; destDC.BitBlt(0, 0, rect.Width(),rect.Height(), backDC, 0, 0, SRCCOPY); //根据用户手指移动的位置,动态刷新按钮,形成按钮跟谁手指移动的效果 hOldBmp = (HBITMAP)srcDC.SelectObject(m_btnHBitmap); m_pngCtrl.BiltPNG(&destDC,&srcDC,(point.x-(m_btnRc.Width()/2) ), (point.y-(m_btnRc.Height()/2)), m_btnRc.Width(),m_btnRc.Height(), m_AlphaSel); srcDC.SelectObject(hOldBmp); //把按钮图片,绘画到屏幕 pDC->BitBlt(0,0,rect.Width(),rect.Height(),&destDC,0,0,SRCCOPY); srcDC.DeleteDC(); destDC.SelectObject(pOlddestBmp); CompatibleBmp.DeleteObject(); destDC.DeleteDC(); return 1; return 0;从这里可以发现,其实拖动一个按钮,就是把该按钮的图片,跟随手指的移动而动态贴图。

需要注意的是如何才能保证拖动的流畅性。

这个需要把你的背景图做成缓存,保留下来,拖动过程中,都是使用缓存中原始的背景图。

这样每次拖动,只需要绘画一个按钮的图片,才能流畅得拖动按钮。

另外你手指点击按钮还需要做一些条件判断,需要符合条件的情况下,才能拖动按钮。

我这里把手指触摸按钮分为3种行为:点击按钮,打开某个程序触摸按钮,滑动切换页面把按钮拖动出来,执行交换、删除、添加操作这几个也是目前智能机系统一般都支持的手势操作,上面我们按钮的拖动,就是属于第三种情况。

2、按钮交换把按钮拖动出来,然后拖动到需要交换的按钮的位置,释放按钮,执行交换操作。

这个就是交换的流程,这里根据释放的位置来识别到底跟哪一个按钮进行交换。

//大按钮移动到大按钮位置 Edited by mythou if (UpBlockBig) //printf("mythou-------->enter change the big block"); CDUIButton * tempDUIBtn; tempDUIBtn = m_pVUICtrlContent.at(m_BlockLine).at(m_BlockClickNum); //保存IDS CString FirstBtnIDS = tempDUIBtn->GetBtnIDS(); m_pVUICtrlContent.at(m_BlockLine).at(m_BlockClickNum) = m_pVUICtrlContent.at(Line).at(vectorBtnIndex); //保存IDS CString SecondBtnIDS = m_pVUICtrlContent.at(Line).at(vectorBtnIndex)->GetBtnIDS(); m_pVUICtrlContent.at(Line).at(vectorBtnIndex) = tempDUIBtn; //动画效果 ChangeBtnPosAni(m_pVUICtrlContent.at(m_BlockLine).at(m_BlockClickNum), m_pVUICtrlContent.at(Line).at(vectorBtnIndex)); //修改配置文件 int PosIndex1 = GetBtnPos(m_BlockLine,m_BlockClickNum); int PosIndex2 = GetBtnPos(Line,vectorBtnIndex); m_pSaveInterFace->SwitchSameButton(Page,m_BlockLine,PosIndex1,FirstBtnIDS, Page,Line,PosIndex2,SecondBtnIDS); }这是一个简单的交换逻辑,因为我们的按钮都是存放在Page类里面的二维Vector向量里面,按钮交换位置也就是交换Vector里面的值。

因为Vector里面存放只是按钮对象的指针应用。

因此,Vector交换两个指针也不会存在负责的数据交互。

当然如果要做出比较好的交换效果,我们免不了使用动画,交换过程中。

我们加入一个动画效果,我这里做的是一个淡入淡出的效果,主要控制按钮图片的Alpha值,形成一个较好的交换效果。

最后还需要把交换的位置信息记录到文件里面,方便下次启动程序的时候,保存交换后的效果。

需要注意的是交换按钮刷新和动画效果之间的操作。

要做到流程,考虑使用一个线程运行动画。

3、拖动删除按钮这个功能其实就是参照Android的删除快捷方式做的。

当按钮被拉动出来后,界面上方会出现一个有垃圾桶图标的区域,把按钮拖动到该区域释放,就可以把相对的快捷方式删除。

下面我们看看逻辑上如何实现。

//在删除区域释放,删除按钮 if (m_rcMainInterfaceDel.PtInRect(point)) //printf("\n mythou------> Enter OnLButtonUpDeal() Delete the Btn ************** \n"); //删除选中按钮 CString DelBtnIDS = m_pVUICtrlContent.at(m_BlockLine).at(m_BlockClickNum)->GetBtnIDS(); m_pVUICtrlContent.at(m_BlockLine).at(m_BlockClickNum)->ResetAllClickFlag(); m_pVUICtrlContent.at(m_BlockLine).erase(m_pVUICtrlContent.at(m_BlockLine).begin()+m_BlockClickNum); //填充空按钮 CDUIButton *pBtn = new CDUIButton(); pBtn->SetNullBtn(); m_pVUICtrlContent.at(m_BlockLine).insert((m_pVUICtrlContent.at(m_BlockLine).begin()+m_BlockClickNum),pBtn); //如果是大按钮,再填充一次 if (m_BigBlock) CDUIButton *pBtn = new CDUIButton(); pBtn->SetNullBtn(); m_pVUICtrlPos.at(m_BlockLine).push_back(CPoint(0,0)); m_pVUICtrlContent.at(m_BlockLine).insert((m_pVUICtrlContent.at(m_BlockLine).begin()+m_BlockClickNum),pBtn); //修改配置文件 int PosIndex1 = GetBtnPos(m_BlockLine,m_BlockClickNum); m_pSaveInterFace->DeleteButton(Page,m_BlockLine,PosIndex1,DelBtnIDS,m_BigBlock); //DeleteButton(Page,m_BlockLine,PosIndex1,DelBtnIDS,m_BigBlock); m_BigBlock = FALSE; ReloadBtnPos(); return DEL_BTN;删除操作在逻辑上也很简单,就是删除我们记录的Vector里面的相对应的按钮指针。

不过删除后,我们需要做一些额外的操作。

第一需要填充一个空按钮指针到原来的位置。

这个操作主要是用来记录界面上哪些位置是可以存放按钮和交换按钮。

空按钮是一个空类,只有一个标记用来记录位置。

删除后还需要针对按钮的类型做不同的添加操作,大按钮和小按钮。

最后还需要在配置文件做记录,记录哪个按钮删除了。

如果需要一个好的效果,可以类似交换按钮一样,加入一个动画效果。

4、添加快捷方式栏这是额外做的一个功能,主要是把常用的功能加入到一个导航栏上面,可以在任何界面使用相关常用功能。

void CDUIPage::Send2TaskBar(CDUIButton *pBtn) printf("\n mythou-------->Enter Send2TaskBar ********************************\n"); DUIButtonData *pDuiData = new DUIButtonData(); //拷贝数据 wcscpy(pDuiData->name, pBtn->m_btnName.GetBuffer(pBtn->m_btnName.GetLength())); pBtn->m_btnName.ReleaseBuffer(); wcscpy(pDuiData->cmd, pBtn->m_BtnClickCMD.GetBuffer(pBtn->m_BtnClickCMD.GetLength())); pBtn->m_BtnClickCMD.ReleaseBuffer(); wcscpy(pDuiData->animate, pBtn->m_AnimateType.GetBuffer(pBtn->m_AnimateType.GetLength())); pBtn->m_AnimateType.ReleaseBuffer(); wcscpy(pDuiData->ids, pBtn->m_BtnNameIDS.GetBuffer(pBtn->m_BtnNameIDS.GetLength())); pBtn->m_BtnNameIDS.ReleaseBuffer(); CString btnPicName = GetExeName(pBtn->m_btnPic); wcscpy(pDuiData->picName, btnPicName.GetBuffer(btnPicName.GetLength())); btnPicName.ReleaseBuffer(); COPYDATASTRUCT cpdata; cpdata.dwData=PROCESSID cpdata.cbData = sizeof(DUIButtonData); cpdata.lpData = (PVOID)pDuiData; HWND mainWnd = ::FindWindow(NULL,_T("APKTaskBar")); if (!mainWnd) return; printf("\n mythou------->Send the OnCopyData Send2TaskBar ********************************"); ::SendMessage(mainWnd, WM_COPYDATA, (WPARAM)m_MainWndH, (LPARAM)&cpdata); delete pDuiData;因为我的快捷栏是另外一个独立程序,所以这里把需要添加的按钮拉动到界面底部,然后把按钮的相关数据转换为相关数据包,发送到快捷栏程序里面。

这里也需要加入相关的动画效果,才能达到较好的界面交互效果。

剩下的就是另外一个程序处理发送过来的数据包。

解析然后显示出来就好了。

今天主要是讲解界面上常用的交换按钮、删除按钮、添加快捷栏等操作。

其中交换按钮这里只是把同页面的交换做了解说,除了同页面交换外,也需要做到不同页面之间交换,这个原理是一样,只是不同页面之间交换需要做到逻辑页面的切换。

这个也是我做了Page类作为页面管理类的原因。

不同类之间交换按钮,只要切换Page类就好了。

转载请标明出处:http://www.cnblogs.com/mythou/p/3172707.html 实现,仿,Win8Metro,风格,的,按钮,交换,和,拖动

C#实现UDP数据包大文件分包传输和接收组包

如果需要使用UDP传输较大数据,例如传输10M的图片,这突破了UDP的设计原则。

UDP的设计是基于"datagram",也就是它假设你发送的每个数据包都能包含在单一的包内。

并且设定UDP数据包的最大长度受基础网络协议的限制。

UDP数据包的理论最大长度限制是 65535 bytes,这包含 8 bytes 数据包头和 65527 bytes 数据。

但如果基于IPv4网络传输,则还需减去 20 bytes 的IP数据包头。

则单一的UDP数据包可传输的数据最大长度为:MaxUdpDataLength = 65535 - 8 - 20 = 65507 bytes这就需要实现UDP包的分包传输和接收组包功能。

分包功能1 /// 2 /// UDP数据包分割器 3 /// 4 public static class UdpPacketSplitter 6 /// 7 /// 分割UDP数据包 8 /// 9 /// UDP数据包所持有的序号10 /// 被分割的UDP数据包11 /// 分割块的长度12 /// 13 /// 分割后的UDP数据包列表14 /// 15 public static ICollection Split(long sequence, byte[] datagram, int chunkLength)17 if (datagram == null)18 throw new ArgumentNullException("datagram");20 List packets = new List();22 int chunks = datagram.Length / chunkLength;23 int remainder = datagram.Length % chunkLength;24 int total = chunks;25 if (remainder > 0) total++;27 for (int i = 1; i 0)35 int length = datagram.Length - (chunkLength * chunks);36 byte[] chunk = new byte[length];37 Buffer.BlockCopy(datagram, chunkLength * chunks, chunk, 0, length);38 packets.Add(new UdpPacket(sequence, total, total, chunk, length));41 return packets;43 }发送分包1 private void WorkThread() 2 { 3 while (IsRunning) 5 waiter.WaitOne(); 6 waiter.Reset(); 8 while (queue.Count > 0)10 StreamPacket packet = null;11 if (queue.TryDequeue(out packet))13 RtpPacket rtpPacket = RtpPacket.FromImage(14 RtpPayloadType.JPEG, 15 packet.SequenceNumber, 16 (long)Epoch.GetDateTimeTotalMillisecondsByYesterday(packet.Timestamp),17 packet.Frame);19 // max UDP packet length limited to 65,535 bytes20 byte[] datagram = rtpPacket.ToArray(); 21 packet.Frame.Dispose();23 // split udp packet to many packets 24 // to reduce the size to 65507 limit by underlying IPv4 protocol25 ICollection udpPackets 26 = UdpPacketSplitter.Split(27 packet.SequenceNumber, 28 datagram, 29 65507 - UdpPacket.HeaderSize);30 foreach (var udpPacket in udpPackets)32 byte[] udpPacketDatagram = udpPacket.ToArray();33 // async sending34 udpClient.BeginSend(35 udpPacketDatagram, udpPacketDatagram.Length,36 packet.Destination.Address,37 packet.Destination.Port,38 SendCompleted, udpClient);43 }接收组包功能1 private void OnDatagramReceived(object sender, UdpDatagramReceivedEventArgs e) 2 { 3 try 5 UdpPacket udpPacket = UdpPacket.FromArray(e.Datagram); 7 if (udpPacket.Total == 1) 9 RtpPacket packet = new RtpPacket(udpPacket.Payload, udpPacket.PayloadSize);10 Bitmap bitmap = packet.ToBitmap();11 RaiseNewFrameEvent(12 bitmap, Epoch.GetDateTimeByYesterdayTotalMilliseconds(packet.Timestamp));14 else16 // rearrange packets to one packet17 if (packetCache.ContainsKey(udpPacket.Sequence))19 List udpPackets = null;20 if (packetCache.TryGetValue(udpPacket.Sequence, out udpPackets))22 udpPackets.Add(udpPacket);24 if (udpPackets.Count == udpPacket.Total)26 packetCache.TryRemove(udpPacket.Sequence, out udpPackets);28 udpPackets = udpPackets.OrderBy(u => u.Order).ToList();29 int rtpPacketLength = udpPackets.Sum(u => u.PayloadSize);30 int maxPacketLength = udpPackets.Select(u => u.PayloadSize).Max();32 byte[] rtpPacket = new byte[rtpPacketLength];33 foreach (var item in udpPackets)35 Buffer.BlockCopy(36 item.Payload, 0, rtpPacket, 37 (item.Order - 1) * maxPacketLength, item.PayloadSize);40 RtpPacket packet = new RtpPacket(rtpPacket, rtpPacket.Length);41 Bitmap bitmap = packet.ToBitmap();42 RaiseNewFrameEvent(43 bitmap, 44 Epoch.GetDateTimeByYesterdayTotalMilliseconds(packet.Timestamp));46 packetCache.Clear();50 else52 List udpPackets = new List();53 udpPackets.Add(udpPacket);54 packetCache.AddOrUpdate(55 udpPacket.Sequence, 56 udpPackets, (k, v) => { return udpPackets; });60 catch (Exception ex)62 RaiseVideoSourceExceptionEvent(ex.Message);64 } 实现,UDP,数据,包大,文件,分包,传输,和,接收,

加入收藏
               

C#事件具体实现步骤

点击下载文档

格式为doc格式

  • 账号登录
社交账号登录