创建一个不显示窗口的程序(2006-03-30修订)

  为了创建一个主窗口不显示的应用程序,我做了若干个探索。结果绕了几个弯,回到最简朴的没有窗体的世界中。
  由此发现,我已经对窗体设计器产生了严重的依赖性,而且自已已经懒得不得了了--哪怕只有几行的代码,也要IDE自动生成。
  本次修订,将探索过程,以及最后的解决,全部呈现出来,希望和我一样懒的人引以为戒。
  2006-03-16 原文
  2006-03-17    修订
  2006-03-30    修订

2006-03-16 多云

  一般在使用NotifyIcon时,都会有一个主窗口。当最小化或关闭主窗口后,可以从NotifyIcon再次显示主窗口。
  这两天在写一个小程序时,想要一个没有窗口的桌面应用,它在工作时不需要窗口显示,顶多用BalloonTip显示一些提示信息。但是在实践中发现,NotifyIcon必须放在窗口中,窗口必须被创建才能显示NotifyIcon。
  最初想的办法是在窗口建立时调用Hide或Visible=false,均不起作用;如果在Shown事件中Hide,则窗口会一闪而过,达不到要求。
  看了一下Main函数:

Application.Run(new FormMain());

  于是认为可能主窗口一定要显示出来的。既然这样,就干脆不要主窗口了,改写Main函数为:

using (new FormMain())
    Application.Run();

  能满足效果。但是发现,再也不能用Close来关闭应用程序,因为此时没有主窗口了,只能使用Application.Exit来关闭应用程序。而关闭应用程序时,不会去结束FormMain,因为FormMain没有显示,不属于Application管理,因此就要使用using来让应用程序结束前自动析构FormMain。

-------------------------------------------------
BTW:
  另外,需要一些设置参数的对话框。于是定义在NotifyIcon的右键菜单中。运行时发现,可以无数次的点选菜单显示对话框,导致对话框在屏幕上同时显示多个出来;试了几种方法后,决定采用遍历Application.OpenForms的方法来确认是否已有对话框显示:
public static DialogResult DoDialog<T>(Form owner) where T : Form, new()
{
    
foreach (Form f in Application.OpenForms)
    
{
        
if (f.Modal)
        
{
            f.BringToFront();
            
return DialogResult.None;
        }

    }


    
using (T dlg = new T())
        
return dlg.ShowDialog(owner);
}


  这样在菜单中只要调用DoDialog就可以保证只有一个对话框在显示了:
private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
{
    DoDialog
<AboutBox>(this);
}



2006-03-17 多云转晴

  随后用reflector来反编译Application.Run方法,试图找出其设置主窗口一定要显示的原因:
public static void Run(Form mainForm) 

  Application.ThreadContext.FromCurrent().RunMessageLoop(
-1new ApplicationContext(mainForm)); 
}
 
  RunMessageLoop则直接调用了RunMessageLoopInner,其参数reason为-1,意为主窗口。RunMessageLoopInner的部份代码如下:
if (reason == -1

   
  
if (this.applicationContext.MainForm != null
  

    
this.applicationContext.MainForm.Visible = true
  }
 
   
}
 

也就是说,创建窗口时,无论如何让窗口隐藏,当到了最后一刻,还是要被显示出来。你只能在显示后再做努力。但无论如何努力,都不能改变窗口被显示的事实,结果只能是一闪而过。

而visible属性的实现,是通过SetVisibleCore(bool value)来实现的。这样倒有了一个简便的方法了,重写 SetVisibleCore 就可以了:

protected override void SetVisibleCore(bool value){} 
//或 
protected override void SetVisibleCore(bool value)
{
  
base.SetVisibleCore(false);
}
 

  但是这种方法仍然不能用Close主窗口的方式来关闭应用程序,还得使用Application.Exit。

  搞到这里,突然发现自已昏了头了,不就是不让窗口显示出来嘛,只要能骗过人就可以了,最简单莫过于设置 ShowInTaskBar 为 false、WindowState 为 Minimized。这样还可以用Close主窗口的方式来关闭应用程序。

最后做一个小结:
    1. 最简单的方法就是让主窗口最小化并且不显示在任务栏上:设置 ShowInTaskBar 为 false、WindowState 为 Minimized。此时,由于主窗口会被创建,因此,可以在主窗口安放所有类型的控件,并且可以使用Close主窗口的方式来关闭应用程序。
 2. 可以采用重写SetVisibleCore方法,禁止窗口显示。同时该窗口并不会被创建。因此,需要主窗口作容器的控件可能不能正常工作,而且,不能使用Close主窗口的方式来关闭应用程序--主窗口根本就没创建出来啊。
 3. 修改Main函数,不为Application.Run指定主窗口。这时,可以Run之前摆弄自已的窗口。但不管窗口是否创建,只要不是主窗口,就只能采用Application.Exit的方式退出应用程序。

  好了,我已经发现自已实在是笨到家了。既然不用创建窗口都可以使用NotifyIcon和ContextMenu/ContextMenuStrip,那我何必非要把它们摆上Form呢?直接在Main中创建NotifyIcon和ContextMenu不就完事了吗?这才叫没有窗口的程序啊。
        static void Main()
        
{
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(
false);

            System.Resources.ResourceManager resources = new System.Resources.ResourceManager("myResource", System.Reflection.Assembly.GetExecutingAssembly());
            NotifyIcon ni = new NotifyIcon();

            ni.BalloonTipIcon 
= System.Windows.Forms.ToolTipIcon.Warning;
            ni.BalloonTipText 
= "Hello world!";
            ni.BalloonTipTitle 
= "Hi.";
            
//ni.ContextMenuStrip = contextMenu;
            ni.Icon = ((System.Drawing.Icon)(resources.GetObject("ni.Icon")));
            ni.Text 
= "Hello world!";
            ni.Visible 
= true;
            ni.MouseClick 
+= delegate(object sender, MouseEventArgs e)
            
{
                ni.ShowBalloonTip(
0);
            }
;

            Application.Run();
        }

 2006-03-30 晴

1. 萧寒网友的方法

和SetVisibleCore一样简单,重写CreateParams属性方法可以实现:

        protected override CreateParams CreateParams{        
            
get{
                Hide();
                
return base.CreateParams;
            }

        }


这个方法能够创建窗口,并且能够使用Close来关闭主窗口和应用程序。因此,算得上是最方便的方法了。Hide在这里比较关键。我试过,去掉CreateParams的WS_VISIBLE标志位是不能够隐藏窗口的。

2. Santé网友的《WinForm程序启动时不显示主窗体的实现方法

这种方法也是改变Application.Run的主窗口。继承ApplicationContext来管理主窗口的生命周期,可以给人不少的启发:
    internal class HideOnStartupApplicationContext : ApplicationContext{
        
public HideOnStartupApplicationContext(Form mainForm){
            mainForm.Closed 
+= delegate(object sender, EventArgs e){
                Application.Exit();
            }
;
        }

    }

    static void Main()    
    
{
        Application.Run(
new HideOnStartupApplicationContext(new MainForm()));
    }


不过要注意的是,这种方法也是采用不创建主窗口的方法。因此,如果需要创建主窗口,目前好象只有两种方法可以做到:
a. 将窗口最小化,并且不显示在任务栏上。
b. 使用萧寒的方法,重写CreateParams。
posted @ 2006-03-16 11:27  沐枫  阅读(7176)  评论(16编辑  收藏  举报