【问题标题】:Accessing class from form and vice-versa从表单访问类,反之亦然
【发布时间】:2018-02-10 11:46:39
【问题描述】:

在网上搜索了两天后,没有找到我能正确理解的解决方案,我不得不在这里寻求答案。

我有一个用 vb.net 编写的 Windows 窗体应用程序,并且工作正常。我决定用 c# 重写它,我认为这不会有太大的问题,但是......

我在项目中有两个类:

FormJobs 和 AppJobs

FormJobs 包含以某种方式修改表单的方法和函数。

AppJobs 包含其他一切的方法和函数(检查、扫描等)。

在我的主窗体 (FrmStart) 上,On_load 事件使用 AppJobs 中的函数来检查网络是否已启动 (public bool CheckNetConnection),然后检查以确保根保存文件夹存在 (public void CheckRoot)。

如果 CheckNetConnection 为 false 或 CheckRoot 不存在,则 FormJobs 类中的方法将一些按钮设置为禁用,一些标签用于显示错误信息,并设置表单的高度。

以上方法在 VB.net 中有效,但我在 C# 代码中不断收到 StackOverflowException 或 NullReferenceException。

我知道异常的原因是因为这两个类和表单都一直互相调用,所以我知道我需要删除这段代码,但我不确定如何让每个类和表单相互访问。这显然是糟糕的设计,因为我刚刚开始学习 C#,所以对此的任何帮助将不胜感激。

但我的主要问题是:-如何获得访问多个类的表单? 允许类相互访问? 让类对表单进行更改?

FrmStart 代码

AppJobs Appjobs = new AppJobs();

