using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.IO; using TradeIdeas.XML; using TradeIdeas.TIProData; using TradeIdeas.TIProData.Configuration; using System.Threading; using System.Windows.Interop; using System.Runtime.InteropServices; namespace TradeIdeas.TIProGUI.CBT { public partial class EarningsDate : UserControl { /*Ratio of the default length of a slider to the default height of the calendarGrid These constants are for resizing purposes (user resizes window)*/ const double SLIDER_HEIGHT_RATIO = 169 / 230.0; const double MAIN_GRID_DEFAULT_HEIGHT = 481; const double GAP = 56; //Difference in height(pixels) between the mainGrid and wpfUserControl private IContainer _container; private TIRanges tiRanges = new TIRanges(); //This list holds the references to all of the tagged cells(rectangles) in the grid private List _refGrid = new List(); [DllImport("gdi32.dll")] //for displaying the filter icons private static extern bool DeleteObject(IntPtr hObject); public EarningsDate() { /* We're also going to match the "Today" on the calendar with the day of week. If the current * day of week is a weekend, we'll default to friday for demo. the _cellTagNumber will * be dependent upon the day of the week.*/ //we need to tag the cells in the grid //We'll "name" the cells from - 27 to + 27 //excluding any weekend cells as well as cells //in the very first row (weekday title cells) //we'll need this to start painting... int cellTagNumber; switch (DateTime.Today.DayOfWeek) { case DayOfWeek.Saturday: cellTagNumber = -29; break; case DayOfWeek.Sunday: cellTagNumber = -29; break; case DayOfWeek.Monday: cellTagNumber = -25; break; case DayOfWeek.Tuesday: cellTagNumber = -26; break; case DayOfWeek.Wednesday: cellTagNumber = -27; break; case DayOfWeek.Thursday: cellTagNumber = -28; break; case DayOfWeek.Friday: cellTagNumber = -29; break; default: // Avoid a compiler warning. Without this it would says that cellTagNumber // might be undefined. throw new Exception("Unknown day!"); } InitializeComponent(); //set the grid lines by adding rectangles to each grid position. Each rectangle has a border, //thus giving the appearance of a calendar grid. for (int i = 0; i < calendarGrid.RowDefinitions.Count; i++) { for (int j = 0; j < calendarGrid.ColumnDefinitions.Count; j++) { Rectangle rect = new Rectangle(); rect.Stroke = Brushes.Silver; //border bool today = false; if (i != 0 && j != 0 && j != 6) //zero and six are for "Sunday" squares and "Monday" squares respectively { rect.Tag = cellTagNumber; //using rect.Name = _cellTagNumber.ToString() throws exception today = cellTagNumber == 0; _refGrid.Add(rect); cellTagNumber++; } Grid.SetColumn(rect, j); Grid.SetRow(rect, i); calendarGrid.Children.Add(rect); if (today) { Label todayLabel = new Label(); todayLabel.Content = "Today"; //"Today" will always have a position of zero todayLabel.FontWeight = FontWeights.Black; todayLabel.VerticalAlignment = System.Windows.VerticalAlignment.Stretch; todayLabel.VerticalContentAlignment = System.Windows.VerticalAlignment.Stretch; Grid.SetColumn(todayLabel, j); Grid.SetRow(todayLabel, i); calendarGrid.Children.Add(todayLabel); } } } } private BitmapSource ToWpfImage(System.Drawing.Bitmap img) //for displaying the filter icons { if (img == null) return null; lock (img) { IntPtr hBitmap = img.GetHbitmap(); try { //code snippet courtesy of: http://www.codeproject.com/Articles/104929/Bitmap-to-BitmapSource BitmapSource bitmapSource = Imaging.CreateBitmapSourceFromHBitmap( img.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); return bitmapSource; } finally { DeleteObject(hBitmap); } } } public void setContainer(IContainer container) //this method is called from the WinForm constructor(ConfigWDemoEarningsDate.cs) { _container = container; _container.MinTextBox.TextChanged += new EventHandler(MinTextBox_TextChanged); _container.MaxTextBox.TextChanged += new EventHandler(MaxTextBox_TextChanged); minPictureBox.Source = ToWpfImage(container.ConnectionMaster.ImageCacheManager.GetFilter("MinEarningD")); maxPictureBox.Source = ToWpfImage(container.ConnectionMaster.ImageCacheManager.GetFilter("MaxEarningD")); updateCalendar(); } public void resizeWPFElements(int height, double ratio, int defaultTabSheetHeight) //called from the Winforms "resize" event... { if (height > defaultTabSheetHeight) { calendarGrid.Height = ratio * height; maxSlider.Height = calendarGrid.Height * SLIDER_HEIGHT_RATIO; minSlider.Height = calendarGrid.Height * SLIDER_HEIGHT_RATIO; if (calendarGrid.Height > MAIN_GRID_DEFAULT_HEIGHT) { mainGrid.Height = MAIN_GRID_DEFAULT_HEIGHT + (calendarGrid.Height - MAIN_GRID_DEFAULT_HEIGHT); wpfUserControl.Height = mainGrid.Height + GAP; } } } void MaxTextBox_TextChanged(object sender, EventArgs e) { updateTrackBar(maxSlider, _container.MaxValue); updateCalendar(); } void MinTextBox_TextChanged(object sender, EventArgs e) { updateTrackBar(minSlider, _container.MinValue); updateCalendar(); } private void minSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) { trackBarScroll(_container.MinTextBox, minSlider); } private void maxSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) { trackBarScroll(_container.MaxTextBox, maxSlider); } /// /// From Phil: /// This means that we are updating the min or max value. Normally, /// when a user types in the text box, that changes the track bar, and /// when the user changes the track bar, that changes the tex box. /// This keeps us from an infinate loop. What's more, If a user enters /// a number that does not perfectly match the track bar, the track /// bar should update to the closest position, but the text box should /// stay the same. If the user uses a slider, the text box will only /// display one of a small number of possible values. /// private bool _internalTrackBarChange = false; public void trackBarScroll(System.Windows.Forms.TextBox textBox, Slider trackBar) { if (_internalTrackBarChange) { //a change already in progress //changed the trackbar to match the text boxes return; } _internalTrackBarChange = true; textBox.Text = trackBar.Value.ToString(); _internalTrackBarChange = false; } private void updateTrackBar(Slider trackBar, double? value) { if (_internalTrackBarChange) // A change is already in progress. return; _internalTrackBarChange = true; if (null == value) { } else { double rounded = Math.Round(value.Value); if (rounded > trackBar.Maximum) // This is higher than the highest value the track bar can represent. // So set the track bar to its highest value. trackBar.Value = trackBar.Maximum; else if (rounded < trackBar.Minimum) trackBar.Value = trackBar.Minimum; else trackBar.Value = (double)rounded; } _internalTrackBarChange = false; } private void drawRectangle(Rectangle rect, DayColors dayColors) { // All of these if statements are just optimizations. You could always draw a gradient with // 3 sections to cover the three times of day. I'm not sure if this is necessary. I just // wanted to make sure the code wasn't any worse than it used to be. if ((dayColors.MarketHours == dayColors.PreMarket) && (dayColors.MarketHours == dayColors.PostMarket)) { rect.Fill = new SolidColorBrush(dayColors.MarketHours); } else { LinearGradientBrush myBrush = new LinearGradientBrush(); myBrush.StartPoint = new Point(0.5, 0); myBrush.EndPoint = new Point(0.5, 1); // Top to bottom: myBrush.GradientStops.Add(new GradientStop(dayColors.PreMarket, 0.0)); if (dayColors.MarketHours != dayColors.PreMarket) { myBrush.GradientStops.Add(new GradientStop(dayColors.PreMarket, 0.33)); myBrush.GradientStops.Add(new GradientStop(dayColors.MarketHours, 0.34)); } if (dayColors.MarketHours != dayColors.PostMarket) { myBrush.GradientStops.Add(new GradientStop(dayColors.MarketHours, 0.66)); myBrush.GradientStops.Add(new GradientStop(dayColors.PostMarket, 0.67)); } myBrush.GradientStops.Add(new GradientStop(dayColors.PostMarket, 1.0)); rect.Fill = myBrush; } } private void updateCalendar() { double? min = null; double? max = null; min = _container.MinValue; max = _container.MaxValue; // Do this just once each time the gui changes. tiRanges.Min = min; tiRanges.Max = max; foreach (Rectangle rect in _refGrid) { //In this loop, we go through each of the cells that are tagged, //then determine how/whether that cell should be painted. int dayNumber = (int)rect.Tag; DayColors dayColors = tiRanges.WhatToShow(dayNumber); drawRectangle(rect, dayColors); } } } public class SimpleRange { public Color Color; private const double BIG_VALUE = 1000000; private const double SMALL_VALUE = -BIG_VALUE; public double Min; public double Max; public void NoMax() { Max = BIG_VALUE; } public void NoMin() { Min = SMALL_VALUE; } public bool Included(double value) { System.Diagnostics.Debug.Assert(Min <= Max); return (value >= Min) && (value <= Max); } public SimpleRange() { NoMin(); NoMax(); } } public struct DayColors { public Color PreMarket; public Color MarketHours; public Color PostMarket; } public class TIRanges { public Color MinColor = Color.FromRgb(0, 192, 192); // Cyan public Color MaxColor = Color.FromRgb(158, 0, 192); // Magenta public Color BetweenColor = Color.FromRgb(79, 96, 192); // Blue public Color NotIncludedColor = Colors.White; private List _simpleRanges = new List(); private Color GetColor(double value) { // GetColor() for the whole set is the color of the first item where Included() is true. foreach (SimpleRange simpleRange in _simpleRanges) { if (simpleRange.Included(value)) return simpleRange.Color; } return NotIncludedColor; } private double? _min; private double? _max; public double? Min { get { return _min; } set { _min = value; Rebuild(); } } public double? Max { get { return _max; } set { _max = value; Rebuild(); } } public TIRanges() { Rebuild(); } private void Rebuild() { _simpleRanges.Clear(); // Start fresh! if ((!_min.HasValue) && (!_max.HasValue)) { // No filters -- Everything is included. SimpleRange simpleRange = new SimpleRange(); simpleRange.Color = BetweenColor; _simpleRanges.Add(simpleRange); } else if (!_min.HasValue) { // Only a max. SimpleRange simpleRange = new SimpleRange(); simpleRange.Max = _max.Value; simpleRange.Color = MaxColor; _simpleRanges.Add(simpleRange); } else if (!_max.HasValue) { // Only a min. SimpleRange simpleRange = new SimpleRange(); simpleRange.Min = _min.Value; simpleRange.Color = MinColor; _simpleRanges.Add(simpleRange); } else if (_max.Value < _min.Value) { // Max < Min. In Phil-world that means two seperate ranges. // The union of what's below the max and what's above the min. SimpleRange lowerRange = new SimpleRange(); lowerRange.Max = _max.Value; lowerRange.Color = MaxColor; _simpleRanges.Add(lowerRange); SimpleRange upperRange = new SimpleRange(); upperRange.Min = _min.Value; upperRange.Color = MinColor; _simpleRanges.Add(upperRange); } else { // Max >= Min. So a single point or a simple range. // We treat a single point as a simple range. SimpleRange simpleRange = new SimpleRange(); simpleRange.Min = _min.Value; simpleRange.Max = _max.Value; simpleRange.Color = BetweenColor; _simpleRanges.Add(simpleRange); } } public DayColors WhatToShow(int dayNumber) { DayColors result; result.PreMarket = GetColor(dayNumber + 0.25); result.MarketHours = GetColor(dayNumber + 0.5); result.PostMarket = GetColor(dayNumber + 0.75); return result; } } }