views:

270

answers:

2

I have created a small chatting application in C#, and started as a Console Application. However I want to create a GUI for it using WPF. It's a class named DanMessengerClientwith functions such as InitializeConnection(), SendMessage(string msg), etc.

I have already designed the UI in Visual Studio, and it created it's Window1 class on Window1.xaml.cs by default. I created an event handler for the "Send" button that only appends some dummy text to a textarea as of now. My question is, how should I call the SendMessage() function from the WIndow1 class?

I tried creating the object in that class, but since I also need to access the Textbox from inside the first class (i.e. When I recieve a message, update the textbox), adding the reference to the Window1 class throws a StackOverflow exception because it keeps creating references in an infinite loop.

I'm new to GUI applications. How should I proceed?

+2  A: 

The canonical way in WPF to display data is to bind a control to it (see Data Binding in MSDN). This would probably require that you wrap or refactor your messenger class so that it exposes bindable properties. For example, your messenger class might expose a property called MessageText, which you update every time you receive a message:

// INotifyPropertyChanged interface implementation and plumbing

public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged(string propertyName)
{
  if (PropertyChanged != null)
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}

// The property you are going to bind to

private string _messageText = String.Empty;

public string MessageText
{
  get { return _messageText; }
  set
  {
    _messageText = value;
    OnPropertyChanged("MessageText");
  }
}

// How to modify your code to update the bindable property

private void OnMessageReceive(string message) // assuming this method already exists
{
  MessageText = MessageText + Environment.NewLine + message;
}

Now you would bind the TextBox.Text property to this new property:

<TextBox Text="{Binding MessageText, Mode=OneWay}" />

This assumes that the messenger object is set as the window's DataContext, e.g. when the window creates the messenger:

public class Window1()
{
  _myMessenger =  = new DanMessengerClient();
  this.DataContext = _myMessenger;
}

Note your messenger class must implement INotifyPropertyChanged for this to work. Also note the OneWay binding so that if the user edits the TextBox it doesn't muck up the MessageText property. (You could also use a TextBlock, so that the user couldn't edit it at all.)

When you've got this set up, WPF will automatically monitor for changes in the _myMessenger.MessageText property, and update the TextBox.Text as they happen (i.e. as messages are received).

Finally, regarding how to do the send: just pass the text:

private void SendButton_Click(...)
{
  _myMessenger.Send(MyTextBox.Text);
}

Use the Name attribute to name the text box containing the message to be sent:

<TextBox Name="MyTextBox" />
itowlson
The Class is very small, I don't have any problem with editing it. I don't understand however how binding could help me. Could you show me an example on how to append a string to the textbox using that?
Daniel S
I assume you already have code in the class to do stuff when a message is received, right? So create a new property, say MessageText, initially an empty string. Now, in your receive code, append the received text to MessageText, and raise the PropertyChanged event for MessageText. That completes the changes to the class. Now in your XAML you set the TextBox binding, and your window DataContext, as shown above. Now whenever your class updates MessageText, WPF will notice the PropertyChanged event and automatically update the TextBox for you. I'll edit to show more details.
itowlson
Okay, I've updated the code. Note that I am not appending the string to the TextBox. I am appending it to a property *in the data model*, and the text box is automagically reflecting that. This is idiomatic WPF and gets around the need for _myMessenger to have a reference to Window1 or the TextBox.
itowlson
Okay, I've understood that. However, where should I initialize _myMessenger? If I put it in the namespace constructor, I get a NullReferenceException for some reason (I can't tell why)
Daniel S
I'm not sure what you mean by a "namespace constructor." Initialise it in the Window1 instance constructor, as shown in the fragment above. If you still get the NullReferenceException, you can step through in the debugger, or examine the stack trace, to find out why it's happening.
itowlson
Sorry, I got confused about that. I'll try using your updated code.
Daniel S
Okay, didn't really manage to get it working (I will though) but that is the correct way to resolve my problem. Thank you!
Daniel S
A: 

Just to expand a bit on what itowlson is saying:

I'd start by creating an interface like this:

interface IMessagingClient
{
    string MessageToSend { get; set; }
    string MessageBuffer { get; }
    void SendMessage();
}

The behavior of classes implementing this interface should be pretty simple. When a message is received, it gets added to MessageBuffer (probably preceded by a newline). To send a message, set MessageToSend and call SendMessage(), which also adds the sent message to MessageBuffer. There are a lot of implementation details I'm skipping to keep this simple.

Next, I'd build a test class that implemented IMessagingClient and INotifyPropertyChanged. This class doesn't actually have to do anything real: it would most likely just produce random test messages at random intervals and update MessageBuffer; also, when SendMessage is called, it would clear MessageToSend and update MessageBuffer.

I'd then add code to my window to create an instance of this class and set the DataContext to it. I'd bind my outbound TextBox to MessageToSend and my inbound TextBlock to MessageBuffer, and hook up a Button to call SendMessage.

Once I got the UI working against my test object, I'd build a another class that implemented the same interfaces, only this one would create a private DanMessengerClient object that the property setters interoperated with. Then I'd make my window create an instance of this object instead.

An actual messaging client is probably going to need to be more sophisticated, For instance, you might want to implement a LastMessageReceived property so that you can do something special with that text, like put it in a ToolTip. And the MessageBuffer property might actually need to support rich text in some way. But this is a good starting point.

Robert Rossney