private void FrmStart_Load(object sender, EventArgs e)
    {

                    KeyPreview = true;

        if (Appjobs.CheckNetConnection(this) == true)
        {
            Appjobs.CheckRoot(this);
        }

AppJobs 代码

public class AppJobs
{

    FormJobs Formjobs = new FormJobs();

    public string AppRoot = Properties.Settings.Default.DefaultFolder;
    public string DefaultDevice = Properties.Settings.Default.DefaultScanner;
    public bool NoDirectory = false;

    DialogResult MsgBoxQuestion;

    public bool CheckNetConnection(Form StartForm)
    {

        IPHostEntry ServerIP = new IPHostEntry();
        bool ConnectedToServer = false;
        string CurrentRoot = "MyServer";

        if (System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable())
        {
            try
            {
                IPHostEntry DNSTest = Dns.GetHostEntry(CurrentRoot);
                if (DNSTest.AddressList.Length > 0)
                {
                    ConnectedToServer = true;
                }
                else
                {
                    ConnectedToServer = false;

                }


            }
            catch (System.Net.Sockets.SocketException ex)
            {
                ConnectedToServer = false;
            }
        }

        return ConnectedToServer;

    }

    public void CheckRoot(Form StartForm)
    {
        if (string.IsNullOrEmpty(AppRoot))
        {
            Formjobs.SetHeight(StartForm);
            return;


        }else if(AppRoot == "0")
        {
            Formjobs.SetHeight(StartForm);
            return;
        }
        else
        {
            if ((!Directory.Exists(AppRoot)))
            {
                NoDirectory = true;
                MsgBoxQuestion = MessageBox.Show(AppRoot + " is set, but the directory does not exist." + Environment.NewLine
                    + Environment.NewLine + "Would you like to create the folder now?", "Root folder missing", MessageBoxButtons.YesNo);
                if (MsgBoxQuestion == DialogResult.Yes)
                {
                    Directory.CreateDirectory(AppRoot);
                    NoDirectory = false;
                }
                else
                {
                    MessageBox.Show("You will not be able to use this program until you create a root folder.", "No root folder selected",MessageBoxButtons.OK);
                }

            }

        }

    }
}

FormJobs 代码

public class FormJobs
{

    AppJobs Appjobs = new AppJobs();

    public void SetHeight(Form StartForm)
    {

        if (Appjobs.AppRoot == null | Appjobs.AppRoot == "0") {

if (Appjobs.DefaultDevice == null | Appjobs.DefaultDevice == "0") {

    if (StartForm.Controls["MenuStrip1"].Visible == true) {
        StartForm.Height = 167;
        StartForm.Controls["LblNoRoot"].Visible = true;
        StartForm.Controls["LblNoRoot"].Location = new Point(0, 24);
        StartForm.Controls["LblNoRoot"].Text = "There is no root folder selected. Please select a root folder to continue.";
        StartForm.Controls["LblNoDevice"].Visible = true;
        StartForm.Controls["LblNoDevice"].Location = new Point(0, 48);
        StartForm.Controls["LblNoDevice"].Text = "There is no default device selected. Please select a default device to continue.";
        StartForm.Controls["BtnOkTickets"].Enabled = false;
        StartForm.Controls["BtnQueryTickets"].Enabled = false;
        StartForm.Controls["BtnSearch"].Enabled = false;

    }else

        {
        StartForm.Height = 147;
        StartForm.Controls["LblNoRoot"].Visible = true;
        StartForm.Controls["LblNoRoot"].Location = new Point(0, 9);
        StartForm.Controls["LblNoRoot"].Text = "There is no root folder selected. Please select a root folder to continue.";
        StartForm.Controls["LblNoDevice"].Visible = true;
        StartForm.Controls["LblNoDevice"].Location = new Point(0, 33);
        StartForm.Controls["LblNoDevice"].Text = "There is no default device selected. Please select a default device to continue.";
        StartForm.Controls["BtnOkTickets"].Enabled = false;
        StartForm.Controls["BtnQueryTickets"].Enabled = false;
        StartForm.Controls["BtnSearch"].Enabled = false;

        }


}

【问题讨论】:

  • 请与我们分享您不工作的 c# 代码。我非常确定错误出在您的实现中,而不是您在此处为我们描述的逻辑中。
  • 不看代码就无法修复代码。至于总体布局,我不会让FormJobs 更改 表单,而是公开一个状态(例如bool DisableAllButtons),并拥有每个表单(在创建时,或在接收到状态改变)观察暴露的状态并相应地调整自身。随意更改类彼此是几乎无法维护的代码的温床。让类/表单根据其环境自行更改更易于管理。
  • 向问题添加代码
  • 好吧仍然是一个的方法来回答一个可回答的问题...哪些异常位于哪里?现在我们可以看到您的代码,您可以删除上面的大部分纯文本

标签: c# winforms visual-studio


【解决方案1】:

您的问题的原因之一是每个人都在更改您的StartForm。除此之外,如果您的Startform 发生更改,这种意大利面条会让人难以理解,这当然无助于使您的类可重用和可维护。

在我看来,AppJobs 旨在决定表单的外观(例如,它决定StartForm 应该改变高度),而FormJobs 执行改变这个高度所需的计算。 StartForm 显然只是允许大家对他进行更改。

更好的设计是StartForm 不会要求AppJobs 改变它的大小,并询问操作员是否应该生成一个文件夹。相反,如果应该向appJobs 寻求建议:“你认为我应该有什么高度”。之后它可以询问 FormJobs:“请根据这个规范调整我的身高”

FormJobs 应该相信StartForm 它已经收集了关于StartForm 应该是什么样子的正确信息。 FormJobs 不应该向 AppJobs 询问任何信息:“嘿 AppJobs,StartForm 要求我将其外观更改为某些规范,但我不确定 StartForm 是否正确完成了它的工作。请告诉我这些规范是否正确,并给我一些遗漏的信息”)

正确的任务划分是:

  • AppJobs 根据其内部状态(a.o. AppRoot,以及某些文件夹的存在)指定任何 StartForm 的格式
  • StartForm 是显示所有项目的人。他决定向谁索取规范,以及如何处理返回的规范。他也是唯一与运营商沟通的人
  • FormJobs 是一个显然知道来自StartForm 的所有元素的类。如果您只有一种类型的StartForm,那么Appjobs 应该是Startform 类的一部分。如果您认为可能存在多个不同的 Startform 类,它们都具有相同的元素,并且应该进行类似的操作,那么这些 StartForm 类不应该都派生自 FormJobs 类吗?

无论如何,重新设计不要让每个人都操纵StartForm

显然,StartForm 布局的数量有限,具体取决于 AppRoot、defaultDevice 等。您似乎在 if 之后缺少一些“else”,因此此列表可能不准确。你还是会明白的:

enum StartFormLayouts
{
    DefaultDevice0,
    AppRoot0,
    Other,        
}

// class that specifies the layout of any startform
class AppJobs
{
    private bool IsAppRoot0 
    {
        get{return Appjobs.AppRoot == null || Appjobs.AppRoot == "0";}
    }
    private bool IsDefaultDevice0
    {
        get{return Appjobs.DefaultDevice == null || Appjobs.DefaultDevice == "0";}
    }

    public StartFormLayoug GetPreferredLayout()
    {
         if (this.IsAppRoot0)
         {
             if (this.IsDefaultDevice)
             {
                  return StartFormLayout.DefaultDevice0;
             }
             else
                  return StartFormLayout.AppRoot0;
          }
          else
          {
              return StartFormLayout.Other;
          }
    }

    public bool ShouldAskDirectoryCreation()
    {
        return (!this.IsAppRoot0 && !Directory.Exists(AppRoot));
    }
}

请注意,此类不需要 StartForm,也不需要 AppJobs。它可以与任何想知道它是否应该请求 DirectoryCreation 的类一起工作。由于它也不会说任何语言,即使是中国人StartForm也可以使用它。毕竟,StartForm 是唯一一个知道它说什么语言以及在请求特定布局时该怎么做的人。

此外,您是否注意到我使用双精度 || 来使用布尔 OR 而不是按位或?

我使用 if (a) 之类的语句而不是 if(a=true) 一个 C# 布尔值是一个真正的布尔值,这与 C 和 C++ 中的布尔值相矛盾。

应能按要求布局的各类表单类,包含与你类似的功能

这取决于您是决定让它成为StartFormStartForm 本身的基类。如果您希望它处理具有所需控件的每个表单类,请考虑使用接口:

public Interface IStartForm
{
    public int Height {get; set;}
    public Label LabelNoRoot {get;}
    public Label LabelNoDevice {get; }
    public Button BtnTickets {get;}
    ...

通过这种方式,您可以设置具有这些标签和按钮的任何表单的大小,即使它们的名称与您使用的字符串不同。

但同样:如果您只想调整StartForm 的大小,那么这应该是StartForm 中的一个函数。

public SetHeight(StartFormLayout layout, IStartForm startForm)
{
    switch (layout)
    {
        case StartFormLayout.DefaultDevice0:
            if (startForm.MenuStrip.Visible)
            {
                startForm.Height = ...;
                startForm.LabelNoRoot.Location = ...
                // etc
            }
            else
            {
               ...

注意到由于这种关注点分离,AppJobs 和 FormJobs 不必相互了解。 AppJobs 和 FormJobs 也不必真正知道“StartForm”是什么,只需知道它具有需要更改的标签和按钮等。

class StartForm : Form, IStartForm
{
    public Label LabelNoRoot {get{return this.label1; } }
    ...

    private void FrmStart_Load(object sender, EventArgs e)
    {
        AppJobs layoutdesigner = new AppJobs(...);
        StartFormLayout layoutdesigner = layouter.GetPreferredLayout();

        FormJobs layouter = new FormJobjs();
        layouter.SetHeight(this)
    }

注意到我的表单没有名为“LabelNoRoot”的标签,而是应该用作 LabelNoRoot 的 Label1。另外:因为我使用类型而不是字符串,所以您可以确定我不能像处理按钮一样处理标签。我不能不小心按标签。当您使用字符串来识别要布局的项目时,可以轻松完成一些事情。

【讨论】:

  • 感谢您的完整解释。它以及下面 Pio 的回答让我按照这里的建议重新设计了应用程序。我现在已将所有内容移入 AppJobs 类并更改了所有代码,以便它可以重用。再次非常感谢所有的帮助
【解决方案2】:

扩展 cmets:您只需删除 FormJobs 和 AppJobs 类中的 new 部分。 将代码保留在 FormJobs 类中,例如:AppJobs appObj;
然后在您的主表单中创建一个 FormJobs obj 和一个 AppJobs obj 并设置其属性。
IE。在主窗体中:

AppJobs appObj = new AppJobs(); 
FormJobs formObj = new FormJobs(); 
formObj.appObj = appObj;

虽然我必须说我不喜欢你对这个采取的那种方法......

您应该考虑另一种方式,或者至少重构您的代码,使 FormJobs 不需要 AppJobs 方法,反之亦然,使所有对 FormJobs 和 AppJobs 的调用都来自您的主窗体。

【讨论】:

    猜你喜欢
    • 2015-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-14
    • 1970-01-01
    • 2012-11-09
    • 2017-01-08
    • 1970-01-01
    相关资源
    最近更新 更多