如果表单已关闭,则每一分钟打开一次Winforms屏幕



只有在有事件时才应打开表单,如果没有事件,则不应在屏幕上显示。所以基本上我想用一个定时器来做这件事。exe将持续运行,每分钟后它都会检查数据库,看看是否有数据,如果有,它会显示在屏幕上,并且只会通过用户交互手动关闭。一分钟后,如果数据库中存在数据,它将再次检查并显示表单。

我使用Program.cs文件中的system.threading.Timer在每分钟后打开一个窗口。以下是代码

timer = new System.Threading.Timer((s) => { 
EL.CustomMessageBox l = new EL.CustomMessageBox(); 
l.ShowDialog();                
}, null, TimeSpan.Zero, 60000);

过了一段时间,我看到这个exe仍然在任务管理器中运行,但即使数据库中有数据,它也不会显示在屏幕上。感谢您的帮助。

System.Threading.Timer在线程池线程上运行回调。您永远不应该使用线程池线程进行UI工作,因为:

  1. 它们不运行消息调度循环
  2. 您无法控制线程何时被回收。UI窗口具有线程关联性,如果它们的线程退出,所有关联的窗口都会立即关闭(您甚至不会收到WM_DESTROY消息(

主线程上的普通Application.Run循环,带有隐藏的主窗口和UI计时器,将为您提供更好的服务。

我会将自己的自定义ApplicationContext传递给program.cs中的Application.Run()

这将允许你没有接口,直到你的条件得到满足。应用程序也将继续运行(即使在关闭窗体时(,直到显式调用应用程序。退出((。

您可以在班级级别保留对您的Form的引用。这将帮助您决定是使用现有的,还是创建一个新的。

请注意,我正在使用系统。Windows。表格。计时器,而不是线程计时器。

类似。。。

static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MyContext());
}
}
public class MyContext : ApplicationContext
{
private EL.CustomMessageBox l = null;
private System.Windows.Forms.Timer timer;
public MyContext()
{
timer = new System.Windows.Forms.Timer();
timer.Interval = (int)TimeSpan.FromMinutes(1).TotalMilliseconds;
timer.Tick += Timer_Tick;
timer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
bool result = true; // hit the database and get an answer
if (result)
{
if (l == null || l.IsDisposed)
{
// no form has been created yet, or the previous one was closed
// create a new instance
l = new EL.CustomMessageBox();
l.Show();
}
else
{
// if we get in here, then the previous form is still being displayed
// if your form can be minimized, you might need to restore it
// if (l.WindowState == FormWindowState.Minimized)
// {
//     restore the window in here?
// }
}
// update the form "l" with some data?
l.xxx = yyy;
}
}
}

我忍不住认为,其他答案虽然在技术上非常正确,但实际上并不能解决问题,因为如果你不知道Windows是如何工作的,它们可能没有意义。Idle_Mind最接近我所做的,尽管如果表单设计者熟悉,我会选择一个基本上只使用它的解决方案——因此,我介绍了我将如何解决您面临的任务:

  • 有一个带有一个表单的应用程序(或者在另一个应用程序中使此表单成为一个自治的表单,但为了简单起见,现在可能将其作为一个专用应用程序(-创建一个新的Windows窗体项目

  • 有一个间隔为60000且Enabled=true 的计时器(工具箱中的Windows窗体计时器,而不是System.Threading计时器(

  • 在您的表单上有一个计时器Tick事件处理程序(双击表单设计器下托盘中的计时器以附加事件处理程序(,它可以查询DB并查找是否有消息

  • 如果有新消息,将它们添加到列表框或其他内容中,并调用this.Show()以显示表单

  • 在FormClosing事件上附加一个事件处理程序,这样当用户单击X时,表单就会隐藏而不是关闭:

private void MyForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing) 
{
e.Cancel = true;
Hide();
}
}
  • 也许让FormClosing事件清除消息列表框。这样,如果表单打开,用户正在吃午饭,消息就会不断累积,然后他们可以阅读并通过关闭表单来清除这些消息。在已经可见的表单上调用Show不会有任何作用,所以如果有更多消息进入,并且表单已经可见,消息就会累积到列表框中

良好的快速经验法则;切勿使用系统。Windows窗体应用程序中的线程计时器。请改用窗体设计器工具箱中的计时器。只有在编写服务或控制台应用程序等时才使用线程计时器。出于稳定性原因,Windows控件绝对必须由最初创建控件的线程访问。Windows窗体计时器意识到了这一点,它的Tick事件可以安全地访问窗体应用程序中的控件(窗体是一个控件,显示它需要访问它(

您应该调用Invoke在拥有控件底层窗口句柄的线程上执行委托。

像这样的东西应该起作用:

timer = new System.Threading.Timer((s) => { 
EL.CustomMessageBox l = new EL.CustomMessageBox(); 
l.Invoke((Action) () => 
{
l.ShowDialog();
});               
}, null, TimeSpan.Zero, 60000);

或者更好的是,使用这种扩展方法:

public static void InvokeIfRequired(this Control c, MethodInvoker action)
{
if (c.InvokeRequired)
{
c.Invoke(action);
}
else
{
action();
}
}

这样称呼它:

l.InvokeIfRequired(() => { l.ShowDialog(); });

更多信息请访问:https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls?view=netframeworkdesktop-4.8

最新更新