NO.2Windows CE窗体切换(慢慢理解)

Posted on 2010-05-28 18:44  我爱熊  阅读(516)  评论(0)    收藏  举报

所谓的窗体转换,其实包括两种形态。一是显示一个新的窗体,让它覆盖原有窗体;二是关闭一个现有窗体,让其后台窗体暴露出来,成为前台窗体。

 

显示一个新的窗体通常有两种方式——调用Show方法或ShowDialog方法。他们的区别主要在于,对Show方法的调用会立即返回,新窗体显示的同时Show方法调用语句后面的代码会得到执行;而对ShowDialog方法的调用会被阻塞,直到新窗体关闭,ShowDialog方法调用语句后面的代码才能得到执行。

 

通常建议使用ShowDialog方法。因为窗体间的切换还带来一个新的问题,就是如何在窗体间传递数据。比如图1中的“案件核实细节”实际上是为“案件核实窗体”提供附加的、更丰富的数据,这就需要在关闭“案件核实细节”时将搜集到的信息传递给“案件核实窗体”。使用对话框无疑是最合适的。我们可以在新窗体中提供一些属性,这样当通过设置DialogResult属性关闭对话框时,可以很方便地取出其中的数据。

实例代码

有两个窗体Form1Form2,存在从Form1Form2的导航。我们可以这样实现Form2

// using语句(略)
class Form2 : Form
{
    private TextBox m_txtName;
    public Form2() {...} // 构造器(略)
    public string UserName
    {
        get { return m_txtName.Text; }
        set { m_txtName.Text = value; }
    }
}

  然后可以在Form1种这样去使用它:

// using语句(略)
class Form1 : Form
{
    ...
    private void btnOK_Click(object sender, EventArgs e)
    {
        Form2 f = new Form2();
        f.UserName = m_userName; // 还可以在窗体显示前为其属性赋初值
        f.ShowDialog(); // 显示新窗体,代码到这里暂停,直到新窗体关闭
        m_userName = f.UserName; // 取出新窗体中的数据
        f.Dispose();
    }
}

略作观察就会发现,Form1中的这段代码和使用通用对话框一样方便。正是出于这种原因,在窗体间进行导航时更推荐使用ShowDialog方法。前面所述的“城市监管系统”中所有的窗体都是通过这种方式进行导航的。

 

窗体的回退

  窗体的回退属于第二种窗体切换形态。如果使用Show方法显示新窗体,我们需要在新窗体中调用this.Close();来关闭;如果使用ShowDialog方法,则需要在新窗体中设置自己的DialogResult属性:

this.DialogResult = DialogResult.OK;

  一旦该属性被设置,新窗体立即关闭,原窗体中对ShowDialog方法的调用返回,并将这里设置的属性值作为返回值传递给原窗体,同时继续执行后面的代码。因此在前面Form2的代码中,我们还可以从对f.ShowDialog方法的调用中取得返回值,并根据返回值结果决定后面进行哪些操作:

Form2 f = new Form2();
f.UserName = m_userName;
DialogResult r = f.ShowDialog();
if(r == DialogResult.OK)
    m_userName = f.UserName;
f.Dispose();

  另外需要注意的就是最后一行对f.Dispose方法的调用,这是必须的,因为移动设备资源非常有限,必须及时进行释放。

隐藏窗体标题

  当应用程序启动了多个窗体以后,如果打开“开始”→“设置”→“内存”→“运行的程序”,我们会同时看到我们所启动的所有程序(确切地说是所有窗体)。这样,对应着我们的一个应用程序,则出现了多个窗体。

  这对我们调试应用程序是非常有必要的,因为我们可以清晰地知道目前有多少个窗体被覆盖在顶层窗体之下,并针对窗体导航图检查是否符合设计;同时,如果同一时刻出现的窗体数量过多,我们还需要对窗体导航图进行优化,争取做到同时显示的窗体尽量少。

  然而,如果程序发布时依然保留这种特征,就会带来很多问题。比如用户可能会通过“运行的程序”界面来手动地把某个窗体激活,这样就干扰了程序中的窗体导航关系。这是我们需要运用一些技巧,在这个界面中隐藏我们的窗体,只暴露一个顶层窗体。

  幸运的是,Mobile系统的这种功能是通过检查窗体标题来实现的,因此只要我们隐藏窗体的标题,就能做到在“运行的程序中”隐藏一个窗体。实现的方法很简单,当构造一个新的窗体时,隐藏掉原窗体的标题即可。

  这时也有两种方法,一是在原窗体中隐藏和恢复窗体标题:

