Skip to content

Commit

Permalink
Add combo box and list box for dynamic collections
Browse files Browse the repository at this point in the history
  • Loading branch information
glopesdev committed Mar 11, 2024
1 parent 78680d5 commit 3de1c23
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 0 deletions.
14 changes: 14 additions & 0 deletions src/Bonsai.Gui/ComboBoxDataSourceBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.ComponentModel;

namespace Bonsai.Gui
{
/// <summary>
/// Represents an operator that interfaces with a combo box control bound to each data
/// source in the sequence and generates notifications whenever the selection changes.
/// </summary>
[TypeVisualizer(typeof(ComboBoxDataSourceVisualizer))]
[Description("Interfaces with a combo box control bound to each data source in the sequence and generates notifications whenever the selection changes.")]
public class ComboBoxDataSourceBuilder : ListControlDataSourceBuilderBase
{
}
}
30 changes: 30 additions & 0 deletions src/Bonsai.Gui/ComboBoxDataSourceVisualizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Drawing;
using System.Windows.Forms;

namespace Bonsai.Gui
{
/// <summary>
/// Provides a type visualizer representing a combo box control bound
/// to an arbitrary data source.
/// </summary>
public class ComboBoxDataSourceVisualizer : ControlVisualizerBase<ComboBox, ComboBoxDataSourceBuilder>
{
/// <inheritdoc/>
protected override ComboBox CreateControl(IServiceProvider provider, ComboBoxDataSourceBuilder builder)
{
var comboBox = new ComboBox();
comboBox.Dock = DockStyle.Fill;
comboBox.Size = new Size(300, 150);
comboBox.SubscribeTo(builder._DisplayMember, value => comboBox.DisplayMember = value);
comboBox.SubscribeTo(builder._DataSource, value => comboBox.DataSource = value);
comboBox.SelectedIndexChanged += (sender, e) =>
{
var index = comboBox.SelectedIndex;
var selectedValue = index < 0 ? null : comboBox.Items[index];
builder._SelectedItem.OnNext(selectedValue);
};
return comboBox;
}
}
}
50 changes: 50 additions & 0 deletions src/Bonsai.Gui/DataSourceControlBuilderBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace Bonsai.Gui
{
/// <summary>
/// Provides an abstract base class for UI controls which can be bound to each
/// data source from an observable sequence.
/// </summary>
public abstract class DataSourceControlBuilderBase : ControlBuilderBase
{
static readonly Range<int> argumentRange = Range.Create(lowerBound: 1, upperBound: 1);

/// <summary>
/// Gets the range of input arguments that this expression builder accepts.
/// </summary>
public override Range<int> ArgumentRange => argumentRange;

/// <summary>
/// Builds the expression tree for configuring and calling the UI control.
/// </summary>
/// <inheritdoc/>
public override Expression Build(IEnumerable<Expression> arguments)
{
var source = arguments.First();
var sourceType = source.Type.GetGenericArguments()[0];
var valueType = ExpressionHelper.GetGenericTypeBindings(typeof(IList<>), sourceType);
return Expression.Call(Expression.Constant(this), nameof(Generate), valueType, source);
}

/// <summary>
/// Generates an observable sequence of values containing the currently
/// selected item from the data source whenever the selection changes.
/// </summary>
/// <typeparam name="TValue">
/// The type of the values in the data source.
/// </typeparam>
/// <param name="source">
/// A sequence of collections representing the data sources to bind to
/// the UI control. Only one collection is bound at any one time.
/// </param>
/// <returns>
/// A sequence of values representing the currently selected item in
/// the UI control.
/// </returns>
protected abstract IObservable<TValue> Generate<TValue>(IObservable<IList<TValue>> source);
}
}
14 changes: 14 additions & 0 deletions src/Bonsai.Gui/ListBoxDataSourceBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.ComponentModel;

namespace Bonsai.Gui
{
/// <summary>
/// Represents an operator that interfaces with a list box control bound to each data
/// source in the sequence and generates notifications whenever the selection changes.
/// </summary>
[TypeVisualizer(typeof(ListBoxDataSourceVisualizer))]
[Description("Interfaces with a list box control bound to each data source in the sequence and generates notifications whenever the selection changes.")]
public class ListBoxDataSourceBuilder : ListControlDataSourceBuilderBase
{
}
}
30 changes: 30 additions & 0 deletions src/Bonsai.Gui/ListBoxDataSourceVisualizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Drawing;
using System.Windows.Forms;

namespace Bonsai.Gui
{
/// <summary>
/// Provides a type visualizer representing a list box control bound
/// to an arbitrary data source.
/// </summary>
public class ListBoxDataSourceVisualizer : ControlVisualizerBase<ListBox, ListBoxDataSourceBuilder>
{
/// <inheritdoc/>
protected override ListBox CreateControl(IServiceProvider provider, ListBoxDataSourceBuilder builder)
{
var listBox = new ListBox();
listBox.Dock = DockStyle.Fill;
listBox.Size = new Size(300, 150);
listBox.SubscribeTo(builder._DisplayMember, value => listBox.DisplayMember = value);
listBox.SubscribeTo(builder._DataSource, value => listBox.DataSource = value);
listBox.SelectedIndexChanged += (sender, e) =>
{
var index = listBox.SelectedIndex;
var selectedValue = index < 0 ? null : listBox.Items[index];
builder._SelectedItem.OnNext(selectedValue);
};
return listBox;
}
}
}
70 changes: 70 additions & 0 deletions src/Bonsai.Gui/ListControlDataSourceBuilderBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Design;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;

namespace Bonsai.Gui
{
/// <summary>
/// Provides an abstract base class for interfacing with combo box and list box controls
/// bound to each data source in an observable sequence.
/// </summary>
public class ListControlDataSourceBuilderBase : DataSourceControlBuilderBase
{
internal readonly BehaviorSubject<string> _DisplayMember = new(string.Empty);
internal readonly BehaviorSubject<object> _DataSource = new(null);
internal readonly BehaviorSubject<object> _SelectedItem = new(null);

/// <summary>
/// Gets or sets the property to display for this list control.
/// </summary>
[Editor(typeof(DataMemberSelectorEditor), typeof(UITypeEditor))]
[Description("The property to display for this list control.")]
public string DisplayMember
{
get => _DisplayMember.Value;
set => _DisplayMember.OnNext(value);
}

/// <summary>
/// Generates an observable sequence of values containing the currently
/// selected item in the list control whenever the selection changes.
/// </summary>
/// <typeparam name="TValue">
/// The type of the values in the data source.
/// </typeparam>
/// <param name="source">
/// A sequence of collections representing the data sources to bind to
/// the list control. Only one collection is bound at any one time.
/// </param>
/// <returns>
/// A sequence of values representing the currently selected item in
/// the list control.
/// </returns>
protected override IObservable<TValue> Generate<TValue>(IObservable<IList<TValue>> source)
{
return Observable.Create<TValue>(observer =>
{
var sourceObserver = Observer.Create<IList<TValue>>(collection =>
{
_SelectedItem.OnNext(null);
_DataSource.OnNext(collection);
}, observer.OnError);
var selectedItem = _SelectedItem.Where(value => value is TValue).Cast<TValue>();
return new CompositeDisposable(
selectedItem.SubscribeSafe(observer),
source.SubscribeSafe(sourceObserver),
Disposable.Create(() =>
{
_DataSource.OnNext(null);
_SelectedItem.OnNext(null);
}));
});
}
}
}

0 comments on commit 3de1c23

Please sign in to comment.