Calling another forms method/function


Frequently when writing winforms applications, one needs to call a method/function in another form. This is very handy for instance, if you want to refresh the other form after changing values in a database.

After trying to solve this using delegates, I came across this eloquent solution.

Create two Winforms, F1 and F2 with a button in each.

In Form 1

using System;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public void MyMessage()
        {
            MessageBox.Show("Hello from Form 1");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Form2 form2 = new Form2(this);
            form2.ShowDialog(this);
        }
    }
}

In Form 2

using System;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form2 : Form
    {
        Form1 form1;

        public Form2(Form1 form1)
        {
            InitializeComponent();
            this.form1 = form1;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            this.form1.MyMessage();
        }
    }
}

 

The instance of form 1 is passed to the second form using the this keyword. Ultimately what is passed is just a reference (pointer) to the form object.

User Control Closing Event


It is a common requirement when using User Controls to detect that the Parent Form or main form is closing.

Presently, I am working on a distributed smart client application that is full of user controls. Most, if not all of these user controls will be getting data by calling a Windows Communication Foundation service (A good tutorial on WCF is available in an earlier blog post here).

When you instantiate a Service Reference client object, it is imperative that you call client.Close() to free up any resources when the control is closed. As there is no FormClosing event in a winforms UserControl, you will need to override the ParentForm.FormClosing event. In this example I have a windows form with a user control on it. When you close the form the event in the user control is raised.

Note: Visual basic example is below

C#

using System.Windows.Forms;

 

namespace UserControlClosingEvent

{

    public partial class UserControl1 : UserControl

    {

        public UserControl1()

        {

            InitializeComponent();

        }

 

        protected override void OnCreateControl()

        {

            base.OnCreateControl();

            this.ParentForm.FormClosing += new FormClosingEventHandler(ParentForm_FormClosing);

        }

 

        void ParentForm_FormClosing(object sender, FormClosingEventArgs e)

        {

            if (MessageBox.Show("Would you like to close the parent form?", "Close parent form?",

                MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)

            {

                e.Cancel = true;

            }

        }

    }

}

 

Visual Basic

Public Class UserControl1

 

    Protected Overloads Overrides Sub OnCreateControl()

        MyBase.OnCreateControl()

        AddHandler Me.ParentForm.FormClosing, AddressOf ParentForm_FormClosing

    End Sub

 

    Private Sub ParentForm_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs)

        If MessageBox.Show("Would you like to close the parent form?", "Close parent form?", _

                           MessageBoxButtons.YesNo, MessageBoxIcon.Question) = DialogResult.No Then

            e.Cancel = True

        End If

    End Sub

 

 

End Class

You can then call the client.Close() (or whatever action you desire) instead of MessageBox.Show() in ParentForm_FormClosing event

Windows Communication Foundation Support in Visual Studio 2008


Recently I was having major problems designing a smart client application because the architecture necessitated a n-tier (or multi tier) paradigm. This "Architecture" was non-negotiable insofar as the smart client being for a relatively successful business. The advantages of this type of architecture are well documented so feel free to Google search on the subject.

The following tutorial will take you through the steps required to create an n-tier application using Visual Studio 2008.

  1. Create a new windows application called NorthwindTraders
  2. In the same solution, add a new class library called Northwind.DataAccessLayer (Delete the Class1.cs file in this project)
  3. Add another class library called Northwind.BusinessEntities (Delete the Class1.cs file in this project)
  4. Add a WCF Service Application called WcfService1 (the default). Make sure you choose the WCF service under the web options shown below

Service

Your solution explorer should have the following four projects.

SolutionExplorer

In the DataAccessLayer project add a DataSet. To do this, make sure the DataAccessLayer project is selected in Solution Explorer, and from the main menu choose "add new data source". Make sure Database is selected and click “Next”.

Note:  I’m assuming you have downloaded and installed the Northwind database, if not you can get the ubiquitous database here.

AddNewDataSource

If you don’t already have a connection to Northwind, click the “New Connection” button and select the SQL database file

DatabaseFile

You should now be able to browse to the database, and test to make sure the connection is working

BrowsetoDB

Click on “Next”, if you get the following dialog, saying yes makes a copy of the Northwind Database to your project. I usually say  no because you ens up with dozens if not hundreds of copies of the same database in your Visual Studio Projects folders, this was you always use the same database in all your projects.

