Mark T
Mark T

Reputation: 3494

C# Separate GUI from Work logic

I'd like to find how to separate my GUI and work code. The contrived example code, below, is the smallest starting point that I can think of that covers the idea.

The example uses a Windows Form as the source of commands and as display (consumer) of results. I want the work code to be capable of getting commands from, say, a command line interface instead. The work code should not depend on knowledge of the Form. The Form should know little about the work code. I'd like to have several consumers "see" when the property in the work code changes value.

I'm guessing this means using events for communication, and perhaps Interfaces as well, but I'm open to anything.

There are a million different suggestions out there. I've read the design pattern books, and I have tried many, and have yet to find a set that is well enough explained that I can fully implement it.

I don't want a universal solution. I want one as simple as possible to implement and maintain for small, personal projects. I'm not designing for a large corporation.

Most solutions I've found will hint at what to do, but not cover the specifics like where an event is declared, and how the other piece of code finds out about the event's existence so it can either issue the event or respond to the event. I always end up needing, somewhere, what amounts to a global variable to hook things together.

The closest match I can find, here, to my question is this: C# Windows Forms App: Separate GUI from Business Logic But the solution uses the form to create an instance of the worker and returns a value directly, rather than informing any interested observers. The provided solution tightly bound the two classes.

The closest solution I've found anywhere is this: https://www.codeproject.com/Articles/14660/WinForms-Model-View-Presenter which does some really cool work with interfaces and reflection, but didn't seem too maintainable nor flexible.

The comment lines in the source code below show the desired interaction points but without the implementation.

File #1:

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

    private void button1_Click(object sender, EventArgs e)
    {
        // tell an instance of JustCounts to increment by 10
    }

    // Here, allow JustCounts to cause a call to this (or something
    // similar, perhaps a property) to inform this code that the TotalCount
    // property has changed.
    public void ShowNewTotalCount(int NewTotal)
    {
        Console.WriteLine("New Total Count = {0}", NewTotal);
    }
}

File #2

class JustCounts
{
    private int m_TotalCount = 100;
    // Inform other classes when the following property changes value,
    // preferably including the value in the notification.
    public int TotalCount { get => m_TotalCount; }

    // The code in File #1 needs to result in a call to this method
    // along with the correct argument value.
    public void AddThisMuch(int increment)
    {
        m_TotalCount += increment;
    }
}

Upvotes: 2

Views: 932

Answers (1)

Barry O'Kane
Barry O'Kane

Reputation: 1187

I'm basing this on the current version of .Net (4.6.2).

If we implement INotifyPropertyChanged then we have an event that we can listen to for property changes. A bit like listening for key presses, we can filter then for the specific property that we want.

public class JustCounts : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private int m_totalCount = 100;

    public int TotalCount
    {
        get { return m_totalCount; }
        set
        {
            if (value != m_totalCount)
            {
                m_totalCount = value;
                NotifyPropertyChanged();
            }
        }
    }
}

There's no need to create a method to manipulate the TotalCount property as we're exposing it.

public class Form1 : Form
{
    // justCounts is a reference to the object wherever it is coming from
    justCounts.PropertyChanged += new PropertyChangedEventHandler(JustCountsChangedHandler);


    private void JustCountsChangedHandler(object sender, PropertyChangingEventArgs e)
    {
        // process on event firing
        Debug.WriteLine($"justCounts TotalCount changed value to {justCounts.TotalCount}");
    }

    // Example of where the handler will fire when called
    private void button1_click(object sender, EventArgs e)
    {
        justCounts.TotalCount++;
    }
}

In the code above, we've created an event in JustCounts to which listeners can subscribe.

Using the INotifyPropertyChanged interface, we fire the event each time TotalCount is changed.

In form 1 we create the handler to listen for property changes, and the handler then carries out any processing.

One note. You say

I'd like to have several consumers "see" when the property in the work code changes value

so in order for this to work we have to assume that the work code can run independently of it's subscriber (something like a server). Otherwise, we'd have different instances for different subscribers.

You also mention interfaces, and they could be used but are not necessary in this instance.

Upvotes: 1

Related Questions