The best way to do this is with data binding.
So we need to first define where our data is coming from. This is called the Context. This is going to come from a ViewModel which is an MVVM term. If you aren't aware of MVVM, don't worry, this can just come from any class you have. In the backend .xaml.cs code we need to add the class to our windows's DataContext. Here's what that looks like:
public partial class DebugView : Window
{
public DebugView()
{
InitializeComponent();
DataContext = new DebugViewModel();
}
}
And in our WPF's XAML file for the window we will have a label and textbox that is defined as such:
<Label Content="{Binding ClientCount, FallbackValue='Clients: 00'}" ... />
<TextBox Text="{Binding Port, UpdateSourceTrigger=PropertyChanged}" ... />
The text of a label is it's "content" while the text of a textbox is just "text." We add the binding keyword in there and now the text for each will be linked to the variables ClientCount
and Port
, repstively. So our DebugViewModel
class will look like this at first:
private string _ClientCount;
public string ClientCount
{
get { return _ClientCount; }
set { _ClientCount= value; RaisePropertyChanged("ClientCount"); }
}
private string _Port;
public string Port
{
get { return _Port; }
set { _Port= value; RaisePropertyChanged("Port"); }
}
Now you don't have a Function called RaisePropertyChanged()
so what I did (and I think is common practice) was I made a base class that implements the INotifyPropertyChanged
and handles all the work there.
So our base class, called BaseViewModel
will inherit from the INotifyPropertyChanged
class, and setup everything for us. That just looks like this (feel free to just copy paste and use as is):
using System.ComponentModel;
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
internal void RaisePropertyChanged(string prop)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
// Other functions we want all ViewModels to have
}
and so then our DebugViewModel
class will look like such:
public class ServerViewModel : BaseViewModel
{
private string _ClientCount;
public string ClientCount
{
get { return _ClientCount; }
set { _ClientCount= value; RaisePropertyChanged("ClientCount"); }
}
private string _Port;
public string Port
{
get { return _Port; }
set { _Port= value; RaisePropertyChanged("Port"); }
}
public DebugViewModel()
{
// Initialize to default values
ClientCount = $"Clients {server.clientCount}";
Port = $"{server.port}";
}
// Rest of code
}
And then when you start your program it will autopopulate the fields and you when you change the data in the textbox, the string will change, and vice versa. The UpdateSourceTrigger=PropertyChanged
part of our XAML declaration makes it so that the variable is updated as soon as the data in the textbox is changed (default behavior is when the textbox loses focus. e.g. you tab to the next textbox or you click away).
This is pretty cool because you can validate input dynamically as it's typed, as well as not having to worry about switching to the UI thread to update the UI, and IMO makes the code look simpler just by having it bound like this.