CopyDBMessage

Save the connection as “NorthwindConnectionString” and Click “Next”. Choose the “Customers” and “Orders” tables, and Change the name of the DataSet to “CustomersDataSet” and click on “Finish”.

CustomersDataSet

”Choose the default database option and click next. Select the Northwind database and then choose the customers and orders tables. Change the name of the DataSet to "CustomerOrdersDataSet" and click finish.

Double click the “CustomersDataSet” in Solution Explorer and right click on the dataset and select properties

DataSetProperties

In the properties pane you can now choose the DataSet project for the data access layer. This is a new feature in Visual Studio 2008 that allows you to separate you data access logic and your business logic into two projects, which was a real pain point in Visual Studio 2005. In this case choose the Northwind.BusinessEntities project and make sure you click “Save”.

ChooseDataBaseProject

If you double click the orders table in the DataSet designer, you should be taken into the business entities project, where you can add some validation logic.

namespace Northwind.BusinessEntities {

 

 

    public partial class CustomersDataSet {

        partial class OrdersDataTable

        {

            public override void EndInit()

            {

                base.EndInit();

                this.ColumnChanged += new System.Data.DataColumnChangeEventHandler(OrdersDataTable_ColumnChanged);

            }

 

            void OrdersDataTable_ColumnChanged(object sender, System.Data.DataColumnChangeEventArgs e)

            {

                if (e.Column.ColumnName == this.OrderDateColumn.ColumnName

                    || e.Column.ColumnName == this.ShippedDateColumn.ColumnName)

                {

                    VaidateDates((OrdersRow)e.Row);

                }

            }

            /// <summary>

            /// Simple validation to ensure that the date an order is shipped is not

            /// before it has been ordered

            /// </summary>

            /// <param name="ordersRow"></param>

            private void VaidateDates(OrdersRow ordersRow)

            {

                if (ordersRow.OrderDate > ordersRow.ShippedDate)

                {

                    ordersRow.SetColumnError(this.OrderDateColumn, "Cannot ship before it’s been ordered");

                    ordersRow.SetColumnError(this.ShippedDateColumn, "Cannot ship before it’s been ordered");

                }

                else

                {

                    ordersRow.SetColumnError(this.OrderDateColumn, "");

                    ordersRow.SetColumnError(this.ShippedDateColumn, "");

                }

            }

        }

    }

}

We can now complete the setup by adding a class into the data access layer called the NorthwindDataManager that connects to the database, and returns the tables. The NorthwindManager will typically contain all the queries that you create. If you need to know how to create queries you can have a look at this article, or do some research. You typically you change the query to GetOrders and not GetData, but some knowledge of this is assumed.

Queries

This is the code that returns a DataTable in the NorthwindDataManager. Make sure you include the Using statements at the top

 

using System;

using System.Collections.Generic;

using System.Linq;

using Northwind.BusinessEntities;

using System.Text;

using Northwind.DataAccessLayer.CustomersDataSetTableAdapters;

 

 

namespace Northwind.DataAccessLayer

{

    /// <summary>

    /// Typically this class would not return all the data, but specific queries related to a table.

    /// For the sake of brevity, we will return all the orders in the northwind database. Also try

    /// change the GetData() to GetOrders() in the OrdersTableAdapter by modifying the query in the

    /// DataSetDesigner. Knowledge of doing this is assumed.

    /// </summary>

    public class NorthwindDataManager

    {

        public static CustomersDataSet.OrdersDataTable GetOrders()

        {

            OrdersTableAdapter ordersAdapter = new OrdersTableAdapter();

            return ordersAdapter.GetData();

        }

    }

 

}

Now we will configure the service. Select the WCF service project, right click the “References” node in Solution Explorer, and go “Add References.” choose the projects tab at the top and add the DataAccessLayer and BusinessEntities projects.

AddReferences

You also need to add a reference to System.Data.DataSetExtensions so add a reference as above, but this time choose the .NET tab

DataSetExtensions

So your solution explorer should look like this

References

In the IService interface file, remove the default entries and add a contract called GetOrders (not forgetting the namespaces at the top)

using System;

using System.Collections.Generic;

using System.Linq;

using System.Runtime.Serialization;

using System.ServiceModel;

using System.Text;

using Northwind.BusinessEntities;

 

namespace WcfService1

{

    // NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in Web.config.