// Form1
private void btnOK_Click(object sender, EventArgs e)
{
    string title = this.Text; // 暂存原窗体标题

    Form2 f = new Form2();
    f.UserName = m_userName;

    this.Text = String.Empty; // 隐藏窗体标题
    f.ShowDialog(); // 显示新的窗体
    this.Text = title; // 恢复窗体标题

    m_userName = f.UserName;
    f.Dispose();
}

  另外一种方法就是在新窗体中完成原窗体的隐藏和恢复:

// Form1
private void btnOK_Click(object sender, EventArgs e)
{
    Form2 f = new Form2(this); // 把原窗体传递给新窗体
    f.UserName = m_userName;
    f.ShowDialog();
    m_userName = f.UserName;
    f.Dispose();
}

// Form2
class Form2 : Form
{
    private Form m_prevForm; // 用来存放原窗体
    private string m_prevFormTitle; // 用来暂存原窗体标题

    // 取消默认构造器,换一个带参数的
    public Form2(Form prevForm)
    {
        // 暂存原窗体及其标题
        m_prevForm = prevForm;
        m_prevFormTitle = prevForm.Text;

        // 隐藏原窗体标题
        prevForm.Text = String.Empty;

        // 添加Closing事件处理器,用来完成恢复动作
        this.Closing += new CancelEventHandler(m_onClosing);

        // 执行其他初始化
        InitializeCompenents();
        ...
    }

    // 处理Closing事件,恢复原窗体标题
    private void m_onClosing(object sender, CancelEventArgs e)
    {
        m_prevForm.Text = m_prevFormTitle;
    }
}

  很明显,第一方方法要简洁得多,而且不需要添加私有域,仅使用一个局部变量即可完成。同时,这种方法还可以避免一些问题,比如当新窗体异常关闭时(如用户强行中止了该窗体等),可能触发不到Closing事件。这种方法还可以避免窗体标题的丢失。

  但第一种方法仅适用于使用ShowDialog方法的情况,如果用Show方法启动一个新的窗体,就不得不使用第二种方法,并对异常情况要做更多的考虑了。

  注意,尽管Smartphone设备上的Mobile系统不提供察看当前运行的程序的功能,但仍有很多第三方软件可以完成这一功能,因此也必须考虑这种情况。

小结

  本文针对在窗体间进行切换时可以使用的一些方法和应该考虑的问题。值得注意的是,一定把窗体导航关系的设计作为整个设计过程中重要的一步来对待;另外一定要在使用完一个窗体后立即调用其Dispose方法释放其占用的资源。

 

 

实现方法:

在C#的WinForm中

有三个窗体

      窗体frmOne(按钮btnOneOK)      
      窗体frmTwo(按钮btnTwoOK)
      窗体frmThree(按钮btnThreeExit)

frmOne载入点击按钮btnOneOK时     把frmOne的属性   ShowInTaskbar   设置为   false
然后frmTwo.show();
frmTwo载入以后
点击btnTwoOK时激活frmThree窗体
这时如果点击按钮   btnTreeExit   时   用frmOne.ActiveForm.Close()   方法
就可以关闭整个应用程序了

 

1、Form的显示问题。由于在CE上运行的是简化版的.NET Framework,上面不仅少了很多的东西,而且因为被过度简化而出现了一些Winform上没有的问题,这些问题当中最容易困惑刚接触CF的人的就是多个Form如何正常显示和隐藏。
   在Winform下面,Form1.Show()就能够将Form1显示出来,但是在CF下面就不一样了。在CF下面,这段代码会让你的程序“消失掉”:
   // this = Form1
   Form2 f2 = new Form2();
   this.Hide();
   f2.Show();
如此简单的代码,你也许会认为应该是显示出一个f2,而事实上却是出现了系统桌面或者另外一个不相关的程序界面。仔细研究了一下,发现只要你的程序转到后台之后,你就不可能通过代码将你的程序中任意一个窗口显示出来(API除外),因为这个Show在CF当中的MSIL仅仅改变了某一个窗口的“可视状态”而没有重新设置程序焦点的步骤。而上面这个代码片断首先将自己隐藏了,这个时候你的程序中所有的界面都已经被隐藏了,因此焦点自然不在这个程序上了,后面的f2.Show又没有夺回焦点,因此还是没有被显示出来。
   那么怎么半呢?通过API?那是可以的,不过这样的话就比较麻烦了:除了需要写出API的声明,还要在每次调用Show的时候额外调用这个API。实际上那个API存不存在我没有研究过,也许是向窗口发送一个什么消息吧。事实上要解决这个问题并不难,关键就是要避免焦点的丢失,想上面的那个代码就应该改成这样:
   // this = Form1
   Form2 f2 = new Form2();
   f2.Show();
   this.Hide();
