using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Input; using System.Windows.Threading; using DTS.Common.Base; using DTS.Common.Classes.ChannelCodes; using DTS.Common.Classes.Groups; using DTS.Common.Converters; using DTS.Common.Enums.Channels; using DTS.Common.Events; using DTS.Common.Events.Groups.GroupChannelList; using DTS.Common.Interface.Channels.ChannelCodes; using Microsoft.Practices.Prism.Events; using Microsoft.Practices.ServiceLocation; namespace DTS.Common.Controls { /// /// Interaction logic for ChannelNameBuilder.xaml /// built from ChannelCodeBuilder then simplified a little /// http://manuscript.dts.local/f/cases/17565/ /// public partial class ChannelNameBuilder : UserControl, IBasePropertyChanged { private LookupPopup _lookupPopup; private DispatcherTimer _possiblesTimer; public ChannelNameBuilder() { InitializeComponent(); Loaded += (sender, args) => { if (!_registered) RegisterCommands(); }; } public static readonly DependencyProperty CodeTypeProperty = DependencyProperty.Register( "CodeType", // The name of the DependencyProperty typeof(ChannelEnumsAndConstants.ChannelCodeType), // The type of the DependencyProperty typeof(ChannelNameBuilder), // The type of the owner of the DependencyProperty new PropertyMetadata( // OnHeaderTitleChanged will be called when HeaderTitle changes ChannelEnumsAndConstants.ChannelCodeType.User // The default value of the DependencyProperty ) ); public ChannelEnumsAndConstants.ChannelCodeType CodeType { get => (ChannelEnumsAndConstants.ChannelCodeType) GetValue(CodeTypeProperty); set => SetValue(CodeTypeProperty, value); } private static readonly DependencyProperty ChannelNameProperty = DependencyProperty.Register( "ChannelName", // The name of the DependencyProperty typeof(string), // The type of the DependencyProperty typeof(ChannelNameBuilder), // The type of the owner of the DependencyProperty new PropertyMetadata( // OnHeaderTitleChanged will be called when HeaderTitle changes "", // The default value of the DependencyProperty OnChannelNameChanged ) ); private volatile bool _bInSet = false; public void SetNameProperty(string value) { if( _bInSet ){ return; } _bInSet = true; SetValue(ChannelNameProperty, value); _bInSet = false; } private static void OnChannelNameChanged( DependencyObject d, DependencyPropertyChangedEventArgs e ) { ChannelNameBuilder cnb = (ChannelNameBuilder)d; cnb.SetValue(ChannelNameProperty, e.NewValue); cnb.UpdatePossibleChannels(); } public string ChannelName { get => (string) GetValue(ChannelNameProperty); set => SetValue(ChannelNameProperty, value); } public static readonly DependencyProperty ChannelNamesFuncProperty = DependencyProperty.Register( "ChannelNamesFunc", typeof(Func>), typeof(ChannelNameBuilder), new PropertyMetadata(null) ); public Func> ChannelNamesFunc { get => (Func>) GetValue(ChannelNamesFuncProperty); set => SetValue(ChannelNamesFuncProperty, value); } #region ISO builder properties private ICommand _pasteCommand; public ICommand PasteCommand { get => _pasteCommand; set { _pasteCommand = value; OnPropertyChanged("PasteCommand"); } } private static readonly DependencyProperty PasteIdProperty = DependencyProperty.Register( "PasteId", // The name of the DependencyProperty typeof(string), // The type of the DependencyProperty typeof(ChannelNameBuilder), // The type of the owner of the DependencyProperty new PropertyMetadata( // OnHeaderTitleChanged will be called when HeaderTitle changes GroupChannel.PASTE_ID // The default value of the DependencyProperty ) ); public string PasteId { get => (string)GetValue(PasteIdProperty); set => SetValue(PasteIdProperty, value); } private bool _registered = false; private void RegisterCommands() { // FB13293: In order to add CCB to Channel Codes tile, we have to expand support for paste types switch (PasteId) { case ChannelCode.PASTE_ID: PasteCommand = new Classes.ChannelCodes.PasteCommandClass(PasteId); break; case GroupChannel.PASTE_ID: default: PasteCommand = new Classes.Groups.PasteCommandClass(PasteId); break; } CommandManager.RegisterClassCommandBinding(GetType(), new CommandBinding(PasteCommand, Paste)); _registered = true; } private void Paste(object sender, ExecutedRoutedEventArgs e) { } #endregion #region ChannelCode select properties public static readonly DependencyProperty AllChannelCodesProperty = DependencyProperty.Register( "AllChannelCodes", // The name of the DependencyProperty typeof(IEnumerable), // The type of the DependencyProperty typeof(ChannelNameBuilder), // The type of the owner of the DependencyProperty new PropertyMetadata( // OnHeaderTitleChanged will be called when HeaderTitle changes new List(), // The default value of the DependencyProperty OnAllChannelCodesChanged ) ); private static void OnAllChannelCodesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ChannelNameBuilder ccb = (ChannelNameBuilder) d; ccb?.UpdatePossibleChannels(); } public IList AllChannelCodes { get => (IList)GetValue(AllChannelCodesProperty); set => SetValue(AllChannelCodesProperty, value); } /// /// lock to enforce updatepossiblechannels doesn't become re-entrant /// private static object MyLock = new object(); /// /// this is the method that updates the popup with channel codes /// immediate means do it now, (as opposed to whenever the user stops typing) /// this is done by using a timer, as long as the user keeps typing the timer gets reset /// /// public void UpdatePossibleChannels(bool immediateUpdate = false) { if (null == _lookupPopup) return; lock (MyLock) { var nameUpple = ChannelName.ToUpper(); if (immediateUpdate) { _possiblesTimer?.Stop(); _possiblesTimer = null; _lookupPopup.PossibleChannels = ChannelNamesFunc?.Invoke() .Where(ch => ch.CodeType == CodeType && ch.Name.ToUpper().Contains(nameUpple)).Select( code => new {code.Code, code.Name}).ToList() ?? AllChannelCodes .Where(ch => ch.CodeType == CodeType && ch.Name.ToUpper().Contains(nameUpple)) .Select(code => new {code.Code, code.Name}).ToList() ?? Enumerable.Repeat(new {Code = "", Name = ""}, 0).ToList(); } else { if (null == _possiblesTimer) { _possiblesTimer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(LookupHelperUpdateDelay), IsEnabled = false }; _possiblesTimer.Tick += (sender, args) => { UpdatePossibleChannels(true); }; _possiblesTimer.Start(); } else { _possiblesTimer.Stop(); _possiblesTimer.Start(); } } } } public static readonly DependencyProperty ShowChannelCodeLookupHelperProperty = DependencyProperty.Register( "ShowChannelCodeLookupHelper", // The name of the DependencyProperty typeof(bool), // The type of the DependencyProperty typeof(ChannelNameBuilder), // The type of the owner of the DependencyProperty new PropertyMetadata( // OnHeaderTitleChanged will be called when HeaderTitle changes true, // The default value of the DependencyProperty OnShowChannelCodeLookupHelperChanged ) ); private static void OnShowChannelCodeLookupHelperChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ChannelNameBuilder ccb = (ChannelNameBuilder)d; if (!(bool) e.NewValue) { ccb._lookupPopup = null; } } public bool ShowChannelCodeLookupHelper { get => (bool)GetValue(ShowChannelCodeLookupHelperProperty); set => SetValue(ShowChannelCodeLookupHelperProperty, value); } public static readonly DependencyProperty LookupHelperUpdateDelay2Property = DependencyProperty.Register( "LookupHelperUpdateDelay2", // The name of the DependencyProperty typeof(int), // The type of the DependencyProperty typeof(ChannelNameBuilder), // The type of the owner of the DependencyProperty new PropertyMetadata( 400, LookupHelperUpdateDelay2Changed, CoerceLookupHelperUpdateDelay2 ) ); private static object CoerceLookupHelperUpdateDelay2(DependencyObject d, object baseValue) { return (int) baseValue < 0 ? 0 : (int) baseValue; } private static void LookupHelperUpdateDelay2Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (!(d is ChannelNameBuilder ccb)) return; ccb._possiblesTimer?.Stop(); ccb._possiblesTimer = null; } public int LookupHelperUpdateDelay { get => (int) GetValue(LookupHelperUpdateDelay2Property); set => SetValue(LookupHelperUpdateDelay2Property, value); } #endregion public event PropertyChangedEventHandler PropertyChanged; public delegate void ChannelCodeSelectedEventHandler(object sender, string code, string name, ChannelEnumsAndConstants.ChannelCodeType codeType); public event ChannelCodeSelectedEventHandler ChannelCodeSelected; public void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } private void MainEditBox_OnGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) { if (sender.Equals(MainEditBox)) { MultiBinding lookupMB = null, isoMB = null; if (ShowChannelCodeLookupHelper && null == _lookupPopup) { _lookupPopup = new LookupPopup() { PlacementTarget = MainEditBox, HorizontalOffset = 10, Placement = PlacementMode.Custom, CustomPopupPlacementCallback = (size, targetSize, offset) => { CustomPopupPlacement bottom = new CustomPopupPlacement(new Point(0, targetSize.Height), PopupPrimaryAxis.Horizontal); CustomPopupPlacement left = new CustomPopupPlacement(new Point(-size.Width - offset.X, 0), PopupPrimaryAxis.Vertical); var topoffset = 0; CustomPopupPlacement top = new CustomPopupPlacement(new Point(0, -size.Height - topoffset), PopupPrimaryAxis.Horizontal); return new CustomPopupPlacement[] { bottom, left, top }; }, }; _lookupPopup.ChannelCodeSelected += LookupPopupOnChannelCodeSelected; lookupMB = new MultiBinding(); lookupMB.Converter = new BooleanOrMultiConverter(); lookupMB.Bindings.Add(new Binding("IsKeyboardFocused") { Source = MainEditBox, Mode = BindingMode.OneWay }); lookupMB.Bindings.Add(new Binding("IsKeyboardFocusWithin") { Source = _lookupPopup, Mode = BindingMode.OneWay }); lookupMB.NotifyOnSourceUpdated = true; } if (null != lookupMB) { _lookupPopup.SetBinding(Popup.IsOpenProperty, lookupMB); } if (null != _lookupPopup) { UpdatePossibleChannels(true); } if (null != isoMB) { if (null != _lookupPopup) { isoMB.Bindings.Add(new Binding("IsKeyboardFocusWithin") { Source = _lookupPopup, Mode = BindingMode.OneWay }); } } } if(sender is TextBox tb) { tb.SelectAll(); } } private void LookupPopupOnChannelCodeSelected(object sender, string code, string name) { ChannelName = name; ChannelCodeSelected?.Invoke(this, code, name, CodeType); Keyboard.ClearFocus(); } private void MainEditBox_OnPreviewKeyDown(object sender, KeyEventArgs e) { if (CodeType != ChannelEnumsAndConstants.ChannelCodeType.ISO) { return; } //might be faster to have a preset regex for this, but seems to run really fast anyhow... bool isAlpha = (e.Key >= Key.A && e.Key <= Key.Z) || e.Key == Key.Space; //need to allow space for names. bool isNumPadNumeric = (e.Key >= Key.NumPad0 && e.Key <= Key.NumPad9); bool isNumeric = (e.Key >= Key.D0 && e.Key <= Key.D9); bool isControl = e.Key == Key.Enter || e.Key == Key.Return || e.Key == Key.Tab || e.Key == Key.OemBackTab || e.Key == Key.Delete || e.Key == Key.Back || e.Key == Key.Home || e.Key == Key.End; bool isDirection = e.Key == Key.Up || e.Key == Key.Down || e.Key == Key.Left || e.Key == Key.Right; e.Handled = !(e.Key == Key.OemQuestion || isAlpha || isNumPadNumeric || isNumeric || isControl || isDirection); } private void MainEditBox_OnLostFocus(object sender, RoutedEventArgs e) { if (!IsKeyboardFocusWithin && (!_lookupPopup?.IsKeyboardFocusWithin ?? true)) { //now that we've lost focus, we can coerce the code //Code = Code; } } private void ChannelNameBuilder_OnLoaded(object sender, RoutedEventArgs e) { Window w = Window.GetWindow(this); if (w != null) { w.LocationChanged += delegate { if (IsKeyboardFocusWithin ) { Keyboard.ClearFocus(); } }; } var scrollParent = Utils.Utils.FindParent(this); if (scrollParent != null) { scrollParent.ScrollChanged += delegate(object o, ScrollChangedEventArgs args) { if (IsKeyboardFocusWithin && (args.HorizontalChange != 0 || args.VerticalChange != 0)) { Keyboard.ClearFocus(); } }; } } private void TextBoxSourceUpdated(object sender, DataTransferEventArgs e) { var eventAggregator = ServiceLocator.Current.GetInstance(); eventAggregator.GetEvent() .Publish(new PageModifiedArg(PageModifiedArg.Status.Modified, null)); eventAggregator.GetEvent() .Publish(new GroupUpdatedEventArgs(null, GroupUpdatedEventArgs.Status.AssignmentsMade)); } private void MainEditBox_OnMouseDoubleClick(object sender, MouseButtonEventArgs e) { if (sender is TextBox tb) { tb.SelectAll(); } } public event TextChangedEventHandler TextChanged; private void MainEditBox_OnTextChanged(object sender, TextChangedEventArgs e) { TextChanged?.Invoke(this, e); } } }