    [ServiceContract]

    public interface IService1

    {

        [OperationContract]

        CustomersDataSet.OrdersDataTable GetOrders();

    }

}

In the Service1.scv file remove the default data and implement the IServive1 interface. A quick shortcut to do this is to right click the IService1 in Visual Studio shown below (this can save quite a lot of typing when setting up big service layers)

ImplementInterface

Add the following code (again note the namespaces above)

using System;

using System.Collections.Generic;

using System.Linq;

using System.Runtime.Serialization;

using System.ServiceModel;

using System.Text;

using Northwind.DataAccessLayer;

using Northwind.BusinessEntities;

 

namespace WcfService1

{

    // NOTE: If you change the class name "Service1" here, you must also update the reference to "Service1" in Web.config and in the associated .svc file.

    public class Service1 : IService1

    {

 

        #region IService1 Members

 

        public CustomersDataSet.OrdersDataTable GetOrders()

        {

            return NorthwindDataManager.GetOrders();

        }

 

        #endregion

 

    }

}

To complete the tutorial go to the NorthwindTraders project, which typically is your client application and make sure you add a reference to the business entities project. This is important because it allows proxy reuse (another VS 2008 Key feature). You should also see here that at no stage is the data access component ever available or referenced on the client. Make sure you build the entire Solution now by clicking F5.

Right click the references node and add a service reference this time.

AddServiceReference

Click the discover button and wait till the reference is located and then click OK

ServiceReference

Note: A word of warning is that if you use third party software libraries, than you do not want their libraries included here. Be sure to click the advanced button, and choose “Reuse types in specified referenced assemblies” and omit them. Failure to do this may result in some rather nasty Visual Studio crashes.

In the app.config, as a security precaution, the client configuration we generate has a default MaxRecievedMessageSize value of 65536, meaning that an exception will be thrown at runtime if the service sends more data to the client than it expects. Since we know we will be passing large DataSets over the wire, please open up the app.config and set the element <bindings>\<wsHttpBinding>\<binding>’s maxReceivedMessageSize attribute to 5000000. Or, if you feel uncomfortable editing your service’s configuration by hand, you can open up the Microsoft Service Configuration Editor available under Visual Studio’s Tools menu.

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

    <system.serviceModel>

        <bindings>

            <wsHttpBinding>

                <binding name="WSHttpBinding_IService1" closeTimeout="00:01:00"

                    openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"

                    bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"

                    maxBufferPoolSize="524288" maxReceivedMessageSize="5000000"

                    messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"

                    allowCookies="false">

                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"

                        maxBytesPerRead="4096" maxNameTableCharCount="16384" />

                    <reliableSession ordered="true" inactivityTimeout="00:10:00"

                        enabled="false" />

                    <security mode="Message">

                        <transport clientCredentialType="Windows" proxyCredentialType="None"

                            realm="" />

                        <message clientCredentialType="Windows" negotiateServiceCredential="true"

                            algorithmSuite="Default" establishSecurityContext="true" />

                    </security>

                </binding>

            </wsHttpBinding>

        </bindings>

        <client>

            <endpoint address="http://localhost:2429/Service1.svc" binding="wsHttpBinding"

                bindingConfiguration="WSHttpBinding_IService1" contract="ServiceReference1.IService1"

                name="WSHttpBinding_IService1">

                <identity>

                    <dns value="localhost" />

                </identity>

            </endpoint>

        </client>

    </system.serviceModel>

</configuration>

Now that our service has been added and configured correctly, let’s do some data binding to the data tables returned from our service. Open up the Data Sources window and drag and drop the Orders table onto Form1.

DragData

Finally, the last thing we need to do is add code to Form1’s OnLoad event to get data from our WCF service.

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

 

 

namespace NorthwindTraders

{

    public partial class Form1 : Form

    {       

        private ServiceReference1.Service1Client serviceClient;

        public Form1()

        {

            InitializeComponent();

            this.serviceClient = new NorthwindTraders.ServiceReference1.Service1Client();

        }

 

        private void Form1_Load(object sender, EventArgs e)

        {

          this.customersDataSet.Merge(this.serviceClient.GetOrders());

        }

 

        // Being a good citizen, always close a service connection

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)

        {

            this.serviceClient.Close();

        }

    }

}

As you can see when the form is loaded and the dates changed, the validation occurs in a library separate from the client.

validation