Compare commits

...
Author SHA1 Message Date
Jake Norbut 7eb3299006 fix merge 2019-09-06 11:11:56 -05:00
Jake Norbut 7be1b658b4 don't crash when MV runs on MultiThreadedWindows; fix for demo, not sure about long-term yet 2019-09-03 17:41:26 -05:00
Jake Norbut 06094f9ecb PR feedback - eliminate use of Tag in side binding 2019-08-29 17:17:11 -05:00
Jake Norbut 110ed184b9 fixing merge conflict 2019-08-29 17:07:38 -05:00
Jake Norbut 7e4d447572 Grid row selection fix for OnMouseDown over MVHyperlinkElementColumn cells 2019-08-29 17:04:00 -05:00
Jake Norbut e3616e3514 PR feedback - tuple destructuring, renaming one parameter to triplet b/c it has 3 fields 2019-08-29 16:43:29 -05:00
Jake Norbut 3cc8921684 refining coord calculation; adding comments 2019-08-29 16:33:38 -05:00
Jake Norbut c9b3cd1560 adding broker mode order ticket tests to market view test 2019-08-29 13:42:32 -05:00
Jake Norbut 5edd274144 adding Title to Order Ticket window so we see something in taskbar hover 2019-08-29 10:58:21 -05:00
Jake Norbut 596d800cfb hover effect is not enabled by default for regular Hyperlink Column; MVHyperlink Column enables hover effect 2019-08-29 09:08:51 -05:00
Jake Norbut 7b7ae9d7e2 making hover effects optional 2019-08-29 09:07:22 -05:00
Jake Norbut a1663c9d06 Merge branch 'master' of bitbucket.org:bluetradesys/spark into SP-187-mv_order_ticket 2019-08-28 19:17:35 -05:00
Jake Norbut a8e043c600 Merge branch 'master' of bitbucket.org:bluetradesys/spark into SP-187-mv_order_ticket 2019-08-28 15:36:38 -05:00
Jake Norbut d02fba8145 hookup various modification buttons to appropriate new commands 2019-08-28 15:35:15 -05:00
Jake Norbut aacb19990b broker mode side is now a Property; add commands to facilitate changing price_side and size_price_side 2019-08-28 15:30:33 -05:00
Jake Norbut 8686287def view takes into account various broker mode properties and hides/repositions buy/sell buttons 2019-08-26 10:23:59 -05:00
Jake Norbut 50232dec55 adding new broker-mode related properties 2019-08-26 10:23:19 -05:00
Jake Norbut c236b30a50 subscribe to new tag; callbacks for opening at position 2019-08-26 09:50:12 -05:00
Jake Norbut 7881601066 coordinate math relative to _grid and post on new tag 2019-08-26 09:48:48 -05:00
Jake Norbut 14990f2d8b update MarketView VM to use new MarketViewHyperlinkElementColumn 2019-08-26 09:47:41 -05:00
Jake Norbut 1c97374e4d add new event to vm for opening ticket at position 2019-08-26 09:46:46 -05:00
Jake Norbut ef386080d9 pass owning vm down to the market view row 2019-08-26 09:44:39 -05:00
Jake Norbut 21c9bfe910 Update DelegateCommands and callback to make sure of new Side and HyperlinkCell args; post to new spark tag 2019-08-26 09:43:02 -05:00
Jake Norbut cd053ee4db use more meaningful name in SparkTag 2019-08-26 09:39:11 -05:00
Jake Norbut ecacafca5a passing the WindowManager down to MarketViewWindow for eventual IShellWindow window coordinate calculations 2019-08-26 09:29:52 -05:00
Jake Norbut e3361e53ee adding GetPosition to the IWindowManager interface; WindowManager already implements this; this just makes it legit 2019-08-26 09:25:52 -05:00
Jake Norbut 9c8024da29 Creating Window ShowAtPosition that opens a window at the given coordinates vs relative to the mouse position 2019-08-26 09:23:03 -05:00
Jake Norbut 16ddcb33a3 adding new SparkTag for opening an OrderTicket at a specific Point 2019-08-26 09:21:25 -05:00
Jake Norbut 7ef89ef7c6 No right click ContextMenu on PriceEntryControl 2019-08-26 09:19:04 -05:00
Jake Norbut 77e9c1b6de adding oppsite(Side) ext method 2019-08-26 09:18:07 -05:00
Jake Norbut 711302262b Adding new MarketViewHyperlinkElementColumn that makes use of new HyperLinkRoutedCommands; adds Left/Right click mouse bindings 2019-08-26 09:17:24 -05:00
Jake Norbut 9ad3c5e34f adding new HyperLinkRoutedCommands that will execute the HyperLink's original Command and pass the Hyperlink itself as part of the CommandParameters 2019-08-26 09:15:47 -05:00
Jake Norbut 253941d686 qty receives kb focus on load; same inc/dec behavior as ladder 2019-08-22 10:26:28 -05:00
Jake Norbut 4413eb5ac6 no copy/paste ContextMenu on TickUpDownNullableUInt 2019-08-22 09:24:22 -05:00
Jake Norbut 4e9e25e883 only paint highlight and change mouse cursor when cell is populated 2019-08-22 08:19:50 -05:00
Jake Norbut cc2943494f working bids/asks are often empty, yet we launch blank OB window on errant clicks in these cells; link CanExexute to text not empty 2019-08-22 08:19:04 -05:00
Jake Norbut 859b9ac369 add highlight effect to bg and fg when IsMouseDirectlyOver 2019-08-22 08:00:56 -05:00
Jake Norbut 5365febd92 HyperlinkCell should launch Command whenever anywhere in the Cell is pressesd, not only when over the link text; update mouse hand to match 2019-08-21 12:25:46 -05:00
Jake Norbut cdb40d5b3d HyperlinkCell will now launch OrderTicket on any MouseDown/Up 2019-08-21 12:16:29 -05:00
18 changed files with 415 additions and 97 deletions

