/Atdl4net/Wpf/AtdlControl.xaml.cs

http://atdl4net.codeplex.com · C# · 525 lines · 301 code · 108 blank · 116 comment · 43 complexity · fa724cb0e774b9020594a62d89683f42 MD5 · raw file

  1. #region Copyright (c) 2010-2012, Cornerstone Technology Limited. http://atdl4net.org
  2. //
  3. // This software is released under both commercial and open-source licenses.
  4. //
  5. // If you received this software under the commercial license, the terms of that license can be found in the
  6. // Commercial.txt file in the Licenses folder. If you received this software under the open-source license,
  7. // the following applies:
  8. //
  9. // This file is part of Atdl4net.
  10. //
  11. // Atdl4net is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
  12. // License as published by the Free Software Foundation, either version 2.1 of the License, or (at your option) any later version.
  13. //
  14. // Atdl4net is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
  15. // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
  16. //
  17. // You should have received a copy of the GNU Lesser General Public License along with Atdl4net. If not, see
  18. // http://www.gnu.org/licenses/.
  19. //
  20. #endregion
  21. using System;
  22. using System.Collections.Generic;
  23. using System.ComponentModel;
  24. using System.IO;
  25. using System.Linq;
  26. using System.Text;
  27. using System.Windows;
  28. using System.Windows.Controls;
  29. using System.Windows.Markup;
  30. using System.Xml;
  31. using Atdl4net.Configuration;
  32. using Atdl4net.Diagnostics;
  33. using Atdl4net.Diagnostics.Exceptions;
  34. using Atdl4net.Fix;
  35. using Atdl4net.Model.Elements;
  36. using Atdl4net.Notification;
  37. using Atdl4net.Resources;
  38. using Atdl4net.Utility;
  39. using Atdl4net.Wpf.View;
  40. using Atdl4net.Wpf.ViewModel;
  41. using Common.Logging;
  42. using ValidationResult = Atdl4net.Validation.ValidationResult;
  43. namespace Atdl4net.Wpf
  44. {
  45. /// <summary>
  46. /// Custom control for rendering FIXatdl strategies.
  47. /// </summary>
  48. public partial class AtdlControl : UserControl, IInitialFixValueProvider, INotifyPropertyChanged
  49. {
  50. private static readonly ILog _log = LogManager.GetLogger("Atdl4net.Wpf");
  51. private string _xaml; // Used for debugging purposes
  52. private bool _inputValuesSet = false;
  53. #region Dependency Properties
  54. /// <summary>
  55. /// Dependency property that provides storage for the data entry mode for this control.
  56. /// </summary>
  57. public static readonly DependencyProperty DataEntryModeProperty =
  58. DependencyProperty.Register("DataEntryMode", typeof(DataEntryMode), typeof(AtdlControl),
  59. new FrameworkPropertyMetadata(DataEntryMode.Create, OnDataEntryModeChanged));
  60. /// <summary>
  61. /// Dependency property that provides storage for the flag that enables and disables rendering.
  62. /// </summary>
  63. public static readonly DependencyProperty IsRenderingDisabledProperty =
  64. DependencyProperty.Register("IsRenderingDisabled", typeof(bool), typeof(AtdlControl),
  65. new FrameworkPropertyMetadata(false));
  66. /// <summary>
  67. /// Dependency property that provides storage for the currently selected strategy for this control.
  68. /// </summary>
  69. public static readonly DependencyProperty StrategyProperty =
  70. DependencyProperty.Register("Strategy", typeof(Strategy_t), typeof(AtdlControl),
  71. new FrameworkPropertyMetadata(OnStrategyPropertyChanged));
  72. /// <summary>
  73. /// Dependency property that provides storage for the input FIX values for the currently selected strategy for this control.
  74. /// </summary>
  75. public static readonly DependencyProperty InputFixValuesProperty =
  76. DependencyProperty.Register("InputFixValues", typeof(FixTagValuesCollection), typeof(AtdlControl),
  77. new FrameworkPropertyMetadata(OnInputFixValuesChanged));
  78. /// <summary>
  79. /// Dependency property that provides storage for the output FIX values for the currently selected strategy for this control.
  80. /// </summary>
  81. public static readonly DependencyProperty OutputFixValuesProperty =
  82. DependencyProperty.Register("OutputFixValues", typeof(FixTagValuesCollection), typeof(AtdlControl),
  83. new FrameworkPropertyMetadata(FixTagValuesCollection.Empty));
  84. #endregion
  85. #region Events
  86. /// <summary>
  87. /// Raised whenever an exception occurs when setting the <see cref="Strategy"/> property.
  88. /// </summary>
  89. /// <remarks>This event is provided because when using Adl4net with data binding, some exceptions are swallowed
  90. /// by the WPF run-time.</remarks>
  91. public event EventHandler<UnhandledExceptionEventArgs> ExceptionOccurred;
  92. /// <summary>
  93. /// Raised whenever a property on this control has changed value.
  94. /// </summary>
  95. public event PropertyChangedEventHandler PropertyChanged;
  96. /// <summary>
  97. /// Raised whenever the validation state of this control has changed.
  98. /// </summary>
  99. public event EventHandler<ValidationStateChangedEventArgs> ValidationStateChanged;
  100. /// <summary>
  101. /// Raised whenever the selected strategy has changed.
  102. /// </summary>
  103. public event EventHandler<StrategyChangedEventArgs> StrategyChanged;
  104. #endregion
  105. /// <summary>
  106. /// Initializes a new <see cref="AtdlControl"/> instance.
  107. /// </summary>
  108. public AtdlControl()
  109. {
  110. InitializeComponent();
  111. Application.Current.Resources[StrategyViewModel.ComboBoxSizerKey] = new WpfComboBoxSizer() { ExampleComboBox = new ComboBox(), InitialComboWidth = 28 };
  112. }
  113. #region Properties
  114. /// <summary>
  115. /// Gets the collection of child controls for this control.
  116. /// </summary>
  117. public UIElementCollection Children { get { return controlRoot.Children; } }
  118. /// <summary>
  119. /// Gets the XAML for the currently selected strategy. (Intended for debugging purposes only.)
  120. /// </summary>
  121. public string CurrentXaml { get { return _xaml; } }
  122. /// <summary>
  123. /// Gets/sets the name of the .NET assembly to be used to provide custom rendering of controls.
  124. /// </summary>
  125. public string CustomControlRendererAssembly
  126. {
  127. get { return WpfStrategyPanelRenderer.CustomControlRenderer; }
  128. set { WpfStrategyPanelRenderer.CustomControlRenderer = value; }
  129. }
  130. /// <summary>
  131. /// Gets/sets the data entry mode to be used (create order/amend order/view order).
  132. /// </summary>
  133. public DataEntryMode DataEntryMode
  134. {
  135. get { return (DataEntryMode)GetValue(DataEntryModeProperty); }
  136. set { SetValue(DataEntryModeProperty, value); }
  137. }
  138. /// <summary>
  139. /// Gets/sets a flag that is used to determine whether to render a strategy when it is set
  140. /// via the <see cref="Strategy"/> property. This property is useful when trying to debug
  141. /// custom renderers.
  142. /// </summary>
  143. public bool IsRenderingDisabled
  144. {
  145. get { return (bool)GetValue(IsRenderingDisabledProperty); }
  146. set { SetValue(IsRenderingDisabledProperty, value); }
  147. }
  148. /// <summary>
  149. /// Gets/sets the currently selected strategy for this control. Sets a new strategy causes the AtdlControl
  150. /// to render the FIXatdl, unless IsRenderingDisabled is set to true.
  151. /// </summary>
  152. public Strategy_t Strategy
  153. {
  154. get { return (Strategy_t)GetValue(StrategyProperty); }
  155. set { SetValue(StrategyProperty, value); }
  156. }
  157. /// <summary>
  158. /// Gets/sets the input FIX values for this control.
  159. /// </summary>
  160. public FixTagValuesCollection InputFixValues
  161. {
  162. get { return (FixTagValuesCollection)GetValue(InputFixValuesProperty); }
  163. set { SetValue(InputFixValuesProperty, value); }
  164. }
  165. /// <summary>
  166. /// Gets/sets the output FIX values for this control.
  167. /// </summary>
  168. public FixTagValuesCollection OutputFixValues
  169. {
  170. get { return (FixTagValuesCollection)GetValue(OutputFixValuesProperty); }
  171. set { SetValue(OutputFixValuesProperty, value); }
  172. }
  173. /// <summary>
  174. /// Determines whether all controls that are populated have valid values, and that all parameters therefore have valid
  175. /// values.
  176. /// </summary>
  177. public bool IsValid
  178. {
  179. get
  180. {
  181. bool isValid = false;
  182. if (Strategy != null && ViewModel != null)
  183. {
  184. IList<ValidationResult> validationResults;
  185. if (ViewModel.AreAllControlsInternallyValid &&
  186. Strategy.TryUpdateParameterValuesFromControls(true, out validationResults))
  187. isValid = Strategy.EvaluateAllStrategyEdits(this, true);
  188. }
  189. return isValid;
  190. }
  191. }
  192. /// <summary>
  193. /// Gets the ViewModel for this control.
  194. /// </summary>
  195. public StrategyViewModel ViewModel
  196. {
  197. get { return Application.Current != null ? Application.Current.Resources[StrategyViewModel.DataContextKey] as StrategyViewModel : null; }
  198. private set { Application.Current.Resources[StrategyViewModel.DataContextKey] = value; }
  199. }
  200. #endregion
  201. #region Public Methods
  202. /// <summary>
  203. /// Refreshes the rendering of the currently selected strategy.
  204. /// </summary>
  205. public void Refresh()
  206. {
  207. if (Strategy != null)
  208. Render();
  209. }
  210. /// <summary>
  211. /// Updates the FIX value within the input FIX fields for the currently selected strategy.
  212. /// </summary>
  213. /// <param name="fixTag">FIX tag whose value is to be updated.</param>
  214. /// <param name="value">New value for the FIX tag being updated.</param>
  215. /// <remarks>This method is used to update FIX tag values that are used within StrategyEdits using the FIX_
  216. /// mechanism. Controls that are initialized using the FIX_ mechanism and parameter values within strategies
  217. /// are unaffected when invoking this method.</remarks>
  218. public void UpdateFixValue(FixField fixTag, string value)
  219. {
  220. if (InputFixValues == null)
  221. return;
  222. InputFixValues[fixTag] = value;
  223. if (ViewModel != null)
  224. ViewModel.EvaluateAffectedStrategyEdits(this, fixTag);
  225. }
  226. /// <summary>
  227. /// Refreshes the output FIX tags and values based on the current state of the Strategy. Note that if any StrategyEdit is
  228. /// invalid, then a <see cref="ValidationException"/> is thrown.
  229. /// </summary>
  230. /// <exception cref="ValidationException">Thrown if any control/parameter value is invalid or any StrategyEdit is invalid.</exception>
  231. public void RefreshOutputValues()
  232. {
  233. if (Strategy == null)
  234. throw ThrowHelper.New<NullReferenceException>(this, ErrorMessages.NoStrategySelectedError);
  235. // NB Not localizable as this is a developer-level rather than end-user error
  236. if (!_inputValuesSet)
  237. throw ThrowHelper.New<InvalidOperationException>(this, ErrorMessages.UnableToInvokeMethodError,
  238. "RefreshOutputValues", "The InputFixValues property must be set prior to attempting to retrieve the output FIX values");
  239. // Step 1: Ensure all controls have internally valid values (NB this is NOT checking the parameter validity)
  240. if (!ViewModel.Controls.AreAllValid)
  241. throw ThrowHelper.New<ValidationException>(this, ErrorMessages.OneOrMoreInvalidControlValues);
  242. IList<ValidationResult> validationResults;
  243. // Step 2: Update all the parameter values from the controls, throwing if there are any problems
  244. if (!Strategy.TryUpdateParameterValuesFromControls(false, out validationResults))
  245. {
  246. StringBuilder sb = new StringBuilder();
  247. foreach (ValidationResult result in validationResults)
  248. sb.AppendFormat("{0}\n", result.ErrorText);
  249. string errorText = sb.ToString();
  250. throw ThrowHelper.New<InvalidFieldValueException>(this, sb.ToString().Substring(0, errorText.Length - 1));
  251. }
  252. // Step 3: Validate all StrategyEdits
  253. if (!ViewModel.EvaluateAllStrategyEdits(this))
  254. {
  255. StringBuilder sb = new StringBuilder();
  256. foreach (StrategyEdit_t strategyEdit in (from se in Strategy.StrategyEdits where !se.CurrentState select se))
  257. sb.AppendFormat("{0}\n", strategyEdit.ErrorMessage);
  258. string errorText = sb.ToString();
  259. throw ThrowHelper.New<ValidationException>(this, sb.ToString().Substring(0, errorText.Length - 1));
  260. }
  261. FixTagValuesCollection fixTagValues = Strategy.Parameters.GetOutputValues();
  262. // Step 4: Add in the StrategyIdentifier and optional VersionIdentifier tags
  263. if (Strategy.Parent != null)
  264. {
  265. fixTagValues.Add(Strategy.Parent.StrategyIdentifierTag, Strategy.WireValue);
  266. if (Strategy.Parent.VersionIdentifierTag != null)
  267. fixTagValues.Add((FixTag)Strategy.Parent.VersionIdentifierTag, Strategy.Version);
  268. }
  269. _log.Debug(m => m("RefreshOutputValues() yielding: {0}", fixTagValues.ToString()));
  270. OutputFixValues = fixTagValues;
  271. }
  272. #endregion
  273. #region Dependency Property Change Event Handlers
  274. private static void OnStrategyPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
  275. {
  276. (source as AtdlControl).OnStrategyPropertyChanged(e.NewValue as Strategy_t);
  277. }
  278. /// <remarks>This method does not throw exceptions as this causes issues with WPF data binding. Instead it
  279. /// invokes the ExceptionOccurred event handler (if registered).</remarks>
  280. private void OnStrategyPropertyChanged(Strategy_t newStrategy)
  281. {
  282. try
  283. {
  284. if (newStrategy != null)
  285. {
  286. _inputValuesSet = false;
  287. if (Atdl4netConfiguration.Settings.Wpf.ResetStrategyOnAssignmentToControl)
  288. newStrategy.Reset();
  289. Render();
  290. NotifyStrategyChanged(newStrategy.Name);
  291. }
  292. }
  293. catch (Exception ex)
  294. {
  295. NotifyExceptionOccurred(ex);
  296. }
  297. }
  298. private static void OnInputFixValuesChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
  299. {
  300. if (e.NewValue != null)
  301. (source as AtdlControl).OnInputFixValuesChanged();
  302. }
  303. private void OnInputFixValuesChanged()
  304. {
  305. if (Strategy == null)
  306. return;
  307. try
  308. {
  309. _inputValuesSet = true;
  310. FixFieldValueProvider fieldValueProvider = new FixFieldValueProvider(this, Strategy.Parameters);
  311. // First initialize all the control values from their initValue or initFixField...
  312. Strategy.LoadInitialControlValues(fieldValueProvider);
  313. // ... then load all the parameter values from the supplied FIX fields...
  314. Strategy.LoadParameterValues(fieldValueProvider, true);
  315. // ... then refresh the values of the controls from their parameters...
  316. Strategy.UpdateControlValuesFromParameters(fieldValueProvider);
  317. // ... and finally update all the state rules
  318. Strategy.RunAllStateRules();
  319. if (ViewModel !=null)
  320. ViewModel.RefreshViewState();
  321. }
  322. catch (Exception ex)
  323. {
  324. NotifyExceptionOccurred(ex);
  325. }
  326. }
  327. private static void OnDataEntryModeChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
  328. {
  329. (source as AtdlControl).OnDataEntryModeChanged();
  330. }
  331. private void OnDataEntryModeChanged()
  332. {
  333. if (ViewModel != null)
  334. ViewModel.UpdateDataEntryMode(DataEntryMode);
  335. }
  336. #endregion
  337. #region General Private Methods
  338. private void NotifyStrategyChanged(string strategyName)
  339. {
  340. EventHandler<StrategyChangedEventArgs> strategyChanged = StrategyChanged;
  341. if (strategyChanged != null)
  342. strategyChanged(this, new StrategyChangedEventArgs(strategyName));
  343. }
  344. private void NotifyPropertyChanged(string name)
  345. {
  346. PropertyChangedEventHandler propertyChanged = PropertyChanged;
  347. if (propertyChanged != null)
  348. propertyChanged(this, new PropertyChangedEventArgs(name));
  349. }
  350. private void Render()
  351. {
  352. WpfComboBoxSizer sizer = Application.Current.Resources[StrategyViewModel.ComboBoxSizerKey] as WpfComboBoxSizer;
  353. sizer.Clear();
  354. CreateViewModel();
  355. try
  356. {
  357. ViewModel.BeginRender();
  358. StringBuilder sb = new StringBuilder();
  359. XmlWriterSettings settings = new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment };
  360. using (XmlWriter writer = XmlWriter.Create(sb, settings))
  361. {
  362. WpfStrategyPanelRenderer.Render(Strategy, writer, sizer);
  363. }
  364. _xaml = sb.ToString();
  365. if (!IsRenderingDisabled)
  366. {
  367. try
  368. {
  369. controlRoot.Children.Clear();
  370. UIElement e = (UIElement)XamlReader.Parse(_xaml);
  371. controlRoot.Children.Add(e);
  372. //using (StreamWriter writer = File.CreateText(Path.Combine(Path.GetTempPath(), "atdl4net_xaml.xml")))
  373. // writer.Write(sb.ToString());
  374. }
  375. catch (XamlParseException ex)
  376. {
  377. _log.ErrorFormat("XamlParseException thrown; details: {0}", ex.Message);
  378. using (StreamWriter writer = File.CreateText(Path.Combine(Path.GetTempPath(), "atdl4net_xaml.xml")))
  379. writer.Write(sb.ToString());
  380. throw;
  381. }
  382. }
  383. }
  384. finally
  385. {
  386. ViewModel.EndRender();
  387. }
  388. }
  389. private void CreateViewModel()
  390. {
  391. StrategyViewModel previousViewModel = ViewModel;
  392. if (previousViewModel != null)
  393. previousViewModel.Controls.ValidationStateChanged -= new EventHandler<ValidationStateChangedEventArgs>(ControlsValidationStateChanged);
  394. StrategyViewModel newViewModel = new StrategyViewModel(Strategy, this);
  395. Application.Current.Resources[StrategyViewModel.DataContextKey] = newViewModel;
  396. newViewModel.Controls.ValidationStateChanged += new EventHandler<ValidationStateChangedEventArgs>(ControlsValidationStateChanged);
  397. }
  398. private void ControlsValidationStateChanged(object sender, ValidationStateChangedEventArgs e)
  399. {
  400. NotifyValidationStateChanged(e);
  401. }
  402. private void NotifyValidationStateChanged(ValidationStateChangedEventArgs e)
  403. {
  404. _log.Debug(m => m("AtdlControl notifying event listeners that validation state for control {0} is now {1}", e.ControlId, e.IsValid));
  405. EventHandler<ValidationStateChangedEventArgs> validationStateChanged = ValidationStateChanged;
  406. if (validationStateChanged != null)
  407. validationStateChanged(this, e);
  408. }
  409. private void NotifyExceptionOccurred(Exception ex)
  410. {
  411. EventHandler<UnhandledExceptionEventArgs> exceptionOccurred = ExceptionOccurred;
  412. if (exceptionOccurred != null)
  413. exceptionOccurred(this, new UnhandledExceptionEventArgs(ex, false));
  414. }
  415. #endregion
  416. }
  417. }