0
votes

Context

On the network are servers that advertise their names with UDP at regular intervals.

The datagrams come in on port 1967 and contain a string like this:

UiProxy SomeServerMachineName

New entries are added, existing entries are updated and stale entries age out of an observable collection that serves as the ItemsSource of a XAML combo box.

This is the combo box

<ComboBox x:Name="comboBox" ItemsSource="{Binding Directory}" />

and this is the supporting code. Exception handlers wrap everything dangerous but are here omitted for brevity.

public class HostEntry
{
  public string DisplayName { get; set;}
  public HostName HostName { get; set; }
  public DateTime LastUpdate { get; set; }
  public HostEntry(string displayname, HostName hostname)
  {
    DisplayName = displayname;
    HostName = hostname;
    LastUpdate = DateTime.Now;
  }
  public override string ToString()
  {
    return DisplayName;
  }
}

HostEntry _selectedHost;
public HostEntry SelectedHost
{
  get { return _selectedHost; }
  set
  {
    _selectedHost = value;
    UpdateWriter();
  }
}

async UpdateWriter() {
  if (_w != null)
  {
    _w.Dispose();
    _w = null;
    Debug.WriteLine("Disposed of old writer");
  }
  if (SelectedHost != null)
  {
    _w = new DataWriter(await _ds.GetOutputStreamAsync(SelectedHost.HostName, "1967"));
    Debug.WriteLine(string.Format("Created new writer for {0}", SelectedHost));
  }
}

ObservableCollection<HostEntry> _directory = new ObservableCollection<HostEntry>();
public ObservableCollection<HostEntry> Directory
{
  get { return _directory; }
}

private async void _ds_MessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
{
  if (_dispatcher == null) return;
  await _dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
  {
    var dr = args.GetDataReader();
    var raw = dr.ReadString(dr.UnconsumedBufferLength);
    var s = raw.Split();
    if (s[0] == "UiProxy")
    {
      if (_directory.Any(x => x.ToString() == s[1]))
      { //update
        _directory.Single(x => x.ToString() == s[1]).LastUpdate = DateTime.Now;
      }
      else
      { //insert
        _directory.Add(new HostEntry(s[1], args.RemoteAddress));
      }
      var cutoff = DateTime.Now.AddSeconds(-10);
      var stale = _directory.Where(x => x.LastUpdate < cutoff);
      foreach (var item in stale) //delete
        _directory.Remove(item);
    }
  });
}

The collection starts empty.

The UpdateWrite method called from the setter of SelectedHost destroys (if necessary) and creates (if possible) a DataWriter around a DatagramSocket aimed at the address described by the value of SelectedHost.

Goals

Automatically select when a value is added and the list ceases to be empty.

The list can also become empty. When this happens the selection must return to null with a selected index of -1.

As things stand, the list is managed and it is possible to interactively pick a server from the list.

At the moment I am setting SelectedHost like this but I am sure it could be done with binding.

private void comboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
  SelectedHost = comboBox.SelectedItem as HostEntry;
}

The setter method for SelectedHost calls CreateWriter which manages the object used elsewhere to send data to the selected host. I've called this from the setter because it must always happen right after the value changes, and at no other time. It's in a method so it can be async.

I could move it to the SelectionChanged handler but if I do that then how can I guarantee order of execution?

Problem

I get errors when I try to programmatically set the selection of the combo box. I am marshalling onto the UI thread but still things aren't good. What is the right way to do this? I've tried setting SelectedIndex and SelectedValue.

1
What errors are you getting?markmnl

1 Answers

1
votes

I get errors when I try to programmatically set the selection of the combo box.

How are you doing it? In code-behind this should work so long as the collection you are bound to has an item at that index:

myComboBox.SelectedIndex = 4;

but I am sure it could be done with binding

Yes it can, looks like you forgot to implement INotifyPropertyChanged. Also since you are using UWP there is a new improved binding syntax Bind instead of Binding learn more here: https://msdn.microsoft.com/en-us/windows/uwp/data-binding/data-binding-in-depth

<ComboBox x:Name="comboBox" ItemsSource="{Binding Directory}" 
     SelectedItem="{Binding SelectedHost}" />

public event PropertyChangedEventHandler PropertyChanged;

HostEntry _selectedHost;
public HostEntry SelectedHost
{
  get { return _selectedHost; }
  set
  {
    _selectedHost = value;
    RaiseNotifyPropertyChanged();
    // What is this? propertys should not do things like this CreateWriter();
  }
}

// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}