SP-257 mview popups for working orders & day fills
This commit is contained in:
1 parent
7875b939e6
commit
2f59900a69
22 files changed
+1061
-166
No files matched your search
@@ -0,0 +1,13 @@
|
||||
using mbts.spark;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
readonly struct MarketViewCell {
|
||||
public readonly MarketViewVM.Row row;
|
||||
public readonly MarketViewView.Columns column;
|
||||
|
||||
public MarketViewCell(MarketViewVM.Row row, MarketViewView.Columns column) {
|
||||
this.row = row;
|
||||
this.column = column;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
@@ -15,17 +18,22 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.MarketViewWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.MarketViewView;
|
||||
|
||||
namespace mbts.spark {
|
||||
/// <summary>
|
||||
/// Viewmodel class designed for <see cref="ui.spark.MarketViewView"/>
|
||||
/// </summary>
|
||||
partial class MarketViewVM : DisposableViewModelBase, IHandleRowSelection {
|
||||
static readonly IReadOnlyList<Func<MarketViewVM, IDataGridColumnFilter<Row>>> _filter_getters =
|
||||
ObjectSnapshot.property_delegates_of_type<MarketViewVM, IDataGridColumnFilter<Row>>().ToReadOnlyList();
|
||||
@@ -43,16 +51,30 @@ namespace mbts.spark {
|
||||
readonly DeferrableActionWrapper _refresh_filter;
|
||||
Func<Row, bool> _filter;
|
||||
IReadOnlyList<(Columns key, ListSortDirection direction)> _sorting_hierarchy;
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
Color _selection_highlight;
|
||||
|
||||
public MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IWindowSettingsManager<GridSettings> settings_manager) {
|
||||
/// <summary>
|
||||
/// Initializes an instance of the <see cref="ui.spark.MarketViewView"/> class.
|
||||
/// </summary>
|
||||
/// <param name="bus"></param>
|
||||
/// <param name="orders_model"></param>
|
||||
/// <param name="security_mds_source"></param>
|
||||
/// <param name="use_mouse_state_transitions">Indicates whether or not hyperlink cells should use visual states to animate mouse over (seems to be slower than style triggers, should investigate to be certain).</param>
|
||||
public MarketViewVM(
|
||||
IBus bus,
|
||||
OrdersModel orders_model,
|
||||
IObservable<TableUpdate<SecurityMD, uint>> security_mds_source,
|
||||
bool use_mouse_state_transitions = false) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
|
||||
_settings_manager = settings_manager;
|
||||
comparer = DIYGridSortHelper.build_comparer(sorting_hierarchy, get_column_comparer, Columns.Security);
|
||||
// rows
|
||||
_wrapped_rows = _rows.WrapSelector(r => r.security_md, s => new Row(_bus, s, _orders_model));
|
||||
_wrapped_rows = _rows.WrapSelector(r => r.security_md, s => {
|
||||
var r = new Row(_bus, s, _orders_model);
|
||||
r.is_visible = _filter == null || _filter(r);
|
||||
return r;
|
||||
});
|
||||
_current_row = backing<Row>(nameof(current_row));
|
||||
_rows_view = new ListCollectionView(_rows) {
|
||||
Filter = obj => obj is Row r && r.is_visible,
|
||||
@@ -67,23 +89,54 @@ namespace mbts.spark {
|
||||
add_col(Columns.Volume, "Volume", r => r.volume.ToString(), nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
|
||||
add_col(Columns.LastSpd, "Spd", r => r.last_price_from_spread ? "S" : "", nameof(Row.last_price_from_spread), DefaultColumnWidths.Side, last_price_from_spread_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.RawDayPosition, "Raw Day Pos", r => SparkUtils.fmt_position(r.raw_day_position), nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingBids, working_col_header, nameof(Row.working_bids_text), nameof(Row.working_best_bid), nameof(Row.working_bids_click), working_bid_volume_filter, BTSColors.MarketData.bid, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.BidMergedSize, merged_size_col_header, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), nameof(Row.bid_merged_size_click), bid_merged_size_filter, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.AskMergedSize, merged_size_col_header, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), nameof(Row.ask_merged_size_click), ask_merged_size_filter, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingAsks, working_col_header, nameof(Row.working_asks_text), nameof(Row.working_best_ask), nameof(Row.working_asks_click), working_ask_volume_filter, BTSColors.MarketData.ask, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Settlement, "Settlement", r => r.format_price(r.settlement), nameof(Row.settlement), DefaultColumnWidths.Settlement, settlement_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.DayChange, "Change", r => r.format_price(r.day_change), nameof(Row.day_change), DefaultColumnWidths.Price, day_change_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SessionLow, "Low", r => r.format_price(r.session_low), nameof(Row.session_low), DefaultColumnWidths.Price, session_low_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax);
|
||||
add_hl_col(
|
||||
Columns.Position,
|
||||
"Pos",
|
||||
new Binding(nameof(Row.position)),
|
||||
nameof(Row.position),
|
||||
position_filter,
|
||||
DataGridFilterKnownTemplates.IntMinMax,
|
||||
DefaultColumnWidths.Size,
|
||||
tooltip: $"Show {_fills_title(Columns.Position).desc}");
|
||||
add_hl_col(
|
||||
Columns.RawDayPosition,
|
||||
"Raw Day Pos",
|
||||
new Binding(nameof(Row.raw_day_position)) {
|
||||
Converter = ValueConverter.create<double, string>(SparkUtils.fmt_position)
|
||||
},
|
||||
nameof(Row.raw_day_position),
|
||||
raw_day_position_filter,
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
DefaultColumnWidths.Size,
|
||||
tooltip: $"Show {_fills_title(Columns.RawDayPosition).desc}");
|
||||
add_hl_working_orders_col(
|
||||
Columns.WorkingBids,
|
||||
working_col_header,
|
||||
nameof(Row.working_bids_text),
|
||||
nameof(Row.working_best_bid),
|
||||
working_bid_volume_filter,
|
||||
BTSColors.MarketData.bid);
|
||||
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid);
|
||||
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid);
|
||||
|
||||
add_hl_merged_size_col(Columns.BidMergedSize, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), bid_merged_size_filter, BTSColors.MarketData.bid);
|
||||
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid);
|
||||
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask);
|
||||
add_hl_merged_size_col(Columns.AskMergedSize, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), ask_merged_size_filter, BTSColors.MarketData.ask);
|
||||
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask);
|
||||
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask);
|
||||
add_hl_working_orders_col(
|
||||
Columns.WorkingAsks,
|
||||
working_col_header,
|
||||
nameof(Row.working_asks_text),
|
||||
nameof(Row.working_best_ask),
|
||||
working_ask_volume_filter,
|
||||
BTSColors.MarketData.ask);
|
||||
add_col(Columns.Settlement, "Settlement", r => r.format_price(r.settlement), nameof(Row.settlement), DefaultColumnWidths.Settlement, settlement_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.DayChange, "Change", r => r.format_price(r.day_change), nameof(Row.day_change), DefaultColumnWidths.Price, day_change_filter, DataGridFilterKnownTemplates.DoubleMinMax, true);
|
||||
add_col(Columns.SessionLow, "Low", r => r.format_price(r.session_low), nameof(Row.session_low), DefaultColumnWidths.Price, session_low_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.SecurityKey, "Security Key", r => r.security_key.ToString(), nameof(Row.security_key), DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
|
||||
add_col(Columns.MarketDescription, "Market Description", r => r.security.market_description(), nameof(Row.market_description), DefaultColumnWidths.SecurityKey, market_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
@@ -93,8 +146,8 @@ namespace mbts.spark {
|
||||
add_col(Columns.StripName, "Strip Name", r => r.security.strip_name(), nameof(Row.strip_name), DefaultColumnWidths.SecurityKey, strip_name_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.MicCode, "Mic Code", r => r.security.mic_code(), nameof(Row.mic_code), DefaultColumnWidths.SecurityKey, mic_code_filter, DataGridFilterKnownTemplates.Regex);
|
||||
//
|
||||
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
RenderTextColumn<Row> add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, HorizontalAlignment h_alignment = HorizontalAlignment.Right) {
|
||||
var col = new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
@@ -103,31 +156,85 @@ namespace mbts.spark {
|
||||
padding = is_animatable ? AnimatableCellPadding : DefaultCellPadding,
|
||||
font_color_override = font_color,
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
};
|
||||
columns.Add(col);
|
||||
return col;
|
||||
}
|
||||
|
||||
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, h_alignment);
|
||||
void add_hl_price_col(Columns col_key, string header, string prop, IDataGridColumnFilter filter, Color foreground, Color? background) =>
|
||||
add_hl_col(
|
||||
col_key,
|
||||
header,
|
||||
price_binding(prop),
|
||||
prop,
|
||||
filter,
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
DefaultColumnWidths.MViewPrice,
|
||||
foreground,
|
||||
background,
|
||||
tooltip: _order_ticket_tooltip(col_key));
|
||||
|
||||
void add_hl_merged_size_col(Columns col_key, string header, string sort_prop, string text_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, h_alignment);
|
||||
void add_hl_merged_size_col(Columns col_key, string sort_prop, string text_prop, IDataGridColumnFilter filter, Color foreground) =>
|
||||
add_hl_col(
|
||||
col_key,
|
||||
merged_size_col_header,
|
||||
new Binding(text_prop),
|
||||
sort_prop,
|
||||
filter,
|
||||
DataGridFilterKnownTemplates.UIntMinMax,
|
||||
DefaultColumnWidths.ImpliedDirectMergedSize,
|
||||
foreground,
|
||||
tooltip: _order_ticket_tooltip(col_key));
|
||||
|
||||
void add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, h_alignment);
|
||||
HyperlinkElementColumn<Columns> add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, IDataGridColumnFilter filter, Color foreground) =>
|
||||
add_hl_col(
|
||||
col_key,
|
||||
header,
|
||||
new Binding(text_prop),
|
||||
sort_prop,
|
||||
filter,
|
||||
DataGridFilterKnownTemplates.IntMinMax,
|
||||
working_orders_col_width,
|
||||
foreground,
|
||||
tooltip: $"Show {_orderbook_title(col_key).desc}");
|
||||
|
||||
void add_hl_col(
|
||||
Columns col_key, string header, BindingBase binding, string prop, string command_prop,
|
||||
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new HyperlinkElementColumn<Row, Columns>(
|
||||
col_key, binding, new Binding(command_prop), filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
font_color_override = foreground,
|
||||
background_color_override = background,
|
||||
Width = DefaultColumnWidths.Price,
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
HyperlinkElementColumn<Columns> add_hl_col(
|
||||
Columns col_key,
|
||||
string header,
|
||||
BindingBase binding,
|
||||
string prop,
|
||||
IDataGridColumnFilter filter,
|
||||
string filter_template,
|
||||
double width,
|
||||
Color? foreground = null,
|
||||
Color? background = null,
|
||||
HorizontalAlignment h_alignment = HorizontalAlignment.Right,
|
||||
string tooltip = null) {
|
||||
|
||||
var col = new HyperlinkElementColumn<Columns>(
|
||||
col_key, filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
font_color_override = foreground,
|
||||
background_color_override = background,
|
||||
TextBinding = binding,
|
||||
Width = DefaultColumnWidths.Price,
|
||||
horizontal_alignment = h_alignment,
|
||||
HyperlinkClickMode = HyperlinkCellClickMode.Anywhere
|
||||
};
|
||||
col.view_generated += on_view_generated;
|
||||
columns.Add(col);
|
||||
return col;
|
||||
//
|
||||
void on_view_generated(HyperlinkCell cell_view) {
|
||||
cell_view.SetValue(ColumnKeyProperty, col_key);
|
||||
cell_view.ToolTip = tooltip;
|
||||
if (use_mouse_state_transitions)
|
||||
init_hyperlink_hover_animation(cell_view, col);
|
||||
}
|
||||
}
|
||||
|
||||
BindingBase price_binding(string prop) {
|
||||
var mb = new MultiBinding { Converter = price_converter };
|
||||
@@ -135,14 +242,6 @@ namespace mbts.spark {
|
||||
mb.Bindings.Add(new Binding($"{nameof(Row.security)}.{nameof(SecurityImmutable.ContractSpec)}") { Mode = BindingMode.OneWay });
|
||||
return mb;
|
||||
}
|
||||
//
|
||||
// make font-color immutable for Side, Size, (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
|
||||
var col_settings_flags = new Dictionary<string, GridColumnSettingsFeatureFlags> {
|
||||
{ Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor },
|
||||
{ Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor},
|
||||
{ Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
|
||||
};
|
||||
// row vm filter
|
||||
// Althoguh securities are filtered at the row 'model' level (SecurityMD), the column base filters have to be applied at the row vm level,
|
||||
// because they access market data, which is polled periodically and cached in the row vm.
|
||||
@@ -158,75 +257,66 @@ namespace mbts.spark {
|
||||
f.filter_changed += _refresh_filter.execute;
|
||||
}
|
||||
//
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, col_settings_flags, _rows);
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
//
|
||||
_dispose = Disposable.Create(
|
||||
row_filter_source.Subscribe(f => {
|
||||
// the rows are polled every 100 ms, just set the filter and let the refresh take care of the rest.
|
||||
_filter = f;
|
||||
}),
|
||||
state.SelectManyInSeries(s => s.security_criteria.subscribe_md_map(bus))
|
||||
.Table(SecurityMD.key_fn)
|
||||
.Where(t => t.has_changes())
|
||||
.OnDispatcher()
|
||||
.Subscribe(table => {
|
||||
_wrapped_rows.RemoveSorted(table.removed.Concat(table.changes.Old()), _sec_md_key_comparer);
|
||||
_wrapped_rows.InsertSorted(table.added.Concat(table.changes.New()), _sec_md_key_comparer);
|
||||
}),
|
||||
state.OnDispatcher().Subscribe(s => {
|
||||
using (Disposable.empty) {
|
||||
if (s.grid != null) {
|
||||
// this means we are not using defaults,
|
||||
// Note: settings_manager in turn notifies grid_settings_manager of this update
|
||||
_settings_manager.instance_settings = s.grid;
|
||||
}
|
||||
if (s.filters is FilterStates filters) {
|
||||
using (_refresh_filter.defer_execution()) {
|
||||
security_filter.load_state(filters.security);
|
||||
bid_price_filter.load_state(filters.bid_price);
|
||||
bid_merged_size_filter.load_state(filters.bid_merged_size);
|
||||
bid_direct_size_filter.load_state(filters.bid_direct_size);
|
||||
bid_implied_size_filter.load_state(filters.bid_implied_size);
|
||||
ask_price_filter.load_state(filters.ask_price);
|
||||
ask_merged_size_filter.load_state(filters.ask_merged_size);
|
||||
ask_direct_size_filter.load_state(filters.ask_direct_size);
|
||||
ask_implied_size_filter.load_state(filters.ask_implied_size);
|
||||
last_price_filter.load_state(filters.last_price);
|
||||
last_price_from_spread_filter.load_state(filters.last_price_from_spread);
|
||||
last_size_filter.load_state(filters.last_size);
|
||||
day_change_filter.load_state(filters.day_change);
|
||||
settlement_filter.load_state(filters.settlement);
|
||||
session_low_filter.load_state(filters.session_low);
|
||||
session_high_filter.load_state(filters.session_high);
|
||||
position_filter.load_state(filters.position);
|
||||
raw_day_position_filter.load_state(filters.raw_day_position);
|
||||
working_bid_volume_filter.load_state(filters.working_bid_volume);
|
||||
working_ask_volume_filter.load_state(filters.working_ask_volume);
|
||||
volume_filter.load_state(filters.volume);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
security_mds_source.Subscribe(_on_securities),
|
||||
ObservableAutoWrapper.connect_item_hooks(_rows, r => r, true),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
WorkLoop.Start("MarketView", _refresh)
|
||||
);
|
||||
void init_hyperlink_hover_animation(HyperlinkCell cell_view, ColumnBase col) {
|
||||
// background timeline
|
||||
var hover_bg_timeline = VisualStateHelper.create_color_animation(
|
||||
cell_view,
|
||||
new PropertyPath("(0).(1)", HyperlinkCell.BackgroundProperty, SolidColorBrush.ColorProperty),
|
||||
new Binding(nameof(ColumnBase.background_color)) {
|
||||
Source = col,
|
||||
Converter = new ColorBlendConverter { Overlay = Colors.Black.with(0x1A) }
|
||||
});
|
||||
// background pressed timeline
|
||||
var pressed_bg_timeline = VisualStateHelper.create_color_animation(
|
||||
cell_view,
|
||||
new PropertyPath("(0).(1)", HyperlinkCell.BackgroundProperty, SolidColorBrush.ColorProperty),
|
||||
new Binding(nameof(ColumnBase.background_color)) {
|
||||
Source = col,
|
||||
Converter = new ColorBlendConverter { Overlay = Colors.Black.with(0x25) }
|
||||
});
|
||||
// border timeline
|
||||
var bd_brush = new SolidColorBrush();
|
||||
BindingOperations.SetBinding(bd_brush, SolidColorBrush.ColorProperty, new Binding(nameof(ColumnBase.background_color)) { Source = col });
|
||||
cell_view.BorderBrush = bd_brush;
|
||||
cell_view.BorderThickness = new Thickness(1);
|
||||
var hover_bd_timeline = VisualStateHelper.create_color_animation(
|
||||
cell_view, new PropertyPath("(0).(1)", HyperlinkCell.BorderBrushProperty, SolidColorBrush.ColorProperty), Colors.Black.with(0x60));
|
||||
// mouse states
|
||||
VisualStateManager.GetVisualStateGroups(cell_view).Add(
|
||||
VisualStateHelper.create_group("MouseStates",
|
||||
new VisualState { Name = "Normal" },
|
||||
VisualStateHelper.create_state("MouseOver", hover_bg_timeline, hover_bd_timeline),
|
||||
VisualStateHelper.create_state("Pressed", pressed_bg_timeline, hover_bd_timeline)));
|
||||
// state groups manager
|
||||
StateManager.SetGroups(cell_view, new StateGroupsManager {
|
||||
StateDefinitions = new StateDefinitionCollection {
|
||||
new MouseStateDefinition { MouseSource = cell_view }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChange<Row> current_row_changed { add => _current_row.Changed += value; remove => _current_row.Changed -= value; }
|
||||
event Action _filter_changed;
|
||||
|
||||
public ICollectionView rows_view => _rows_view;
|
||||
public ObservableCollection<Row> rows => _rows;
|
||||
public ObservableCollection<Row> selected_rows { get; } = new ObservableCollection<Row>();
|
||||
public Color selection_highlight { get => _selection_highlight; set => Set(ref _selection_highlight, value); }
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
public Row current_row { get => _current_row.Value; set => _current_row.set(value); }
|
||||
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
public ICommand insert_group_cmd { get; }
|
||||
public ICommand remove_partition_cmd { get; }
|
||||
internal IBus bus => _bus;
|
||||
internal OrdersModel orders_model => _orders_model;
|
||||
|
||||
public IReadOnlyList<(Columns key, ListSortDirection direction)> sorting_hierarchy {
|
||||
get => _sorting_hierarchy ?? (_sorting_hierarchy = Array.Empty<(Columns, ListSortDirection)>());
|
||||
@@ -308,11 +398,45 @@ namespace mbts.spark {
|
||||
Comparer<T>.Default.wrap(selector);
|
||||
}
|
||||
|
||||
public GridSettings get_grid_save_state() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
return null;
|
||||
else
|
||||
return grid_settings_manager.create();
|
||||
public (FillsVM vm, string context, string title, Color fg) fills_params(SecurityImmutable security, Columns column) {
|
||||
var (title_desc, fg) = _fills_title(column);
|
||||
var state = StartupData.DefaultAuxiliaryFills;
|
||||
if (state.filters == null)
|
||||
state.filters = new bts.shell.fills.FillsFilterStates();
|
||||
state.filters.security = SecurityCriteria.from_keys(new[] { security.Id });
|
||||
switch (column) {
|
||||
case Columns.RawDayPosition:
|
||||
state.filters.fill_kind = new[] {
|
||||
Fill.Types.FillKind.Standard
|
||||
};
|
||||
break;
|
||||
case Columns.Position:
|
||||
state.filters.fill_kind = new[] {
|
||||
Fill.Types.FillKind.Standard,
|
||||
Fill.Types.FillKind.Sod
|
||||
};
|
||||
break;
|
||||
}
|
||||
state.grid = GridSettingsHelper.hide_cols(state.grid, new[] { ui.spark.FillsView.Columns.Security });
|
||||
var vm = new FillsVM(
|
||||
_bus, _orders_model, Observable.Return(state), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
|
||||
return (vm, $"{security}", title_desc, fg);
|
||||
}
|
||||
|
||||
public (OrderBookVM vm, string context, string title, Color fg) orderbook_params(SecurityImmutable security, Columns column) {
|
||||
var (title_desc, fg) = _orderbook_title(column);
|
||||
var filters = new bts.shell.orderbook.FilterStates {
|
||||
security_criteria = SecurityCriteria.from_keys(new[] { security.Id })
|
||||
};
|
||||
switch (column) {
|
||||
case Columns.WorkingBids: filters.sides = new[] { Side.Buy }; break;
|
||||
case Columns.WorkingAsks: filters.sides = new[] { Side.Sell }; break;
|
||||
}
|
||||
var vm = _mk_orderbook_vm_common(filters, new[] {
|
||||
OrderBookGridView.ColumnKeys.Security,
|
||||
OrderBookGridView.ColumnKeys.Side
|
||||
});
|
||||
return (vm, $"{security}", title_desc, fg);
|
||||
}
|
||||
|
||||
public FilterStates get_filter_states() {
|
||||
@@ -348,6 +472,79 @@ namespace mbts.spark {
|
||||
};
|
||||
}
|
||||
|
||||
public void load_filters(FilterStates filters) {
|
||||
if (filters != null) {
|
||||
using (rows_view.DeferRefresh()) {
|
||||
security_filter.load_state(filters.security);
|
||||
bid_price_filter.load_state(filters.bid_price);
|
||||
bid_merged_size_filter.load_state(filters.bid_merged_size);
|
||||
bid_direct_size_filter.load_state(filters.bid_direct_size);
|
||||
bid_implied_size_filter.load_state(filters.bid_implied_size);
|
||||
ask_price_filter.load_state(filters.ask_price);
|
||||
ask_merged_size_filter.load_state(filters.ask_merged_size);
|
||||
ask_direct_size_filter.load_state(filters.ask_direct_size);
|
||||
ask_implied_size_filter.load_state(filters.ask_implied_size);
|
||||
last_price_filter.load_state(filters.last_price);
|
||||
last_price_from_spread_filter.load_state(filters.last_price_from_spread);
|
||||
last_size_filter.load_state(filters.last_size);
|
||||
day_change_filter.load_state(filters.day_change);
|
||||
settlement_filter.load_state(filters.settlement);
|
||||
session_low_filter.load_state(filters.session_low);
|
||||
session_high_filter.load_state(filters.session_high);
|
||||
position_filter.load_state(filters.position);
|
||||
raw_day_position_filter.load_state(filters.raw_day_position);
|
||||
working_bid_volume_filter.load_state(filters.working_bid_volume);
|
||||
working_ask_volume_filter.load_state(filters.working_ask_volume);
|
||||
volume_filter.load_state(filters.volume);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static (string desc, Color fg) _fills_title(Columns key) {
|
||||
switch (key) {
|
||||
case Columns.Position: return ("Day Fills", Colors.Black);
|
||||
case Columns.RawDayPosition: return ("Raw Day Fills", Colors.Black);
|
||||
default: return ("Fills", Colors.Black);
|
||||
}
|
||||
}
|
||||
|
||||
static (string desc, Color fg) _orderbook_title(Columns key) {
|
||||
switch (key) {
|
||||
case Columns.WorkingBids: return ("Working Buy Orders", BTSColors.MarketData.bid);
|
||||
case Columns.WorkingAsks: return ("Working Sell Orders", BTSColors.MarketData.ask);
|
||||
default: return ("Working Orders", Colors.Black);
|
||||
}
|
||||
}
|
||||
|
||||
static GridSettings _hide_cols(GridSettings grid, IEnumerable<OrderBookGridView.ColumnKeys> columns) =>
|
||||
grid.with(_hide_cols(grid.columns, columns));
|
||||
|
||||
static IEnumerable<GridColumnSettings> _hide_cols(IEnumerable<GridColumnSettings> cols, IEnumerable<OrderBookGridView.ColumnKeys> columns) {
|
||||
// hide columns who's data is implied by the grouping
|
||||
var col_hash = new HashSet<string>(columns.Select(k => k.ToString()));
|
||||
foreach (var c in cols)
|
||||
yield return col_hash.Contains(c.id) ? c.with(is_visible: false) : c;
|
||||
}
|
||||
|
||||
static string _order_ticket_tooltip(Columns key) {
|
||||
var bid_or_ask = Regex.Match(key.ToString(), "(Bid|Ask)");
|
||||
return bid_or_ask.Success ? $"Order Ticket @ {bid_or_ask.Value}" : "Order Ticket";
|
||||
}
|
||||
|
||||
OrderBookVM _mk_orderbook_vm_common(bts.shell.orderbook.FilterStates filters, IEnumerable<OrderBookGridView.ColumnKeys> hide_cols, IObservable<TableUpdate<OrderSecurityRootPair, uint>> orders_source = null) {
|
||||
var state = new bts.shell.orderbook.OrderBookStartupData(filters, bts.shell.orderbook.OrderBookStartupData.default_mini_grid, use_defaults: false);
|
||||
state.orderbook_grid.general = _hide_cols(state.orderbook_grid.general, hide_cols);
|
||||
var settings_man = new WindowSettingsManager<bts.shell.orderbook.OrderBookGridSettings>.DisconnectedImpl(state.orderbook_grid);
|
||||
var obvm = orders_source == null ?
|
||||
new OrderBookVM(_bus, null, _orders_model, settings_man, true) :
|
||||
new OrderBookVM(_bus, null, _orders_model, orders_source, settings_man, true);
|
||||
foreach (var c in OrderBookVM.OrderBookColumnCollection.editable_columns)
|
||||
obvm.columns.Insert(0, c);
|
||||
obvm.load_from(state);
|
||||
return obvm;
|
||||
}
|
||||
|
||||
void _refresh() {
|
||||
foreach (var r in _rows) {
|
||||
r.refresh();
|
||||
@@ -361,5 +558,22 @@ namespace mbts.spark {
|
||||
selected_rows.RemoveSorted(e.RemovedItems.OfType<Row>(), _row_key_comparer);
|
||||
selected_rows.InsertSorted(e.AddedItems.OfType<Row>(), _row_key_comparer);
|
||||
}
|
||||
|
||||
void _on_securities(TableUpdate<SecurityMD, uint> table) {
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
foreach (var smd in table.removed) {
|
||||
int i = CollectionHelper.binary_search(_wrapped_rows, smd, _sec_md_key_comparer);
|
||||
Log.assert(i >= 0);
|
||||
_wrapped_rows.RemoveAt(i);
|
||||
}
|
||||
foreach (var smd in table.added) {
|
||||
int i = CollectionHelper.binary_search(_wrapped_rows, smd, _sec_md_key_comparer);
|
||||
Log.assert(i < 0);
|
||||
_wrapped_rows.Insert(~i, smd);
|
||||
}
|
||||
sw.Stop();
|
||||
Log.Ui.info($"MarketView update took {sw.ElapsedMilliseconds:0.00}ms.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,13 @@
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:bv="clr-namespace:bts.view"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
u:WindowStyle.UnsafeWindow="{Binding Path='(u:WindowStyle.UnsafeWindow)', RelativeSource={RelativeSource AncestorType=bv:SparkWindowView}}"
|
||||
FocusManager.IsFocusScope="True"
|
||||
Background="{StaticResource CommonBackground}"
|
||||
>
|
||||
@@ -13,8 +18,54 @@
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<Style TargetType="c:HyperlinkCell">
|
||||
<Setter Property="Background" Value="{Binding Path='(c:DIYGrid.Column).background_brush', RelativeSource={RelativeSource Self}}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Command" Value="{x:Static usp:MarketViewView.CellClickCommand}"/>
|
||||
<Setter Property="CommandParameter">
|
||||
<Setter.Value>
|
||||
<MultiBinding Converter="{x:Static usp:MarketViewView.CellConverter}">
|
||||
<Binding Path=""/>
|
||||
<Binding Path="(c:DIYGrid.ColumnKey)" RelativeSource="{RelativeSource Self}"/>
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=usp:MarketViewView}}"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOverClickArea" Value="True">
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush>
|
||||
<SolidColorBrush.Color>
|
||||
<MultiBinding Converter="{x:Static cv:ColorBlendMultiConverter.Instance}">
|
||||
<Binding Path="SelectionHighlight" RelativeSource="{RelativeSource AncestorType=c:DIYGrid}"/>
|
||||
<Binding Path="(c:DIYGrid.Column).background_color" RelativeSource="{RelativeSource AncestorType=c:HyperlinkCell}"/>
|
||||
</MultiBinding>
|
||||
</SolidColorBrush.Color>
|
||||
</SolidColorBrush>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="BorderBrush">
|
||||
<Setter.Value>
|
||||
<Binding Path="SelectionHighlight" RelativeSource="{RelativeSource AncestorType=c:DIYGrid}">
|
||||
<Binding.Converter>
|
||||
<cv:ValueConverterPipe>
|
||||
<cv:ColorWithConverter A="255"/>
|
||||
<cv:ColorToBrushConverter/>
|
||||
</cv:ValueConverterPipe>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<UserControl.CommandBindings>
|
||||
<CommandBinding Command="{x:Static usp:MarketViewView.CellClickCommand}" Executed="{x:Static usp:MarketViewView.CellClickExecuted}"/>
|
||||
</UserControl.CommandBindings>
|
||||
<AdornerDecorator>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
@@ -27,7 +78,7 @@
|
||||
Columns="{Binding columns}"
|
||||
CurrentRow="{Binding current_row}"
|
||||
Rows="{Binding rows_view}"
|
||||
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
|
||||
SelectionHighlight="{Binding selection_highlight, Mode=OneWay}"
|
||||
SyncColumnSortDirectionWithSortDescriptions="False"
|
||||
>
|
||||
<c:DIYGrid.InputBindings>
|
||||
|
||||
@@ -1,14 +1,36 @@
|
||||
using mbts.spark;
|
||||
using bts.api;
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for MarketSheetsView.xaml
|
||||
/// </summary>
|
||||
partial class MarketViewView : UserControl {
|
||||
public static readonly RoutedCommand CellClickCommand = new RoutedCommand();
|
||||
|
||||
MarketViewVM _vm;
|
||||
DIYGridPopupManager _popup_man_backing;
|
||||
|
||||
public MarketViewView() {
|
||||
InitializeComponent();
|
||||
this.ConnectDataContext<MarketViewVM>(vm => _vm = vm);
|
||||
AdornerHelper.attach_continuously_on_loaded(
|
||||
() => new GridAnimation.DIYGridAnimationAdorner<Columns>(
|
||||
_grid, _grid.descendants_of_type<DIYGridRowsPresenter>().First()),
|
||||
_grid);
|
||||
}
|
||||
|
||||
public enum Columns {
|
||||
None,
|
||||
Security,
|
||||
Volume,
|
||||
Last,
|
||||
@@ -39,12 +61,105 @@ namespace mbts.ui.spark {
|
||||
MicCode
|
||||
}
|
||||
|
||||
public MarketViewView() {
|
||||
InitializeComponent();
|
||||
AdornerHelper.attach_continuously_on_loaded(
|
||||
() => new GridAnimation.DIYGridAnimationAdorner<Columns>(
|
||||
_grid, _grid.descendants_of_type<DIYGridRowsPresenter>().First()),
|
||||
_grid);
|
||||
public static ExecutedRoutedEventHandler
|
||||
CellClickExecuted => CommandImpl.cell_click;
|
||||
|
||||
public static IMultiValueConverter CellConverter =>
|
||||
ValueConverter.create<MarketViewVM.Row, Columns, MarketViewCell>((r, c) => new MarketViewCell(r, c));
|
||||
|
||||
IBus _bus => _vm.bus;
|
||||
OrdersModel _model => _vm.orders_model;
|
||||
DIYGridPopupManager _popup_man =>
|
||||
_popup_man_backing ?? (_popup_man_backing = DIYGridPopupManager.create(this));
|
||||
|
||||
public void show_mini_orderbook(SecurityMD security_md) =>
|
||||
_show_mini_orderbook(security_md, Columns.None);
|
||||
|
||||
void _on_cell_click(MarketViewVM.Row row, Columns col) {
|
||||
switch (col) {
|
||||
case Columns.WorkingAsks:
|
||||
case Columns.WorkingBids:
|
||||
_show_mini_orderbook(row.security_md, col);
|
||||
break;
|
||||
case Columns.Bid:
|
||||
_show_order_ticket(row.security, row.bid_price);
|
||||
break;
|
||||
case Columns.BidMergedSize: _show_order_ticket(row.security, row.bid_price, row.bid_merged_size); break;
|
||||
case Columns.BidDirectSize: _show_order_ticket(row.security, row.bid_price, row.bid_direct_size); break;
|
||||
case Columns.BidImpliedSize: _show_order_ticket(row.security, row.bid_price, row.bid_implied_size); break;
|
||||
case Columns.Ask:
|
||||
_show_order_ticket(row.security, row.ask_price);
|
||||
break;
|
||||
case Columns.AskMergedSize: _show_order_ticket(row.security, row.ask_price, row.ask_merged_size); break;
|
||||
case Columns.AskDirectSize: _show_order_ticket(row.security, row.ask_price, row.ask_direct_size); break;
|
||||
case Columns.AskImpliedSize: _show_order_ticket(row.security, row.ask_price, row.ask_implied_size); break;
|
||||
case Columns.Last:
|
||||
_show_order_ticket(row.security, row.last_price);
|
||||
break;
|
||||
case Columns.LastSize:
|
||||
_show_order_ticket(row.security, row.last_price, row.last_size);
|
||||
break;
|
||||
case Columns.Position:
|
||||
case Columns.RawDayPosition:
|
||||
_show_mini_fills(row.security, col);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _show_mini_orderbook(SecurityMD security_md, Columns col) {
|
||||
var (vm, context, title, fg) = _vm.orderbook_params(security_md.security, col);
|
||||
_show_popup(vm, new AuxiliaryDataHeader {
|
||||
context = context,
|
||||
title = title,
|
||||
title_foreground = fg
|
||||
});
|
||||
}
|
||||
|
||||
void _show_mini_fills(SecurityImmutable security, Columns col) {
|
||||
var (vm, context, title, fg) = _vm.fills_params(security, col);
|
||||
_show_popup(vm, new AuxiliaryDataHeader {
|
||||
context = context,
|
||||
title = title,
|
||||
title_foreground = fg
|
||||
});
|
||||
}
|
||||
|
||||
async void _show_order_ticket(SecurityImmutable security, double price = double.NaN, uint size = 0) {
|
||||
var sec_md = await _bus.subscribe(SparkTags.SecuritiesTag)
|
||||
.Select(sec_map => sec_map.TryGetValue(security.Id, out var smd) ? smd : (SecurityMD?)null)
|
||||
.SkipWhile(smd => !smd.HasValue)
|
||||
.FirstOrDefaultAsync();
|
||||
if (sec_md.HasValue) {
|
||||
var resources = new OrderTicketResources();
|
||||
resources.MergedDictionaries.Add(new AuxiliaryDataResources());
|
||||
var ticket_vm = new NewOrderTicketVM(
|
||||
_model, sec_md.Value, _model.factory.order(security), price, size, Observable.Return(mbts.spark.LadderSettings.Default.calculator_buttons));
|
||||
_show_popup(ticket_vm, new OrderTicketHeader { bbo = ticket_vm.bbo }, resources);
|
||||
}
|
||||
}
|
||||
|
||||
void _show_popup(object content, object header) =>
|
||||
_show_popup(content, header, new AuxiliaryDataResources());
|
||||
|
||||
void _show_popup(object content, object header, ResourceDictionary resources) {
|
||||
var view = new HeaderedContentControl {
|
||||
DataContext = content,
|
||||
Content = content,
|
||||
Header = header,
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
VerticalAlignment = VerticalAlignment.Top,
|
||||
Resources = resources ?? new AuxiliaryDataResources()
|
||||
};
|
||||
view.Style = view.Resources[AuxiliaryDataResources.PopupChildStyleKey] as Style;
|
||||
_popup_man.show_async(view).ignore();
|
||||
}
|
||||
|
||||
static class CommandImpl {
|
||||
public static void cell_click(object sender, ExecutedRoutedEventArgs e) {
|
||||
if (sender is MarketViewView mview && e.Parameter is MarketViewCell cell)
|
||||
mview._on_cell_click(cell.row, cell.column);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.collections;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
@@ -22,6 +22,14 @@ using System.Windows;
|
||||
namespace mbts.spark {
|
||||
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow, ISlowMeasureContentSize {
|
||||
const string _window_type = "MarketView", _window_type_display_name = "Market View";
|
||||
// make font-color immutable for Side, Size, (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
|
||||
static readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> _col_settings_flags = new Dictionary<string, GridColumnSettingsFeatureFlags> {
|
||||
{ MarketViewView.Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor },
|
||||
{ MarketViewView.Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor},
|
||||
{ MarketViewView.Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ MarketViewView.Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
|
||||
};
|
||||
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly AllowOnce _init_guard = new AllowOnce("MarketView");
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
@@ -33,6 +41,7 @@ namespace mbts.spark {
|
||||
SecurityCriteria
|
||||
_selected_security_criteria_backing,
|
||||
_sorted_selected_security_criteria_backing;
|
||||
SimpleGridSettingsManager<MarketViewVM.Row> _grid_settings_manager;
|
||||
|
||||
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Market View", bus, factory, SparkCommands.ShowMarketView.icon) {
|
||||
@@ -43,8 +52,6 @@ namespace mbts.spark {
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().market_view ? "MarketView" : null;
|
||||
}
|
||||
|
||||
event Action<MarketViewWindowFactory.StartupData> _state_changed;
|
||||
|
||||
public override string window_type => _window_type;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
@@ -52,7 +59,7 @@ namespace mbts.spark {
|
||||
_state = new MarketViewWindowFactory.StartupData(
|
||||
_state.security_criteria,
|
||||
_vm.get_filter_states(),
|
||||
_vm.get_grid_save_state(),
|
||||
_settings_manager.instance_settings,
|
||||
_vm.sorting_hierarchy.Select(tup => new SortingCriteria(tup.key.ToString(), tup.direction == System.ComponentModel.ListSortDirection.Ascending))
|
||||
.ToList());
|
||||
|
||||
@@ -85,15 +92,22 @@ namespace mbts.spark {
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
Log.notnull(_state);
|
||||
var state_src = Observable.FromEvent<Action<MarketViewWindowFactory.StartupData>, MarketViewWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
|
||||
_vm = new MarketViewVM(_bus, _orders_model, state_src, _settings_manager);
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
|
||||
_vm = new MarketViewVM(
|
||||
_bus,
|
||||
_orders_model,
|
||||
_bus.subscribe(update_securities_cmd.tag)
|
||||
.StartWith(_state.security_criteria)
|
||||
.SelectManyInSeries(c => c.subscribe_md_map(_bus))
|
||||
.BufferLatest(TimeSpan.FromSeconds(1))
|
||||
.Table(SecurityMD.key_fn)
|
||||
.Where(t => t.has_changes())
|
||||
.OnDispatcher());
|
||||
_vm.load_filters(_state.filters);
|
||||
_view = new MarketViewView { DataContext = _vm };
|
||||
_view._grid.Sorting += on_grid_sorting;
|
||||
_vm.sorting_hierarchy = DIYGridSortHelper.convert_sorting_criterion<MarketViewView.Columns>(_state.sorting_criterion).ToList();
|
||||
_vm.SubscribeDisposed(
|
||||
DIYGridSortHelper.observe_active_comparers<MarketViewView.Columns, MarketViewVM.Row>(_view._grid, MarketViewVM.get_column_comparer)
|
||||
.OnDispatcher()
|
||||
.Subscribe(sh => _vm.sorting_hierarchy = sh.Select(s => (s.key, s.direction)).ToList()));
|
||||
_grid_settings_manager = new SimpleGridSettingsManager<MarketViewVM.Row>(_vm.columns, _col_settings_flags, _vm.rows);
|
||||
_grid_settings_manager.load_from(_settings_manager.current_settings());
|
||||
/// The 'CanExecute' plumbing for the cmds below is designed to avoid potentially costly .ToList() operations on the selected_rows/securities.
|
||||
/// Some of the cmds post ITags with data that is, or has, a SecurityCriteria member; the SecurityCriteria constructor creates a new List of the specified securities.
|
||||
/// For these commands, the objective is to evaluate 'CanExecute' without instantiating the SecurityCriteria (or more importantly, without creating a new List).
|
||||
@@ -124,7 +138,7 @@ namespace mbts.spark {
|
||||
var show_synth_create_actual_cmd = SparkCommands.ShowSyntheticCreate();
|
||||
var show_synth_create_cmd_wrapper = new SparkCommandCommand(show_synth_create_actual_cmd.title, show_synth_create_actual_cmd.icon, show_synth_create_cmd, SparkCommands.KeyGestures.ShowSyntheticCreate);
|
||||
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
|
||||
var show_mini_ob_cmd = ObservantCommand.create<SecurityMD?, SecurityMD>(_view.show_mini_orderbook, s => s.HasValue, s => s.Value, out var show_mini_ob_observer);
|
||||
// commands
|
||||
var current_sec_md_src = Observable.Create<SecurityMD?>(o => {
|
||||
notify(null, null);
|
||||
@@ -139,6 +153,7 @@ namespace mbts.spark {
|
||||
_commands = new List<SparkCommand> {
|
||||
SparkCommands.ShowLadders(SparkCommands.KeyGestures.ShowLadder).complete(_vm.selected_rows.Observe().Select(_ => _vm.selected_rows.WrapSelectorRO(r => r.security_md)), smds => smds.Count > 0),
|
||||
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(current_sec_md_src, md => md.HasValue, ConvertUtils.ValueOrDefault),
|
||||
new SparkCommandCommand("Working Orders", Icons.Orders, show_mini_ob_cmd),
|
||||
show_fills_cmd_wrapper,
|
||||
show_tas_cmd_wrapper,
|
||||
show_synth_create_cmd_wrapper,
|
||||
@@ -149,7 +164,7 @@ namespace mbts.spark {
|
||||
_settings_manager,
|
||||
us => us.market_view_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
s => _vm.grid_settings_manager.create_vm(
|
||||
s => _grid_settings_manager.create_vm(
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.with(_vm.columns.Select(c => c.create()))
|
||||
@@ -163,8 +178,11 @@ namespace mbts.spark {
|
||||
};
|
||||
//
|
||||
_vm.selected_rows.CollectionChanged += on_selection_changed;
|
||||
_subscriptions = new CompositeDisposable(_bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(c =>
|
||||
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.get_filter_states(), _vm.grid_settings_manager.create())))) {
|
||||
_subscriptions = new CompositeDisposable(
|
||||
current_sec_md_src.Subscribe(show_mini_ob_observer),
|
||||
_settings_manager.subscribe_current().OnDispatcher().Subscribe(new CompositeObserver<GridSettings>(_grid_settings_manager) {
|
||||
s => _vm.selection_highlight = s.selection_highlight
|
||||
})) {
|
||||
() => {
|
||||
_vm.selected_rows.CollectionChanged -= on_selection_changed;
|
||||
_view._grid.Sorting -= on_grid_sorting;
|
||||
|
||||
@@ -89,7 +89,19 @@ namespace bts.shell {
|
||||
}
|
||||
|
||||
|
||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion, enable_background_color: true);
|
||||
public static GridSettings DefaultGridSettings =>
|
||||
GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion, enable_background_color: true);
|
||||
|
||||
public static fills.FillsSettings DefaultAuxiliaryFills {
|
||||
get {
|
||||
var col_keys = new HashSet<mbts.ui.spark.FillsView.Columns>(
|
||||
fills.FillsSettings.mini_column_keys);
|
||||
col_keys.Add(mbts.ui.spark.FillsView.Columns.Account);
|
||||
var rv = fills.FillsSettings.Default();
|
||||
rv.grid = fills.FillsSettings.create_grid(col_keys);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
|
||||
|
||||
|
||||
@@ -47,13 +47,13 @@ namespace mbts.spark {
|
||||
add_col(Columns.Name, DefaultColumnHeaders.Name, r => r.name, nameof(Row.name), name_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.Security);
|
||||
add_col(Columns.Description, DefaultColumnHeaders.Description, r => r.description, nameof(Row.description), description_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.Description);
|
||||
add_col(Columns.n_legs, DefaultColumnHeaders.N_Legs, r => r.n_legs.ToString(), nameof(Row.n_legs), n_legs_filter, DataGridFilterKnownTemplates.IntMinMax, DefaultColumnWidths.N_Legs);
|
||||
columns.Add(new HyperlinkElementColumn<Row, Columns>(
|
||||
columns.Add(new HyperlinkElementColumn<Columns>(
|
||||
Columns.n_working_orders,
|
||||
new Binding(nameof(Row.n_working_orders)),
|
||||
new Binding(nameof(Row.show_order_book)),
|
||||
n_working_orders_filter,
|
||||
DataGridFilterKnownTemplates.UIntMinMax) {
|
||||
id = col_id(Columns.n_working_orders),
|
||||
CommandBinding = new Binding(nameof(Row.show_order_book)),
|
||||
TextBinding = new Binding(nameof(Row.n_working_orders)),
|
||||
Header = DefaultColumnHeaders.N_Working_Orders,
|
||||
Width = DefaultColumnWidths.N_Working_Orders
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.ui;
|
||||
using bts.collections;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
@@ -34,10 +35,26 @@ namespace bts.shell.fills {
|
||||
|
||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
|
||||
public static GridSettings MiniGridSettings => GridSettings.Default.with(DefaultColumns.Mini.all, DefaultColumns.Mini.sorting_criterion);
|
||||
public static IEnumerable<Columns> mini_column_keys => DefaultColumns.Mini.mini_cols_data.Keys;
|
||||
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
|
||||
public static FillsSettings Default() =>
|
||||
new FillsSettings(null, null, new FillsFilterStates(), null);
|
||||
|
||||
public static GridSettings create_grid(IEnumerable<Columns> keys) {
|
||||
var key_set = keys as ISet<Columns> ?? new HashSet<Columns>(keys.empty_if_null());
|
||||
var grid = DefaultGridSettings;
|
||||
var cols = new LazyMutatorList<GridColumnSettings>(grid.columns);
|
||||
for (int i = 0; i < cols.Count; i++) {
|
||||
var c = cols[i];
|
||||
if (c.is_visible != key_set.Contains(c.strong_id<Columns>()))
|
||||
cols[i] = c.with(is_visible: !c.is_visible);
|
||||
}
|
||||
if (cols.IsMutated)
|
||||
grid = grid.with(cols);
|
||||
return grid;
|
||||
}
|
||||
|
||||
static class DefaultColumns {
|
||||
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings symbol => new GridColumnSettings(col_id(Columns.Symbol), DefaultColumnHeaders.Symbol, width: DefaultColumnWidths.Symbol);
|
||||
@@ -83,21 +100,21 @@ namespace bts.shell.fills {
|
||||
}
|
||||
|
||||
public static class Mini {
|
||||
static readonly IReadOnlyDictionary<Columns, (bool is_visible, double? width_override)> _mini_cols_data =
|
||||
public static readonly IReadOnlyDictionary<Columns, (bool is_visible, double? width_override)> mini_cols_data =
|
||||
new Dictionary<Columns, (bool is_visible, double? width_override)> {
|
||||
{ Columns.Security, (true, null) },
|
||||
{ Columns.FillTime, (true, ShortColumnWidths.Time) },
|
||||
{ Columns.OrderType, (true, ShortColumnWidths.OrderType) },
|
||||
{ Columns.Side, (true, ShortColumnWidths.Side) },
|
||||
{ Columns.Size, (true, ShortColumnWidths.Size) },
|
||||
{ Columns.Price, (true, ShortColumnWidths.Price) },
|
||||
{ Columns.Price, (true, ShortColumnWidths.Price) }
|
||||
};
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all => DefaultColumns.all.Select(_convert);
|
||||
public static IEnumerable<SortingCriteria> sorting_criterion => default_sorting_criterion.Where(c => _try_get_data(c.col_name, out (bool is_visible, double? _) d) && d.is_visible);
|
||||
|
||||
static bool _try_get_data(string id_str, out (bool is_visible, double? width_override) data) {
|
||||
if (Enum.TryParse(id_str, out Columns k) && _mini_cols_data.TryGetValue(k, out data))
|
||||
if (Enum.TryParse(id_str, out Columns k) && mini_cols_data.TryGetValue(k, out data))
|
||||
return true;
|
||||
data = default;
|
||||
return false;
|
||||
@@ -110,4 +127,4 @@ namespace bts.shell.fills {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,5 +70,21 @@ namespace mbts.ui {
|
||||
}, () => Disposable.dispose(ref can_execute_sub));
|
||||
return cmd;
|
||||
}
|
||||
|
||||
public static ICommand create<T, TResult>(Action<TResult> execute, Func<T, bool> get_can_execute, Func<T, TResult> convert, out IObserver<T> observer) {
|
||||
TResult param = default;
|
||||
bool can_execute = false;
|
||||
var cmd = new DelegateCommand(() => execute(param), () => can_execute);
|
||||
IDisposable can_execute_sub = null;
|
||||
observer = new AnonymousObserver<T>(val => {
|
||||
var could_execute = can_execute;
|
||||
can_execute = get_can_execute(val);
|
||||
if (can_execute)
|
||||
param = convert(val);
|
||||
if (can_execute != could_execute)
|
||||
cmd.RaiseCanExecuteChanged();
|
||||
}, () => Disposable.dispose(ref can_execute_sub));
|
||||
return cmd;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,7 @@ namespace mbts.ui.controls {
|
||||
_border_stroke_pen_backing;
|
||||
|
||||
static HyperlinkCell() {
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(HyperlinkCell), new FrameworkPropertyMetadata(typeof(HyperlinkCell)));
|
||||
IsMouseOverClickAreaProperty = IsMouseOverClickAreaPropertyKey.DependencyProperty;
|
||||
WPFHelper.override_metadata<HorizontalAlignment, HyperlinkCell>(
|
||||
HorizontalAlignmentProperty,
|
||||
|
||||
+34
-15
@@ -1,41 +1,52 @@
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui.converters;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
partial class DIYGrid {
|
||||
internal class HyperlinkElementColumn<TRowVM, TKey> : FrameworkElementColumnBase<TKey>, IFrameworkElementColumn, IFilterableColumn, IKeyedColumn<TKey> {
|
||||
internal class HyperlinkElementColumn<TKey> : ColumnBase<TKey>, IFrameworkElementColumn, IFilterableColumn, IKeyedColumn<TKey> {
|
||||
static readonly Thickness _default_padding = new Thickness(6, 0, 6, 0);
|
||||
readonly BindingBase _binding, _command_binding;
|
||||
|
||||
public HyperlinkElementColumn(
|
||||
TKey key, BindingBase binding, BindingBase command_binding, IDataGridColumnFilter filter, string filter_template) : base(key) {
|
||||
public HyperlinkElementColumn(TKey key, IDataGridColumnFilter filter, string filter_template)
|
||||
: base(key) {
|
||||
this.filter = filter;
|
||||
this.filter_template = filter_template;
|
||||
_binding = binding;
|
||||
_command_binding = command_binding;
|
||||
}
|
||||
|
||||
public IDataGridColumnFilter filter { get; }
|
||||
public string filter_template { get; }
|
||||
public Thickness padding { get; set; } = _default_padding;
|
||||
public HyperlinkCellClickMode HyperlinkClickMode { get; set; }
|
||||
public event Action<HyperlinkCell> view_generated;
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
public Style ElementStyle { get; set; }
|
||||
public IDataGridColumnFilter filter { get; set; }
|
||||
public string filter_template { get; set; }
|
||||
public HyperlinkCellClickMode HyperlinkClickMode { get; set; }
|
||||
public Thickness padding { get; set; } = _default_padding;
|
||||
// bindings
|
||||
public BindingBase CommandBinding { get; set; }
|
||||
public BindingBase CommandParameterBinding { get; set; }
|
||||
public BindingBase TextBinding { get; set; }
|
||||
|
||||
public FrameworkElement view_factory() {
|
||||
var hl = new HyperlinkCell {
|
||||
HyperlinkClickMode = HyperlinkClickMode,
|
||||
Padding = padding
|
||||
};
|
||||
hl.SetCurrentValue(DIYGrid.ColumnProperty, this);
|
||||
// bindings
|
||||
if (TextBinding != null)
|
||||
hl.SetBinding(HyperlinkCell.TextProperty, TextBinding);
|
||||
if (CommandBinding != null)
|
||||
hl.SetBinding(HyperlinkCell.CommandProperty, CommandBinding);
|
||||
if (CommandParameterBinding != null)
|
||||
hl.SetBinding(HyperlinkCell.CommandParameterProperty, CommandParameterBinding);
|
||||
hl.SetBinding(HyperlinkCell.ForegroundProperty, new Binding(nameof(font_brush)) { Source = this });
|
||||
hl.SetBinding(HyperlinkCell.TextProperty, _binding);
|
||||
hl.SetBinding(HyperlinkCell.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
|
||||
hl.SetBinding(HyperlinkCell.FontSizeProperty, new Binding(nameof(font_size)) { Source = this });
|
||||
hl.SetBinding(HyperlinkCell.HorizontalContentAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
|
||||
hl.SetBinding(HyperlinkCell.CommandProperty, _command_binding);
|
||||
hl.SetBinding(HyperlinkCell.WidthProperty, new Binding(nameof(ActualWidth)) { Source = this });
|
||||
hl.SetBinding(HyperlinkCell.BackgroundProperty, new Binding(nameof(background_brush)) { Source = this });
|
||||
hl.SetBinding(FrameworkElement.WidthProperty, new Binding(nameof(ActualWidth)) { Source = this });
|
||||
hl.SetBinding(
|
||||
CursorProperty,
|
||||
new Binding {
|
||||
@@ -46,6 +57,14 @@ namespace mbts.ui.controls {
|
||||
ValueForTrue = Cursors.Hand
|
||||
}
|
||||
});
|
||||
// background
|
||||
var bg_brush = new SolidColorBrush();
|
||||
BindingOperations.SetBinding(bg_brush, SolidColorBrush.ColorProperty, new Binding(nameof(background_color)) { Source = this });
|
||||
hl.SetCurrentValue(HyperlinkCell.BackgroundProperty, bg_brush);
|
||||
// style
|
||||
if (ElementStyle != null)
|
||||
hl.Style = ElementStyle;
|
||||
view_generated?.Invoke(hl);
|
||||
return hl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +269,7 @@ namespace mbts.ui.controls {
|
||||
_rsa = new RowSelectionAdorner(this, _scroll_viewer);
|
||||
_rsa.bind(RowSelectionAdorner.FillProperty, _highlight_path, DIYGrid.SelectionHighlightBrushConverter, _grid);
|
||||
_rsa.bind(RowSelectionAdorner.StrokeProperty, _highlight_path, DIYGrid.SelectionHighlightPenConverter, _grid);
|
||||
_rsa.bind(OpacityProperty, _active_path, DIYGrid.SelectionHighlightOpacityConverter, _grid);
|
||||
_rsa.bind(OpacityProperty, _active_path, DIYGrid.SelectionHighlightOpacityConverter, this);
|
||||
_rsa.attach();
|
||||
// cell hover adorner
|
||||
_cell_hover_adorner = new DIYGridCellHoverAdorner(this, _scroll_viewer);
|
||||
@@ -924,6 +924,14 @@ namespace mbts.ui.controls {
|
||||
// Public Properties
|
||||
#region Public Properties
|
||||
|
||||
#region Column
|
||||
public static readonly DependencyProperty ColumnProperty =
|
||||
DependencyProperty.RegisterAttached("Column", typeof(ColumnBase), typeof(DIYGrid));
|
||||
|
||||
public static ColumnBase GetColumn(DependencyObject d) => d.GetValue(ColumnProperty) as ColumnBase;
|
||||
public static void SetColumn(DependencyObject d, ColumnBase value) => d.SetValue(ColumnProperty, value);
|
||||
#endregion
|
||||
|
||||
#region ColumnKey
|
||||
public static readonly DependencyProperty ColumnKeyProperty =
|
||||
DependencyProperty.RegisterAttached("ColumnKey",
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
<ResourceDictionary
|
||||
x:Class="mbts.ui.spark.OrderTicketResources"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:msys="clr-namespace:bts.api;assembly=spark_api"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Resources/Buttons.xaml"/>
|
||||
<ResourceDictionary Source="/view/Resources/Converters.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<sys:Double x:Key="HorizontalSpacing">8.0</sys:Double>
|
||||
<sys:Double x:Key="VerticalSpacing">4.0</sys:Double>
|
||||
<sys:Double x:Key="ButtonHeight">22.0</sys:Double>
|
||||
|
||||
<Style x:Key="SellButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Background" Value="{u:BTSColor MarketData.ask}"/>
|
||||
<Setter Property="BorderBrush" Value="{u:BTSColor UI.Button.normal_border}"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="MinHeight" Value="40"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center" Foreground="{u:BTSColor UI.alternate_text}">Sell</TextBlock>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template" Value="{StaticResource SellButtonTemplate}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="BuyButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Background" Value="{u:BTSColor MarketData.bid}"/>
|
||||
<Setter Property="BorderBrush" Value="{u:BTSColor UI.Button.normal_border}"/>
|
||||
<Setter Property="Padding" Value="2"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="MinHeight" Value="40"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center" Foreground="{u:BTSColor UI.alternate_text}">Buy</TextBlock>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template" Value="{StaticResource BuyButtonTemplate}"/>
|
||||
</Style>
|
||||
|
||||
<DataTemplate DataType="{x:Type sp:BBOVM}">
|
||||
<TextBlock Margin="2">
|
||||
<TextBlock.Inlines>
|
||||
<Run Text="{Binding security, Mode=OneWay}" FontWeight="Bold"/>
|
||||
<Run Foreground="{u:BTSColor MarketData.bid}" FontSize="10" Text="{Binding bid_size, Mode=OneWay, StringFormat='{}({0})'}"/>
|
||||
<Run Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
|
||||
<Run.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="bid"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</Run.Text>
|
||||
</Run>
|
||||
<Run Foreground="{u:BTSColor MarketData.ask}" FontWeight="Bold">
|
||||
<Run.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="ask"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</Run.Text>
|
||||
</Run>
|
||||
<Run Foreground="{u:BTSColor MarketData.ask}" FontSize="10" Text="{Binding ask_size, Mode=OneWay, StringFormat='{}({0})'}"/>
|
||||
</TextBlock.Inlines>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type sp:NewOrderTicketVM}">
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition MinWidth="120"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSpacing}}" MaxWidth="{Binding Source={StaticResource HorizontalSpacing}}"/>
|
||||
<ColumnDefinition MinWidth="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<usp:OrderModificationControl Grid.ColumnSpan="3"/>
|
||||
<Button Grid.Row="2" Style="{StaticResource BuyButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Buy, Type=msys:Side}"/>
|
||||
<Button Grid.Row="2" Grid.Column="2" Style="{StaticResource SellButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Sell, Type=msys:Side}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type sp:ExistingOrderTicketVM}">
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition MinWidth="120"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSpacing}}"/>
|
||||
<ColumnDefinition MinWidth="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<usp:OrderModificationControl Grid.ColumnSpan="3"/>
|
||||
<Button
|
||||
Grid.Row="2"
|
||||
Command="{Binding modify_order}"
|
||||
IsDefault="True"
|
||||
MinHeight="40"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" FontWeight="Bold">OK</TextBlock>
|
||||
<TextBlock
|
||||
Grid.Row="1" HorizontalAlignment="Center" Margin="0,2,0,0" FontSize="9" Text="{Binding dirty_items_text}"
|
||||
Visibility="{Binding dirty_items_text, Converter={StaticResource DirtyItemsVisibilityConverter}}"
|
||||
/>
|
||||
</Grid>
|
||||
</Button>
|
||||
<Button
|
||||
Grid.Row="2"
|
||||
Grid.Column="2"
|
||||
Command="ApplicationCommands.Close"
|
||||
FontWeight="Bold"
|
||||
IsCancel="True"
|
||||
MinHeight="40"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,10 @@
|
||||
using mbts.ui.utility;
|
||||
using System.Windows;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
partial class OrderTicketResources : ResourceDictionary {
|
||||
public OrderTicketResources() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.converters {
|
||||
class ColorBlendConverter : IValueConverter {
|
||||
public Color Overlay { get; set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
value is Color color ? color.blend(Overlay) : Overlay;
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
class ColorWithConverter : IValueConverter {
|
||||
public byte? A { get; set; }
|
||||
public byte? R { get; set; }
|
||||
public byte? G { get; set; }
|
||||
public byte? B { get; set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
value is Color c ? c.with(A, R, G, B) : value;
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
class ColorBlendMultiConverter : IMultiValueConverter {
|
||||
public static ColorBlendMultiConverter Instance => new ColorBlendMultiConverter();
|
||||
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) =>
|
||||
values.OfType<Color>().Aggregate(Colors.Transparent, WPFExtensions.blend);
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,7 @@ namespace mbts.ui {
|
||||
public static IObservable<Point?> subscribe_drag_coordinates(
|
||||
FrameworkElement element,
|
||||
FrameworkElement init_element = null,
|
||||
Rect? drag_start_bounds = null,
|
||||
bool use_preview_events = false,
|
||||
MouseButton mouse_button = MouseButton.Left) {
|
||||
if (element == null)
|
||||
@@ -80,7 +81,6 @@ namespace mbts.ui {
|
||||
init_element = element;
|
||||
return Observable.Create<Point?>(o => {
|
||||
Point? mouse_down = null, drag_start = null;
|
||||
bool is_explict_drag = false;
|
||||
if (use_preview_events) {
|
||||
init_element.PreviewMouseDown += on_mouse_down;
|
||||
element.PreviewMouseMove += on_mouse_move;
|
||||
@@ -104,7 +104,6 @@ namespace mbts.ui {
|
||||
// callbacks
|
||||
void on_mouse_up(object sender, MouseButtonEventArgs e) {
|
||||
mouse_down = null;
|
||||
is_explict_drag = false;
|
||||
if (drag_start.HasValue)
|
||||
o.OnNext(drag_start = null);
|
||||
}
|
||||
@@ -118,15 +117,15 @@ namespace mbts.ui {
|
||||
e.Handled = true;
|
||||
drag_start = curr_pt;
|
||||
mouse_down = null;
|
||||
is_explict_drag = false;
|
||||
o.OnNext(curr_pt);
|
||||
}
|
||||
}
|
||||
}
|
||||
void on_mouse_down(object sender, MouseButtonEventArgs e) {
|
||||
if (!is_explict_drag) {
|
||||
var pt = e.GetPosition(init_element);
|
||||
if (!drag_start_bounds.HasValue || drag_start_bounds.Value.Contains(pt)) {
|
||||
drag_start = null;
|
||||
mouse_down = e.GetPosition(element);
|
||||
mouse_down = pt;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -146,10 +146,14 @@ namespace mbts.ui.extensions {
|
||||
element.Loaded += on_loaded;
|
||||
if (disconnect_on_unloaded)
|
||||
element.Unloaded += on_unloaded;
|
||||
if (element.IsLoaded)
|
||||
connection = connect();
|
||||
return Disposable.Create(dispose);
|
||||
|
||||
void on_loaded(object sender, RoutedEventArgs e) =>
|
||||
Disposable.replace(ref connection, connect());
|
||||
void on_loaded(object sender, RoutedEventArgs e) {
|
||||
connection?.Dispose();
|
||||
connection = connect();
|
||||
}
|
||||
|
||||
void on_unloaded(object sender, RoutedEventArgs e) =>
|
||||
Disposable.dispose(ref connection);
|
||||
|
||||
@@ -132,8 +132,23 @@ namespace mbts.ui {
|
||||
return brush;
|
||||
}
|
||||
|
||||
public static Color with(this Color color, byte alpha) =>
|
||||
Color.FromArgb(alpha, color.R, color.G, color.B);
|
||||
public static Color blend(this Color color, Color other) {
|
||||
// it doesn't make sense to avg the Alpha value (layering transparent over an opaque color should yield the opaque color).
|
||||
// just apply the max Alpha to the resultant color
|
||||
if (color.A == 0)
|
||||
return other;
|
||||
if (other.A == 0)
|
||||
return color;
|
||||
var color_opac = color.A / 255.0;
|
||||
var other_opac = other.A / 255.0;
|
||||
var avg_red = (byte)new[] { (color.R, color.A), (other.R, other.A) }.weighted_average(tup => tup.R, tup => tup.A);
|
||||
var avg_green = (byte)new[] { (color.G, color.A), (other.G, other.A) }.weighted_average(tup => tup.G, tup => tup.A);
|
||||
var avg_blue = (byte)new[] { (color.B, color.A), (other.B, other.A) }.weighted_average(tup => tup.B, tup => tup.A);
|
||||
return Color.FromArgb(Math.Max(color.A, other.A), avg_red, avg_green, avg_blue);
|
||||
}
|
||||
|
||||
public static Color with(this Color color, byte? alpha = null, byte? red = null, byte? green = null, byte? blue = null) =>
|
||||
Color.FromArgb(alpha ?? color.A, red ?? color.R, green ?? color.G, blue ?? color.B);
|
||||
|
||||
public static SolidColorBrush with(this SolidColorBrush brush, double opacity) {
|
||||
brush.Opacity = opacity;
|
||||
|
||||
@@ -595,6 +595,54 @@
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<ControlTemplate x:Key="BuyButtonTemplate" TargetType="Button">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
Background="{TemplateBinding Background}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="True"
|
||||
>
|
||||
<ContentPresenter
|
||||
Name="button_content"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
RecognizesAccessKey="True"
|
||||
/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="#99167ed3"/>
|
||||
<Setter TargetName="Border" Property="BorderBrush" Value="{mbts:BTSColor UI.Button.mouseover_border}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
|
||||
<ControlTemplate x:Key="SellButtonTemplate" TargetType="Button">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
Background="{TemplateBinding Background}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="True"
|
||||
>
|
||||
<ContentPresenter
|
||||
Name="button_content"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
RecognizesAccessKey="True"
|
||||
/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="#99c00000" />
|
||||
<Setter TargetName="Border" Property="BorderBrush" Value="{mbts:BTSColor UI.Button.mouseover_border}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style x:Key="CommonButton" BasedOn="{StaticResource {x:Type Button}}" TargetType="{x:Type Button}">
|
||||
<Setter Property="MinWidth" Value="60" />
|
||||
<Setter Property="Margin" Value="4" />
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
namespace mbts.ui {
|
||||
static class VisualStateHelper {
|
||||
public static VisualStateGroup create_group(string name, params VisualState[] states) {
|
||||
var vsg = new VisualStateGroup { Name = name };
|
||||
for (int i = 0; i < states.Length; i++)
|
||||
vsg.States.Add(states[i]);
|
||||
return vsg;
|
||||
}
|
||||
|
||||
public static VisualState create_state(string name, params Timeline[] timelines) {
|
||||
var sb = new Storyboard();
|
||||
for (int i = 0; i < timelines.Length; i++)
|
||||
sb.Children.Add(timelines[i]);
|
||||
return new VisualState {
|
||||
Name = name,
|
||||
Storyboard = sb
|
||||
};
|
||||
}
|
||||
|
||||
public static ColorAnimation create_color_animation(UIElement target, PropertyPath target_path) {
|
||||
var timeline = new ColorAnimation {
|
||||
BeginTime = TimeSpan.Zero,
|
||||
Duration = TimeSpan.Zero
|
||||
};
|
||||
Storyboard.SetTarget(timeline, target);
|
||||
Storyboard.SetTargetProperty(timeline, target_path);
|
||||
return timeline;
|
||||
}
|
||||
|
||||
public static ColorAnimation create_color_animation(UIElement target, PropertyPath target_path, Color color) {
|
||||
var timeline = create_color_animation(target, target_path);
|
||||
timeline.To = color;
|
||||
return timeline;
|
||||
}
|
||||
|
||||
public static ColorAnimation create_color_animation(UIElement target, PropertyPath target_path, BindingBase color_binding) {
|
||||
var timeline = create_color_animation(target, target_path);
|
||||
BindingOperations.SetBinding(timeline, ColorAnimation.ToProperty, color_binding);
|
||||
return timeline;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,28 @@ namespace mbts.ui {
|
||||
static class WPFHelper {
|
||||
public delegate void PropertyChangedCallback<TValue, TOwner>(TOwner owner, TValue old_value, TValue new_value);
|
||||
|
||||
public static CoerceValueCallback coerce_value_callback<T>(T fallback = default) =>
|
||||
(d, base_value) => base_value is T v ? v : fallback;
|
||||
|
||||
public static CoerceValueCallback coerce_mutable_freezable<T>(T fallback = default) where T : Freezable {
|
||||
Func<T> get_fallback;
|
||||
if (fallback == null) {
|
||||
get_fallback = () => null;
|
||||
} else {
|
||||
fallback.Freeze();
|
||||
get_fallback = () => (T)fallback.Clone();
|
||||
}
|
||||
return coerce_mutable_freezable(get_fallback);
|
||||
}
|
||||
|
||||
public static CoerceValueCallback coerce_mutable_freezable<T>(Func<T> get_fallback) where T : Freezable =>
|
||||
(d, base_value) => {
|
||||
var rv = base_value as T ?? get_fallback();
|
||||
if (rv != null && rv.IsFrozen)
|
||||
rv = (T)rv.Clone();
|
||||
return rv;
|
||||
};
|
||||
|
||||
public static DependencyProperty create_dp<TOwner>(string name, Action<TOwner, object> callback, object default_value = null) =>
|
||||
DependencyProperty.Register(name, typeof(object), typeof(TOwner), new PropertyMetadata(default_value, (d, e) => callback(ConvertUtils.try_convert<TOwner>(d), e.NewValue)));
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace bts.collections {
|
||||
/// <summary>
|
||||
/// Implements both <see cref="IReadOnlyList{T}"/> and <see cref="IList{T}"/>, as a wrapper around a source list.
|
||||
/// When (not before) a mutation method of the latter interface is called (for the first time), the source list will be copied to a new instance of <see cref="List{T}"/>,
|
||||
/// which will become the target of all subsequent calls to methods of either interface.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the list.</typeparam>
|
||||
class LazyMutatorList<T> : IList<T>, IReadOnlyList<T> {
|
||||
IReadOnlyList<T> _result;
|
||||
List<T> _mutable_backing;
|
||||
|
||||
public LazyMutatorList(IReadOnlyList<T> source) =>
|
||||
_result = source ?? throw new ArgumentNullException(nameof(source));
|
||||
|
||||
public T this[int index] {
|
||||
get => _result[index];
|
||||
set => _mutable[index] = value;
|
||||
}
|
||||
|
||||
public int Count => _result.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public bool IsMutated => _mutable_backing != null;
|
||||
|
||||
public IReadOnlyList<T> Result => _result;
|
||||
|
||||
public List<T> MutatedResult => _mutable_backing ?? throw new InvalidOperationException("List has not been mutated.");
|
||||
|
||||
IList<T> _mutable {
|
||||
get {
|
||||
if (_mutable_backing == null) {
|
||||
_mutable_backing = new List<T>(_result.Count);
|
||||
for (int i = 0; i < _result.Count; i++)
|
||||
_mutable.Add(_result[i]);
|
||||
_result = _mutable_backing;
|
||||
}
|
||||
return _mutable_backing;
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(T item) =>
|
||||
_mutable.Add(item);
|
||||
|
||||
public void Clear() =>
|
||||
_mutable.Clear();
|
||||
|
||||
public bool Contains(T item) =>
|
||||
IndexOf(item) >= 0;
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex) {
|
||||
var r = _result;
|
||||
for (int i = 0; i < r.Count; i++)
|
||||
array[i + arrayIndex] = r[i];
|
||||
}
|
||||
|
||||
public int IndexOf(T item) {
|
||||
var r = _result;
|
||||
for (int i = 0; i < r.Count; i++) {
|
||||
if (EqualityComparer<T>.Default.Equals(r[i], item))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void Insert(int index, T item) =>
|
||||
_mutable.Insert(index, item);
|
||||
|
||||
public bool Remove(T item) =>
|
||||
_mutable.Remove(item);
|
||||
|
||||
public void RemoveAt(int index) =>
|
||||
_mutable.RemoveAt(index);
|
||||
|
||||
public IEnumerator<T> GetEnumerator() =>
|
||||
(_mutable_backing ?? _result).GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user