No files matched your search

+19 -2
View File
@@ -5,12 +5,14 @@ using bts.spark;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.spark;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Input;
using LadderSettings = mbts.spark.LadderSettings;
namespace bts.shell {
@@ -30,16 +32,23 @@ namespace bts.shell {
_error_reporter = error_reporter;
_dispose = Disposable.Create(
model.on_orders.OnDispatcher().Subscribe(o => _orders = o),
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m =>_md_map = m),
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m => _md_map = m),
bus.subscribe(SparkTags.ShowOrderTicketFromSecurityTag).OnDispatcher().Subscribe(_show_order_ticket),
bus.subscribe(SparkTags.ShowOrderTicketTag).OnDispatcher().Subscribe(_show_order_ticket),
bus.subscribe(SparkTags.ShowModifyOrderTag).OnDispatcher().Subscribe(_show_modify_order)
bus.subscribe(SparkTags.ShowModifyOrderTag).OnDispatcher().Subscribe(_show_modify_order),
bus.subscribe(SparkTags.ShowOrderTicketAtPositionTag).OnDispatcher().Subscribe(_show_order_ticket_at_position)
);
}
public void Dispose() =>
Disposable.dispose(ref _dispose);
void _show_order_window_at_position(Window window, Point window_start_position) {
foreach (var kv in AlgoStudioModelUtils.get_dynamic_resources(_model.algo_model.ui_configuration))
window.Resources[kv.Key] = kv.Value;
window.ShowAtPosition(window_start_position, true);
}
void _show_order_window(Window window) {
foreach (var kv in AlgoStudioModelUtils.get_dynamic_resources(_model.algo_model.ui_configuration))
window.Resources[kv.Key] = kv.Value;
@@ -60,6 +69,14 @@ namespace bts.shell {
});
}
void _show_order_ticket_at_position((OrderDefinitionImmutable def, double price, uint size, Point window_start_position) ops) {
if (_md_map.TryGetValue(ops.def.SecurityKey, out SecurityMD smd))
_show_order_window_at_position(new OrderTicket {
DataContext = new NewOrderTicketVM(_model, smd, ops.def, ops.price, ops.size,
Observable.Return(LadderSettings.Default.calculator_buttons), true, ops.def.Side)
}, ops.window_start_position);
}
void _show_modify_order(uint order_key) {
if (_orders.state.TryGetValue(order_key, out var ori) && ori.sec.HasValue
&& (ori.order.HasDef && ori.order.Def.HasCustomDef && !(ori.order.Def.CustomDef.HasStop || ori.order.Def.CustomDef.HasStopLimit))) {
@@ -19,9 +19,9 @@ namespace bts.shell
class MarketShellModule : IShellModule
{
[ImportingConstructor]
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import(LoginContainer.Exports.UserName)]string user_name) {
public MarketShellModule(IBus bus, IWindowManager wm, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import(LoginContainer.Exports.UserName)]string user_name) {
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub);
_mvf = new MarketViewWindowFactory(this, bus, settings_hub, orders_model);
_mvf = new MarketViewWindowFactory(this, bus, wm, settings_hub, orders_model);
}
TimeAndSalesWindowFactory _tsf;
@@ -15,6 +15,7 @@ using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Linq;
using System.Windows.Input;
using static mbts.ui.controls.DIYGrid;
namespace mbts.spark {
partial class MarketViewVM {
@@ -35,13 +36,15 @@ namespace mbts.spark {
readonly CellProperty<string> _working_bids_text, _working_asks_text;
readonly MyOrderSummarizer _order_summarizer = new MyOrderSummarizer();
readonly MDDisplayHelper.SizeFormatter _size_formatter = MDDisplayHelper.get_formatter(MarketSizeDisplay.get_default());
readonly MarketViewVM _owner;
Property<uint> _bid_direct_size, _bid_merged_size, _ask_direct_size, _ask_merged_size;
double _raw_day_position;
string _bid_merged_size_text, _ask_merged_size_text;
ulong _bbo_ref, _last_ref;
public Row(IBus bus, SecurityMD smd, OrdersModel orders_model) : base(smd) {
public Row(IBus bus, SecurityMD smd, OrdersModel orders_model, MarketViewVM owner) : base(smd) {
_bus = bus;
_owner = owner;
_orders_model = orders_model;
_security_text = smd.security.ToString();
_security_key_text = security_key.ToString();
@@ -75,16 +78,16 @@ namespace mbts.spark {
_working_asks_text = new CellProperty<string>(this, nameof(working_asks_text), string.Empty);
//
default_command = new DelegateCommand(_show_ladder);
bid_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Bid));
bid_merged_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidMergedSize));
bid_direct_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidDirectSize));
bid_implied_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidImpliedSize));
working_bids_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingBids));
ask_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Ask));
ask_merged_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskMergedSize));
ask_direct_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskDirectSize));
ask_implied_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskImpliedSize));
working_asks_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingAsks));
bid_price_click = new DelegateCommand<(Side side, HyperlinkCell hlc)>((args) => _show_order_ticket(MarketViewView.Columns.Bid, args), (args) => args.side != Side.Undefined && args.hlc != null);
bid_merged_size_click = new DelegateCommand<(Side side, HyperlinkCell hlc)>((args) => _show_order_ticket(MarketViewView.Columns.BidMergedSize, args), (args) => args.side != Side.Undefined && args.hlc != null);
bid_direct_size_click = new DelegateCommand<(Side side, HyperlinkCell hlc)>((args) => _show_order_ticket(MarketViewView.Columns.BidDirectSize, args), (args) => args.side != Side.Undefined && args.hlc != null);
bid_implied_size_click = new DelegateCommand<(Side side, HyperlinkCell hlc)>((args) => _show_order_ticket(MarketViewView.Columns.BidImpliedSize, args), (args) => args.side != Side.Undefined && args.hlc != null);
working_bids_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingBids), () => working_bids_text != string.Empty);
ask_price_click = new DelegateCommand<(Side side, HyperlinkCell hlc)>((args) => _show_order_ticket(MarketViewView.Columns.Ask, args), (args) => args.side != Side.Undefined && args.hlc != null);
ask_merged_size_click = new DelegateCommand<(Side side, HyperlinkCell hlc)>((args) => _show_order_ticket(MarketViewView.Columns.AskMergedSize, args), (args) => args.side != Side.Undefined && args.hlc != null);
ask_direct_size_click = new DelegateCommand<(Side side, HyperlinkCell hlc)>((args) => _show_order_ticket(MarketViewView.Columns.AskDirectSize, args), (args) => args.side != Side.Undefined && args.hlc != null);
ask_implied_size_click = new DelegateCommand<(Side side, HyperlinkCell hlc)>((args) => _show_order_ticket(MarketViewView.Columns.AskImpliedSize, args), (args) => args.side != Side.Undefined && args.hlc != null);
working_asks_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingAsks), () => working_asks_text != string.Empty);
_animation.AnimationsChanged += on_animations_changed;
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
_dispose = Disposable.Create(
@@ -217,38 +220,39 @@ namespace mbts.spark {
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
}
void _show_order_ticket(MarketViewView.Columns col) {
void _show_order_ticket(MarketViewView.Columns col, (Side side, HyperlinkCell hlc) args) {
var order = _orders_model.factory.order(security);
switch (col) {
case MarketViewView.Columns.Bid:
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, 0u));
_owner._raise_open_order_ticket_at_position((order.with(side: args.side), security, bid_price, 0u, args.hlc));
break;
case MarketViewView.Columns.BidMergedSize:
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_merged_size));
_owner._raise_open_order_ticket_at_position((order.with(side: args.side), security, bid_price, bid_merged_size, args.hlc));
break;
case MarketViewView.Columns.BidDirectSize:
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_direct_size));
_owner._raise_open_order_ticket_at_position((order.with(side: args.side), security, bid_price, bid_direct_size, args.hlc));
break;
case MarketViewView.Columns.BidImpliedSize:
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_implied_size));
_owner._raise_open_order_ticket_at_position((order.with(side: args.side), security, bid_price, bid_implied_size, args.hlc));
break;
case MarketViewView.Columns.Ask:
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Buy), ask_price, 0u));
_owner._raise_open_order_ticket_at_position((order.with(side: args.side), security, ask_price, 0u, args.hlc));
break;
case MarketViewView.Columns.AskMergedSize:
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Buy), ask_price, ask_merged_size));
_owner._raise_open_order_ticket_at_position((order.with(side: args.side), security, ask_price, ask_merged_size, args.hlc));
break;
case MarketViewView.Columns.AskDirectSize:
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), ask_price, ask_direct_size));
_owner._raise_open_order_ticket_at_position((order.with(side: args.side), security, ask_price, ask_direct_size, args.hlc));
break;
case MarketViewView.Columns.AskImpliedSize:
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), ask_price, ask_implied_size));
_owner._raise_open_order_ticket_at_position((order.with(side: args.side), security, ask_price, ask_implied_size, args.hlc));
break;
case MarketViewView.Columns.Last:
_bus.post(SparkTags.ShowOrderTicketTag, (order, last_price, 0u));
_owner._raise_open_order_ticket_at_position((order.with(side: args.side), security, last_price, 0u, args.hlc));
break;
case MarketViewView.Columns.LastSize:
_bus.post(SparkTags.ShowOrderTicketTag, (order, last_price, last_size));
_owner._raise_open_order_ticket_at_position((order.with(side: args.side), security, last_price, last_size, args.hlc));
break;
}
}
@@ -1,4 +1,5 @@
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.utils;
using mbts.persist;
@@ -22,6 +23,9 @@ namespace mbts.spark {
readonly OrdersModel _orders_model;
IWindowSettingsManager<GridSettings> _settings_manager;
public event Action<(OrderDefinitionImmutable order_def, SecurityImmutable security, double price, uint size, HyperlinkCell hlc)> open_order_ticket_at_position;
internal void _raise_open_order_ticket_at_position((OrderDefinitionImmutable order_def, SecurityImmutable security, double price, uint size, HyperlinkCell hlc) args) => open_order_ticket_at_position?.Invoke(args);
public MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IWindowSettingsManager<GridSettings> settings_manager )
: this(bus, orders_model, state, state.SelectManyInSeries(s => s.security_criteria.subscribe_md(bus)), settings_manager) { }
@@ -41,17 +45,17 @@ namespace mbts.spark {
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_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, Side.Buy, 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_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, Side.Buy, 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, Side.Buy, 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, Side.Sell, 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, Side.Sell, 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_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, Side.Sell, 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);
@@ -77,20 +81,20 @@ namespace mbts.spark {
horizontal_alignment = h_alignment
});
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, string command_prop, IDataGridColumnFilter filter, Color foreground, Color? background, Side market_side, System.Windows.HorizontalAlignment h_alignment) =>
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, market_side, h_alignment);
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 header, string sort_prop, string text_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Side market_side, 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, market_side, h_alignment);
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);
void add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Side market_side, 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, market_side, h_alignment);
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) {
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Color? background, Side market_side, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
columns.Add(new MarketViewHyperlinkElementColumn(
col_key, binding, new Binding(command_prop), filter, filter_template, market_side) {
// it's necessary to have this id populated for grid settings
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
Header = header,
@@ -271,7 +275,7 @@ namespace mbts.spark {
}
#endregion
protected override Row row_factory(SecurityMD smd) => new Row(_bus, smd, _orders_model);
protected override Row row_factory(SecurityMD smd) => new Row(_bus, smd, _orders_model, this);
protected override void on_row_removed(Row row) => row.Dispose();
protected override IEnumerable<SortingCriteria> get_default_sorting() { yield return new SortingCriteria(nameof(Row.security), true); }
@@ -24,6 +24,7 @@ namespace mbts.spark {
readonly OrdersModel _orders_model;
readonly AllowOnce _init_guard = new AllowOnce("MarketView");
readonly IWindowSettingsManager<GridSettings> _settings_manager;
readonly IWindowManager _wm;
MarketViewVM _vm;
MarketViewView _view;
MarketViewWindowFactory.StartupData _state;
@@ -33,11 +34,12 @@ namespace mbts.spark {
_selected_security_criteria_backing,
_sorted_selected_security_criteria_backing;
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, IWindowSettingsManager<GridSettings> settings_manager)
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, IWindowSettingsManager<GridSettings> settings_manager, IWindowManager wm)
: base("Market View", bus, factory, SparkCommands.ShowMarketView.icon) {
_orders_model = orders_model;
_state = state;
_settings_manager = settings_manager;
_wm = wm;
Icon.Freeze();
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().market_view ? "MarketView" : null;
}
@@ -104,6 +106,20 @@ namespace mbts.spark {
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);
_view = new MarketViewView { DataContext = _vm };
var tab_height = Application.Current.TryFindResource("TabHeight") as Double? ?? 30;
_vm.open_order_ticket_at_position += (args) => {
if (_view.try_find_ancestor_of_type<System.Windows.Controls.Grid>(out var grid) && grid.Name == "_grid") {
var pos = Application.Current.Dispatcher.Invoke(() => _wm.GetPosition(this));
var coords = args.hlc.TranslatePoint(new Point(0, 0), grid);
/* X = (Floating Window left pos) + (Floating Window BorderThickness) + (window_border left border) */
coords.X += pos.left + 1 + 7;
/* Y = (Floating Window top pos) + (Floating Window BorderThickness) + (window_border top border) + tab height + row_height */
coords.Y += pos.top + 1 + 1 + tab_height + args.hlc.ActualHeight;
_bus.post(SparkTags.ShowOrderTicketAtPositionTag, (args.order_def, args.price, args.size, coords));
}
};
_view._grid.Sorting += on_grid_sorting;
/// 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.
@@ -21,10 +21,12 @@ namespace bts.shell {
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly UserSettingsHub _settings_hub;
readonly IWindowManager _wm;
public MarketViewWindowFactory(MarketShellModule module, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(module, "MarketView") {
public MarketViewWindowFactory(MarketShellModule module, IBus bus, IWindowManager wm, UserSettingsHub settings_hub, OrdersModel orders_model) : base(module, "MarketView") {
_bus = bus;
_wm = wm;
_orders_model = orders_model;
_settings_hub = settings_hub;
_dispose = bus.subscribe(SparkTags.ShowMarketViewTag).OnDispatcher().Subscribe(_show);
@@ -39,7 +41,7 @@ namespace bts.shell {
_bus.post(SparkTags.ShowWindow, _create(StartupData.Default(criteria)));
IShellWindow _create(StartupData state) =>
new MarketViewWindow(this, _bus, _orders_model, state, _settings_hub.create_mview_settings_manager(state.grid));
new MarketViewWindow(this, _bus, _orders_model, state, _settings_hub.create_mview_settings_manager(state.grid), _wm);
[ProtoContract]
public class StartupData {
+33 -3
View File
@@ -31,9 +31,16 @@ namespace mbts.spark {
class NewOrderTicketVM : DisposableViewModelBase, IRequestViewClose {
readonly Property<SecurityImmutable> _security;
readonly bool _is_broker_mode;
public NewOrderTicketVM(OrdersModel model, SecurityMD security_md, OrderDefinitionImmutable def, double price, uint size, IObservable<int[]> calculator_buttons)
: this(model, Observable.Return(security_md), Observable.Return(new InlineOrderTicketProperties(model, def, price, size)), calculator_buttons) { }
readonly Property<bool> _buy_is_visible;
// need this one too b/c the determination is made off of _is_broker_mode as well
readonly Property<bool> _sell_is_visible;
readonly Property<Side> _broker_mode_side;
public NewOrderTicketVM(OrdersModel model, SecurityMD security_md, OrderDefinitionImmutable def, double price, uint size, IObservable<int[]> calculator_buttons, bool is_broker_mode = false, Side broker_mode_side = Side.Undefined)
: this(model, Observable.Return(security_md), Observable.Return(new InlineOrderTicketProperties(model, def, price, size)), calculator_buttons, is_broker_mode, broker_mode_side) { }
public NewOrderTicketVM(
OrdersModel model,
@@ -45,9 +52,15 @@ namespace mbts.spark {
OrdersModel model,
IObservable<SecurityMD> security_md_source,
IObservable<InlineOrderTicketProperties> props_source,
IObservable<int[]> calculator_buttons
IObservable<int[]> calculator_buttons,
bool is_broker_mode = false,
Side init_broker_mode_side = Side.Undefined
) {
_security = backing(nameof(security), security_md_source.Select(smd => smd.security), SecurityDataHelper.Defaults.SecurityImmutable);
_is_broker_mode = is_broker_mode;
_broker_mode_side = backing(nameof(broker_mode_side), () => init_broker_mode_side);
_buy_is_visible = backing(nameof(buy_is_visible), () => !is_broker_mode || (is_broker_mode && broker_mode_side == Side.Buy), nameof(broker_mode_side));
_sell_is_visible = backing(nameof(sell_is_visible), () => !is_broker_mode || (is_broker_mode && broker_mode_side == Side.Sell), nameof(broker_mode_side));
properties_vm = new OrderPropertiesEditorVM(model, security_md_source, props_source.Select(p => p.order_props));
bbo = new BBOVM();
send_order = new DelegateCommand<Side>(side => {
@@ -58,6 +71,17 @@ namespace mbts.spark {
}
});
set_price_command = new DelegateCommand<double>(set_price);
set_price_and_side_command = new DelegateCommand<Tuple<double, Side>>((pair) => {
(price, broker_mode_side) = pair;
RaisePropertyChanged(nameof(price));
});
set_size_and_price_and_side_command = new DelegateCommand<Tuple<uint, double, Side>>((triplet) =>
{
size_calculator.set_value_command?.Execute(triplet.Item1);
price = triplet.Item2;
RaisePropertyChanged(nameof(price));
broker_mode_side = triplet.Item3;
});
_dispose = Disposable.Create(
bbo,
security_md_source.OnDispatcher().Subscribe(smd => bbo.security_md = smd),
@@ -86,6 +110,12 @@ namespace mbts.spark {
public BBOVM bbo { get; }
public ICommand send_order { get; }
public ICommand set_price_command { get; }
public ICommand set_price_and_side_command { get; }
public ICommand set_size_and_price_and_side_command { get; }
public bool buy_is_visible { get => _buy_is_visible.Value; private set => _buy_is_visible.Value = value; }
public bool sell_is_visible { get => _sell_is_visible.Value; private set => _sell_is_visible.Value = value; }
public bool is_broker_mode => _is_broker_mode;
public Side broker_mode_side { get => _broker_mode_side.Value; private set => _broker_mode_side.Value = value; }
bool _validate_price() {
if (DoubleHelper.IsNaNOrInfinity(price)) {
@@ -1,6 +1,7 @@
<UserControl x:Class="mbts.ui.spark.OrderModificationControl"
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:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
@@ -86,7 +87,14 @@
CommitOnEnter="True"
Focusable="False"
Value="{Binding size_calculator.value}"
/>
>
<c:TickUpDownNullableUInt.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="u:TextBoxBehavior.FocusOnLoaded" Value="True"/>
<Setter Property="u:TextBoxBehavior.IncrementDecrementOnMouseClick" Value="True" />
</Style>
</c:TickUpDownNullableUInt.Resources>
</c:TickUpDownNullableUInt>
</Grid>
<Grid Grid.Column="2">
<Grid.ColumnDefinitions>
@@ -94,17 +102,29 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSpacing}}"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Command="{Binding set_price_command}" CommandParameter="{Binding bbo.bid}" Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
<Button Command="{Binding set_price_and_side_command}" Tag="{u:Enum Buy, Type=msys:Side}" Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
<Binding Path="bbo.bid"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource TupleConverter}">
<Binding Path="bbo.bid"/>
<Binding Path="Tag" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
<Button Grid.Column="2" Command="{Binding set_price_command}" CommandParameter="{Binding bbo.ask}" Foreground="{u:BTSColor MarketData.ask}" FontWeight="Bold">
<Button Grid.Column="2" Command="{Binding set_price_and_side_command}" Tag="{u:Enum Sell, Type=msys:Side}" Foreground="{u:BTSColor MarketData.ask}" FontWeight="Bold">
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
<Binding Path="bbo.ask"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource TupleConverter}">
<Binding Path="bbo.ask"/>
<Binding Path="Tag" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
</Grid>
<Grid Grid.Row="2" Grid.Column="2">
@@ -114,15 +134,36 @@
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button
Content="{Binding bbo.bid_size}" Command="{Binding size_calculator.set_value_command}"
CommandParameter="{Binding bbo.bid_size}" Foreground="{u:BTSColor MarketData.bid}"
/>
Content="{Binding bbo.bid_size}" Command="{Binding set_size_and_price_and_side_command}"
Foreground="{u:BTSColor MarketData.bid}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource TupleConverter}">
<Binding Path="bbo.bid_size"/>
<Binding Path="bbo.bid"/>
<Binding>
<Binding.Source>
<msys:Side>Buy</msys:Side>
</Binding.Source>
</Binding>
</MultiBinding>
</Button.CommandParameter>
</Button>
<Button
Grid.Column="2"
Content="{Binding bbo.ask_size}" Command="{Binding size_calculator.set_value_command}"
CommandParameter="{Binding bbo.ask_size}"
Foreground="{u:BTSColor MarketData.ask}"
/>
Content="{Binding bbo.ask_size}" Command="{Binding set_size_and_price_and_side_command}"
Foreground="{u:BTSColor MarketData.ask}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource TupleConverter}">
<Binding Path="bbo.ask_size"/>
<Binding Path="bbo.ask"/>
<Binding>
<Binding.Source>
<msys:Side>Sell</msys:Side>
</Binding.Source>
</Binding>
</MultiBinding>
</Button.CommandParameter>
</Button>
</Grid>
<Grid Grid.Row="4" Grid.RowSpan="3">
<Grid.RowDefinitions>
+31 -6
View File
@@ -7,7 +7,8 @@
xmlns:sys="clr-namespace:System;assembly=mscorlib"
u:WindowStyle.SizeToContent="WidthAndHeight"
u:WindowStyle.TitleContent="{Binding bbo, Mode=OneWay}"
Style="{StaticResource BTSWindowTitleTemplate}">
Style="{StaticResource BTSWindowTitleTemplate}"
Title="Order Ticket">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
@@ -23,9 +24,12 @@
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Visibility" Value="{Binding sell_is_visible, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Setter Property="Grid.Column" Value="2" />
<Setter Property="Grid.ColumnSpan" Value="1" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Foreground="{u:BTSColor UI.alternate_text}">Sell</TextBlock>
<Setter.Value>
<TextBlock VerticalAlignment="Center" Foreground="{u:BTSColor UI.alternate_text}">Sell</TextBlock>
</Setter.Value>
</Setter>
<Setter Property="Template">
@@ -56,6 +60,16 @@
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding is_broker_mode}" Value="True" />
<Condition Binding="{Binding sell_is_visible}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="Grid.ColumnSpan" Value="3" />
<Setter Property="Grid.Column" Value="0" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="BuyButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{u:BTSColor MarketData.bid}" />
@@ -64,6 +78,8 @@
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Visibility" Value="{Binding buy_is_visible, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Setter Property="Grid.ColumnSpan" Value="1" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Foreground="{u:BTSColor UI.alternate_text}">Buy</TextBlock>
@@ -97,6 +113,15 @@
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding is_broker_mode}" Value="True" />
<Condition Binding="{Binding buy_is_visible}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="Grid.ColumnSpan" Value="3" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</Window.Resources>
@@ -115,9 +140,9 @@
<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>
<Button Grid.Row="2" Style="{StaticResource BuyButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Buy, Type=msys:Side}" />
<Button Grid.Row="2" Style="{StaticResource SellButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Sell, Type=msys:Side}" />
</Grid>
<u:WindowStyle.TitleContentTemplate>
<DataTemplate>
<TextBlock>
+7
View File
@@ -330,6 +330,13 @@ namespace mbts.spark {
}
}
public static Side opposite(this Side side) {
switch (side) {
case Side.Buy: return Side.Sell;
case Side.Sell: return Side.Buy;
default: return Side.Undefined;
}
}
public static AggressorSide to_aggressor(this Side side) {
switch (side) {
case Side.Buy: return AggressorSide.Buy;
+2 -1
View File
@@ -8,7 +8,7 @@ using mbts.ui;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using static mbts.spark.SparkBusData;
using System.Windows;
namespace mbts.spark
{
@@ -30,6 +30,7 @@ namespace mbts.spark
public static ITag<uint> ShowModifyOrderTag { get; } = new SparkTag<uint>(nameof(ShowModifyOrderTag));
public static ITag<SecurityMD> ShowOrderTicketFromSecurityTag = new SparkTag<SecurityMD>(nameof(ShowOrderTicketFromSecurityTag));
public static ITag<(OrderDefinitionImmutable def, double price, uint size)> ShowOrderTicketTag = new SparkTag<(OrderDefinitionImmutable def, double price, uint size)>(nameof(ShowOrderTicketTag));
public static ITag<(OrderDefinitionImmutable def, double price, uint size, Point window_start_position)> ShowOrderTicketAtPositionTag = new SparkTag<(OrderDefinitionImmutable def, double price, uint size, Point window_start_position)>(nameof(ShowOrderTicketAtPositionTag));
#endregion
#region Selection/Utility
@@ -1,4 +1,5 @@
using System;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
@@ -10,6 +11,7 @@ namespace mbts.ui {
void remove(IShellWindow window);
bool contains(IShellWindow window);
void clear();
WindowPosition GetPosition(IShellWindow shell_window);
// Bring to front of window stack and z-order
void MakeVisible(IShellWindow window);
@@ -12,6 +12,9 @@ using FontFamily = System.Windows.Media.FontFamily;
namespace mbts.ui.controls {
partial class DIYGrid {
public class HyperlinkCell : FrameworkElement, ICommandSource {
static Brush _hover_background_highlight = new SolidColorBrush(Colors.Yellow) { Opacity = 0.5 }.frozen();
static Brush _hover_foreground_brush = new SolidColorBrush(Colors.Black).frozen();
static Pen _hover_underline_pen = new Pen(_hover_foreground_brush, 1);
// these fields each represent properties that are accessed frequently,
// they are stored locally to reduce repetative DependencyProperty lookups
Thickness _padding;
@@ -49,12 +52,14 @@ namespace mbts.ui.controls {
_font_size = FontSize;
_text = Text;
_glyph_context = GlyphContext.create(FontFamily, FontWeight);
AddHandler(MouseDownEvent, new RoutedEventHandler(on_hyperlink_click_command), true);
IsMouseDirectlyOverChanged += (s, e) => {
if (_is_mouse_over_text != IsMouseDirectlyOver)
_toggle_mouse_over_text();
_toggle_mouse_cursor();
};
}
#region Background
public static readonly DependencyProperty BackgroundProperty =
WPFHelper.create_dp<Brush, HyperlinkCell>(
@@ -221,32 +226,53 @@ namespace mbts.ui.controls {
#endregion
#region EnableHoverEffect
public static readonly DependencyProperty EnableHoverEffectProperty =
WPFHelper.create_dp<bool, HyperlinkCell>(nameof(EnableHoverEffect), false);
public bool EnableHoverEffect {
get => (bool)GetValue(EnableHoverEffectProperty);
set => SetValue(EnableHoverEffectProperty, value);
}
#endregion
#region Paraphrased/Simplified from https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Documents/Hyperlink.cs
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {
base.OnMouseLeftButtonDown(e);
// Note that OnMouseDown doesn't appear to fire when used in a MarketViewHyperlinkElementColumn due to the
// InputBindings, we think, setting e.Handled to true.
// Instead, we AddHandler to the MouseDownEvent in the constructor and use the
// callback below, on_hyperlink_click_command, to reset Handled
// so that it continues propagating down to the ListBoxItem. This in turn,
// causes the ListBoxItem to be selected, which is then reflected in the
// RowSelectionAdorner.
protected override void OnMouseDown(MouseButtonEventArgs e) {
base.OnMouseDown(e);
_check_mouse_over_text(e);
if (_is_mouse_over_text) {
// Hyperlink should take focus when left mouse button is clicked on it
// This is consistent with all ButtonBase controls and current Win32 behavior
Focus();
// It is possible that the mouse state could have changed during all of
// the call-outs that have happened so far.
if (e.ButtonState == MouseButtonState.Pressed) {
// Capture the mouse, and make sure we got it.
CaptureMouse();
}
e.Handled = true;
}
// It is possible that the mouse state could have changed during all of
// the call-outs that have happened so far.
if (e.ButtonState == MouseButtonState.Pressed) {
// Capture the mouse, and make sure we got it.
CaptureMouse();
}
e.Handled = true;
}
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) {
base.OnMouseLeftButtonUp(e);
private void on_hyperlink_click_command(object sender, RoutedEventArgs e) {
e.Handled = false;
}
protected override void OnMouseUp(MouseButtonEventArgs e) {
base.OnMouseUp(e);
_check_mouse_over_text(e);
if (IsMouseCaptured)
ReleaseMouseCapture();
if (_can_execute && _is_mouse_over_text)
if (_can_execute)
CommandHelper.ExecuteCommandSource(this);
e.Handled = true;
}
@@ -279,12 +305,21 @@ namespace mbts.ui.controls {
var rs = RenderSize;
var rect = new Rect(0, 0, rs.Width, rs.Height);
var bg = Background;
if (bg != null)
drawingContext.DrawRectangle(bg, null, rect);
if (bg != null) {
if (_glyph_run != null && IsMouseDirectlyOver && EnableHoverEffect) {
drawingContext.DrawRectangle(_hover_background_highlight, null, rect);
} else {
drawingContext.DrawRectangle(bg, null, rect);
}
}
if (_glyph_run != null) {
drawingContext.PushClip(new RectangleGeometry(rect));
drawingContext.DrawGlyphRun(_foreground, _glyph_run);
if (_is_mouse_over_text) {
if (IsMouseDirectlyOver) {
if (EnableHoverEffect) {
drawingContext.DrawGlyphRun(_hover_foreground_brush, _glyph_run);
} else {
drawingContext.DrawGlyphRun(_foreground, _glyph_run);
}
// draw underline
Point pt;
switch (_horizontal_alignment) {
@@ -299,9 +334,15 @@ namespace mbts.ui.controls {
pt = new Point(0.5 * (ActualWidth - _glyph_width), 0.5 * (ActualHeight + _font_size) + 1);
break;
}
drawingContext.DrawLine(_underline_pen, pt, new Point(pt.X + _glyph_width, pt.Y));
if (EnableHoverEffect) {
drawingContext.DrawLine(_hover_underline_pen, pt, new Point(pt.X + _glyph_width, pt.Y));
} else {
drawingContext.DrawLine(_underline_pen, pt, new Point(pt.X + _glyph_width, pt.Y));
}
} else {
drawingContext.DrawGlyphRun(_foreground, _glyph_run);
}
drawingContext.Pop();
}
}
@@ -331,10 +372,15 @@ namespace mbts.ui.controls {
void _toggle_mouse_over_text() {
_is_mouse_over_text = !_is_mouse_over_text;
if (_is_mouse_over_text)
_toggle_mouse_cursor();
}
void _toggle_mouse_cursor() {
if (_glyph_run != null && IsMouseDirectlyOver) {
SetCurrentValue(CursorProperty, Cursors.Hand);
else
} else {
ClearValue(CursorProperty);
}
InvalidateVisual();
}
}
@@ -1,9 +1,11 @@
using mbts.spark.filters;
using mbts.ui.converters;
using bts.api;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui.spark;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Input;
using static mbts.ui.controls.DIYGrid;
namespace mbts.ui.controls {
partial class DIYGrid {
@@ -12,19 +14,21 @@ namespace mbts.ui.controls {
readonly BindingBase _binding, _command_binding;
public HyperlinkElementColumn(
TKey key, BindingBase binding, BindingBase command_binding, IDataGridColumnFilter filter, string filter_template) : base(key) {
TKey key, BindingBase binding, BindingBase command_binding, IDataGridColumnFilter filter, string filter_template, bool enable_hover_effect = false) : base(key) {
this.filter = filter;
this.filter_template = filter_template;
_binding = binding;
_command_binding = command_binding;
this.enable_hover_effect = enable_hover_effect;
}
public IDataGridColumnFilter filter { get; }
public string filter_template { get; }
public Thickness padding { get; set; } = _default_padding;
public bool enable_hover_effect { get; set; }
public override FrameworkElement view_factory() {
var hl = new HyperlinkCell { Padding = padding };
var hl = new HyperlinkCell { Padding = padding, EnableHoverEffect = enable_hover_effect };
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 });
@@ -36,5 +40,35 @@ namespace mbts.ui.controls {
return hl;
}
}
internal class MarketViewHyperlinkElementColumn : HyperlinkElementColumn<MarketViewVM.Row, MarketViewView.Columns> {
readonly Side _market_side;
public MarketViewHyperlinkElementColumn(MarketViewView.Columns key, BindingBase binding, BindingBase command_binding, IDataGridColumnFilter filter, string filter_template, Side market_side) : base(key, binding, command_binding, filter, filter_template, true) {
_market_side = market_side;
}
public override FrameworkElement view_factory() {
var hl = base.view_factory();
hl.InputBindings.Add(new MouseBinding(HyperLinkRoutedCommands.rc, new MouseGesture(MouseAction.LeftClick)) { CommandParameter = _market_side });
hl.InputBindings.Add(new MouseBinding(HyperLinkRoutedCommands.rc, new MouseGesture(MouseAction.RightClick)) { CommandParameter = _market_side.opposite() });
hl.CommandBindings.Add(new CommandBinding(HyperLinkRoutedCommands.rc, HyperLinkRoutedCommands.create_command_with_coordinates<Side>()));
return hl;
}
}
}
public static class HyperLinkRoutedCommands {
public static RoutedCommand rc = new RoutedCommand();
public static ExecutedRoutedEventHandler create_command_with_coordinates<T>() =>
(s, e) => {
if (s is HyperlinkCell hl && e.Parameter is T orig_param) {
hl.Command.TryExecuteIfCan((orig_param, hl));
}
};
}
}
@@ -34,6 +34,9 @@
u:TextBoxBehavior.EnableArrowKeyTraverse="True"
BorderThickness="0"
>
<TextBox.ContextMenu>
<ContextMenu Visibility="Collapsed" />
</TextBox.ContextMenu>
<TextBox.CommandBindings>
<CommandBinding Command="{x:Static u:TextBoxBehavior.SelectAllCommand}" Executed="{x:Static u:TextBoxBehavior.SelectAllCommandExecutedHandler}"/>
</TextBox.CommandBindings>
@@ -429,7 +429,11 @@
Name="PART_TextBox"
u:TextBoxBehavior.EnableArrowKeyTraverse="True"
BorderThickness="0"
/>
>
<TextBox.ContextMenu>
<ContextMenu Visibility="Collapsed" />
</TextBox.ContextMenu>
</TextBox>
</c:TickUpDown>
</ControlTemplate>
</Setter.Value>
@@ -312,5 +312,17 @@ namespace mbts.ui {
}
});
}
public static IDisposable ShowAtPosition(this Window window, Point coords, bool dispose_vm_on_close = false) {
if (dispose_vm_on_close) window.Closed += (s, e) => (window.DataContext as IDisposable)?.Dispose();
window.set_position(new WindowPosition() { top = (int)coords.Y, left = (int)coords.X });
window.Show();
return Disposable.Create(() => {
// not very elegant, but safe and effective
if (window != null) {
try { window.Close(); } catch { }
}
});
}
}
}
+70
View File
@@ -15,3 +15,73 @@ Now update securities to add an exchange listed spread
Double click each of the security cells and verify a ladder comes up
Right click in the grid and bring up each form, close them
Test each Spark Menu (toolbar) item other than update securities with a security row selected, make sure context from the selected row is respected if applicable
=== "broker mode" functionality test steps circa Sep 2019 (MV and corresponding OrderTicket)
-- MV Hyperlink cell highlighting --
Bid/Ask column test - Verify price columns reflect a hover effect when the mouse is over their cells
The background will change to yellow
The foreground hyperlink cell text will change to black
The cursor changes to a hand anywhere within the cell (not only over the hyperlink text itself)
Qty (M) column test - Verify the Qty (Merged) column reflect a hover effect
same as above
Working column test - Verify the Working columns reflect a hover effect
Note: the hover effect should only occur over cell that actually have text
Clicking on an empty working column cell should *not* launch an OrderBook
-- broker mode OrderTicket --
"Broker mode" general notes:
Order Tickets should appear immediately below the cell from which they were clicked
The qty box has focus an is ready to be typed into immediately
The qty box has the same left/right mouse behavior as the ladder qty for quick increment/decrement
There is only 1 big Buy/Sell button on the bottom
Mousebutton action to join/cross
Left click on a bid price cell
A buy-only ticket launches (i.e. your intent is to join the bid)
Right click on a bid price cell
A sell-only ticket launches (i.e. your intent is to cross the bid)
Left click on a ask price cell
A sell-only ticket launches (i.e. your intent is to join the ask)
Right click on a sell price cell
A buy-only ticket launches (i.e. your intent is to cross the ask)
Order Ticket price buttons
Launch a buy order ticket by joining a bid
Click on the Ask Price button
The price field should update to reflect the ask price at the time the button was clicked
The single Buy button is replaced with a Sell button
Now, click the "1" qty button
Click Sell
Verify a Sell Order was actually sent at the appropriate price and size
Now, click on the Bid Price button
The price field should update to reflect the bid price at the time the button was clicked
The single Ask button is replaced with a Buy button
Click Buy
Verify a Buy Order was actually sent at the appropriate price and size
Close the ticket
Extra credit - repeat the above test by first lauching a sell order and swap everything else
Order Ticket size buttons
Launch a buy order ticket by joining a bid
Click on the Ask Size button
The price field should update to reflect the ask price at the time the button was clicked
The size field should update to reflect the ask size at the time the button was clicked
The single Buy button is replaced with a Sell button
Click Sell
Verify a Sell order was sent at the appropriate price and size
Now, click on the Bid Size button
The price field should update to reflect the bid price at the time the button was clicked
The size field should update to reflect the bid size at the time the button was clicked
The single Sell button is replaced with a Buy button
Click Buy
Verify a Buy order was actually sent at the appopriate price and size
Close the ticket
Extra credit - repeat the above test by first lauching a sell order and swap everything else