需要注意的是,这个问题对于ShowDialog的窗口也同样存在,比如:
   // this = Form1
   Form2 f2 = new Form2();
   f2.ShowDialog();
   this.Show();

   // this = Form2,f1 = Form1
   private void Form2_Load(...)
   {
      f1.Hide();
   }
因为只有当Dialog被隐藏了之后才会执行ShowDialog下面的一句话,因此实际上的顺序就和
   f2.Hide();
   this.Show();
一样了。处理的办法只能够是在f2隐藏之前先把原来的窗口显示出来,比如:
   // this = Form1
   Form2 f2 = new Form2();
   f2.ShowDialog();

   // this = Form2, f1 = Form1
   private void Form2_Load(...)
   {
      f1.Hide();
   }
   private void Form2_Closing(...)
   {
      f1.Show();
   }
   private void ButtonOK_Click(...)
   {
      f1.Show();
      this.ShowDialog = DialogResult.OK;
      this.Hide();
   }
2、Form的外观问题:
   Form的外观控制是另外一个非常困惑初次接触者的问题。这里的外观控制指的是如何控制只显示一个小窗口、如何全屏、如何点击右上角就立即关闭而不是隐藏等等。

   小窗口  这里指的是如同Winform那样,只占据屏幕一部分而不是全部的那种显示模式。方法是将FormBorderStyle调整为None,这意味着没有标题栏,也就是不能够移动。如果你希望有标题栏,能够移动,看来只能够你自己去实现了。
   全屏    这里指的是连标题栏都不想要了,实现的方法是:
           1.关掉SIP
           2.将Form的Menu设为null
           3.将Form的WindowState设为Maximize.
           注意,这个必须在这个Form已经显示出来之后再设才能够有效。
   点击右上角就立即关闭而不是隐藏
           这个比较简单,只要将MinimizeBox设为False就可以了。

3. SIP的问题
   Sip就是输入板,比如什么拼音啦、全屏手写什么的。这个东西非常讨厌,整个开发当中都会不断的出现和她有关的问题。总结了一下,主要需要注意以下几个方面:
   1、Sip似乎是全局的,Form1代码里面将Sip.Enabled = false,然后Form2代码里面将Sip.Enabled = true,这个时候显示的是Form1,你不要以为那个Sip就没有显示出来,实际上是显示出来的。于是很多时候会出现这样的问题:你显示着的窗口关闭了SIP,但是某些用户操作触动了这个窗口的代码,而这个代码又触动了另外一个隐藏着的窗口的代码,而这个代码却将Sip打开了。于是你在调试的时候就会非常的郁闷:我的代码明明没有打开SIP,为是么会突然就自动打开了呢?调是的时候很容易就忽略了那些“无关的”代码,与是很难调试出来。
   2、Sip和MainMenu是关联的。如果一个Form没有设置Menu属性为一个有效值,那么一旦调用sip.Enabled = true就会抛出一个异常。一个比较容易因为没有注意到而出现的情况是,如果你想要将窗口全屏,那么就必须要将Menu去掉,如果这时候sip.Enabled = true……
   3、Sip不会自动判断搜属的Form是否已经被抛弃了(Disposed)。同时由于Form在Close之后不是马上,甚至根本就不会调用Dispose(),于是Sip在Form事实上已经Disposed的情况下并没有被Dispose,甚至关联在这个Sip上面的事件也不会被解除。下面是一个出错的例子:

   // this = Form1
   Form2 f2 = new Form2();
   f2.Close();
   sip.Enabled = true;
   sip.Enabled = false;

   // this = Form2
   ... void InitializeComponents()
   {
   ...
       sip.EnabledChanged += new System.EventHandler(sip_EnabledChanged);
   ...
   }
   ... void sip_EnabledChanged(...)
   {
       this.Visible = true;  // 在这里抛出异常,因为实际上f2已经被关闭了。
   }  

博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3