using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Threading; using System.Windows; using System.Windows.Input; using System.Windows.Threading; using SPC.Kiosk.Base; using SPC.Kiosk.Common; using SPC.Kiosk.Popup.Model; using SPC.Kiosk.Payments; namespace SPC.Kiosk.Popup.ViewModel { /// /// HappyPointUsing.xaml에 대한 ViewModel /// public class VmHappyPointUsing : PopupViewModelBase { #region [ Members ] private enum ProcessingType { Certify, PointUsing } private ProcessingType ProcessType { get; set; } = ProcessingType.Certify; private string titileImage; /// /// Barcode Scan Image /// public string TitileImage { get { return titileImage; } set { titileImage = value; PropertyChange("TitileImage"); } } private string wonIconImage; /// /// Won Icon Image /// public string WonIconImage { get { return wonIconImage; } set { wonIconImage = value; PropertyChange("WonIconImage"); } } private string pointImage; /// /// Point Using Title Image /// public string PointImage { get { return pointImage; } set { pointImage = value; PropertyChange("PointImage"); } } private string allUseNoramlBrush; /// /// All Use Button Normal Brush /// public string AllUseNoramlBrush { get { return allUseNoramlBrush; } set { allUseNoramlBrush = value; PropertyChange("AllUseNoramlBrush"); } } private string allUseSwitchOnBrush; /// /// All Use Button Switch On Brush /// public string AllUseSwitchOnBrush { get { return allUseSwitchOnBrush; } set { allUseSwitchOnBrush = value; PropertyChange("AllUseSwitchOnBrush"); } } public bool allUseSwitch = false; /// /// All Use Button Switch /// public bool AllUseSwitch { get { return allUseSwitch; } set { allUseSwitch = value; PropertyChange("AllUseSwitch"); } } private List headerText; /// /// Top Header Text (first Grid) /// public List HeaderText { get { return headerText; } set { headerText = value; PropertyChange("HeaderText"); } } private List scanGuidText; /// /// Second Header Text (first Grid) /// public List ScanGuidText { get { return scanGuidText; } set { scanGuidText = value; PropertyChange("ScanGuidText"); } } private List infoGuid1Text; /// /// Bootom first infomation text (first Grid) /// public List InfoGuid1Text { get { return infoGuid1Text; } set { infoGuid1Text = value; PropertyChange("InfoGuid1Text"); } } private List infoGuid2Text; /// /// Bottom second infomation text (first Grid) /// public List InfoGuid2Text { get { return infoGuid2Text; } set { infoGuid2Text = value; PropertyChange("InfoGuid2Text"); } } private string processingGIF; /// /// Processing GIF /// public string ProcessingGIF { get { return processingGIF; } set { processingGIF = value; PropertyChange("ProcessingGIF"); } } private string errorImage; /// /// Error Icon /// public string ErrorImage { get { return errorImage; } set { errorImage = value; PropertyChange("ErrorImage"); } } private string successImage; /// /// Sucess Icon /// public string SuccessImage { get { return successImage; } set { successImage = value; PropertyChange("SuccessImage"); } } public bool certifyStartVisible = false; /// /// 인증 시작 화면 Visible /// public bool CertifyStartVisible { get { return certifyStartVisible; } set { certifyStartVisible = value; PropertyChange("CertifyStartVisible"); } } public bool pointUsingVisible = false; /// /// Point Using Window Visible /// public bool PointUsingVisible { get { return pointUsingVisible; } set { pointUsingVisible = value; PropertyChange("PointUsingVisible"); } } public bool processingVisible = false; /// /// 처리 진행 화면 Visible /// public bool ProcessingVisible { get { return processingVisible; } set { processingVisible = value; PropertyChange("ProcessingVisible"); } } public bool processingErrorVisible = false; /// /// 인증 오류 화면 Visible /// public bool ProcessingErrorVisible { get { return processingErrorVisible; } set { processingErrorVisible = value; PropertyChange("ProcessingErrorVisible"); } } public bool certifySuccessVisible = false; /// /// 인증 완료 화면 Visible /// public bool CertifySuccessVisible { get { return certifySuccessVisible; } set { certifySuccessVisible = value; PropertyChange("CertifySuccessVisible"); } } private List pointPadGuidText; /// /// Point Pad Input Guide Text /// public List PointPadGuidText { get { return pointPadGuidText; } set { pointPadGuidText = value; PropertyChange("PointPadGuidText"); } } private List totalPayHeaderText; /// /// Payments Total Header Text /// public List TotalPayHeaderText { get { return totalPayHeaderText; } set { totalPayHeaderText = value; PropertyChange("TotalPayHeaderText"); } } private List queryPointsHeaderText; /// /// Member Point Header Text /// public List QueryPointsHeaderText { get { return queryPointsHeaderText; } set { queryPointsHeaderText = value; PropertyChange("QueryPointsHeaderText"); } } private List processingMessageText; /// /// Number Pad Input Guide Text /// public List ProcessingMessageText { get { return processingMessageText; } set { processingMessageText = value; PropertyChange("ProcessingMessageText"); } } private List processingGuide; /// /// Processing Guide Text /// public List ProcessingGuide { get { return processingGuide; } set { processingGuide = value; PropertyChange("ProcessingGuide"); } } private List errorGuide; /// /// Error Guide Text /// public List ErrorGuide { get { return errorGuide; } set { errorGuide = value; PropertyChange("ErrorGuide"); } } private List successText; /// /// Sucess Guide Text /// public List SuccessText { get { return successText; } set { successText = value; PropertyChange("SuccessText"); } } private double queryPoints; /// /// Member Points /// public double QueryPoints { get { return queryPoints; } set { queryPoints = value; PropertyChange("QueryPoints"); } } private double usingPoints; /// /// Using Points /// public double UsingPoints { get { return usingPoints; } set { usingPoints = value; PropertyChange("UsingPoints"); } } private double payments; /// /// Payments Total /// public double Payments { get { return payments; } set { payments = value; PropertyChange("Payments"); } } private List errorText; /// /// Error Text /// public List ErrorText { get { return errorText; } set { errorText = value; PropertyChange("ErrorText"); } } private bool isErrorBlink = false; /// /// Is Error Text Blink /// public bool IsErrorBlink { get { return isErrorBlink; } set { isErrorBlink = value; PropertyChange("IsErrorBlink"); } } private string formatedBarCode; /// /// Read Barcoe Formated Text /// public string FormatedBarCode { get { return formatedBarCode; } set { formatedBarCode = value; PropertyChange("FormatedBarCode"); } } private string readBarCode; /// /// Read BarCode /// public string ReadBarCode { get { return readBarCode; } set { readBarCode = value; PropertyChange("ReadBarCode"); } } private string inputPoints = string.Empty; /// /// Input Using Point Text /// public string InputPoints { get { return inputPoints; } set { inputPoints = value; PropertyChange("InputPoints"); } } public bool numPadInputVisible = false; /// /// Number Pad Points Visible /// public bool NumPadInputVisible { get { return numPadInputVisible; } set { numPadInputVisible = value; PropertyChange("NumPadInputVisible"); } } public bool numPadGuideVisible = true; /// /// Nummber Pad Guid Visible /// public bool NumPadGuideVisible { get { return numPadGuideVisible; } set { numPadGuideVisible = value; PropertyChange("NumPadGuideVisible"); } } private List allUseButtonText; /// /// All Point Using Button text /// public List AllUseButtonText { get { return allUseButtonText; } set { allUseButtonText = value; PropertyChange("AllUseButtonText"); } } private List cancelButtonText; /// /// Cancel Button Text /// public List CancelButtonText { get { return cancelButtonText; } set { cancelButtonText = value; PropertyChange("CancelButtonText"); } } private double cancelButtonWidth = 270d; /// /// Cancel Button Width /// public double CancelButtonWidth { get { return cancelButtonWidth; } set { cancelButtonWidth = value; PropertyChange("CancelButtonWidth"); } } private double okButtonWidth = 270d; /// /// OK Button Width /// public double OKButtonWidth { get { return okButtonWidth; } set { okButtonWidth = value; PropertyChange("OKButtonWidth"); } } private List okButtonText; /// /// OK Button Text /// public List OkButtonText { get { return okButtonText; } set { okButtonText = value; PropertyChange("OkButtonText"); } } public bool oKButtonEnabled = false; /// /// OK Button Enabled /// public bool OKButtonEnabled { get { return oKButtonEnabled; } set { oKButtonEnabled = value; PropertyChange("OKButtonEnabled"); } } private int pointPadMaxLength; /// /// Point Pad Input Max Length /// public int PointPadMaxLength { get { return pointPadMaxLength; } set { pointPadMaxLength = value; PropertyChange("PointPadMaxLength"); } } private string certifyPassword = string.Empty; public string CertifyPassword { get { return certifyPassword; } set { certifyPassword = value; PropertyChange("CertifyPassword"); } } public bool isOpenPasswordWindow = false; /// /// Password Window Open /// public bool IsOpenPasswordWindow { get { return isOpenPasswordWindow; } set { isOpenPasswordWindow = value; PropertyChange("IsOpenPasswordWindow"); } } private PointUsingWindowType pointUsingWindow = PointUsingWindowType.CertifyStart; /// /// Current View Window /// public PointUsingWindowType PointUsingWindow { get { return pointUsingWindow; } set { pointUsingWindow = value; PropertyChange("PointUsingWindow"); } } private double popupWidth = 1300; /// /// Popup Whidth (For Calculate OK Button Width) /// public double PopupWidth { get { return popupWidth; } set { popupWidth = value; PropertyChange("PopupWidth"); } } private M_HappyPointReturn happyPointReturn; /// /// HappyPoint 인증 정보 /// public M_HappyPointReturn HappyPointReturn { get { return happyPointReturn; } set { happyPointReturn = value; PropertyChange("HappyPointReturn"); } } /// /// OK Click Command /// public ICommand OkCommand { get; protected set; } /// /// Cancel Click Command /// public ICommand CancelCommand { get; protected set; } /// /// All Points Using Click Command /// public ICommand AllUseCommand { get; protected set; } public bool ImmediatelyClose { get; protected set; } = false; private bool ProcessStart = false; private posHappyPoint posHappyPoint = null; private Task ProcessTask = null; private string certifyKey = string.Empty; private double queryUsingPoints = 0d; private double canUseAllPoints = 0d; private int passCheckLength = 4; #endregion Members #region [ Ctor ] /// /// Ctor /// public VmHappyPointUsing() { OkCommand = new Command(OkCommandHandler); CancelCommand = new Command(CancelCommandHandler); AllUseCommand = new Command(AllUseCommandHandler); this.PropertyChanged += VmHappyPointUsing_PropertyChanged; TitileImage = ResourceManager.GetNximagePathAdd("img_gif_barcode_happy.gif", CommonValue.PBdesignImagesPath); WonIconImage = ResourceManager.GetNximagePathAdd("ic_won.png", CommonValue.PBdesignImagesPath); PointImage = ResourceManager.GetNximagePathAdd("img_point.png", CommonValue.PBdesignImagesPath); ProcessingGIF = ResourceManager.GetNximagePathAdd("ic_alert_ing2.gif", CommonValue.PBdesignImagesPath); ErrorImage = ResourceManager.GetNximagePathAdd("ic_alert_error.png", CommonValue.PBdesignImagesPath); SuccessImage = ResourceManager.GetNximagePathAdd("ic_alert_check.png", CommonValue.PBdesignImagesPath); AllUseNoramlBrush = ResourceManager.GetNximagePathAdd("btn_number_all_n.png", CommonValue.PBdesignImagesPath); AllUseSwitchOnBrush = ResourceManager.GetNximagePathAdd("btn_number_all_p.png", CommonValue.PBdesignImagesPath); ErrorText = new List(); OkButtonText = Languages.GetMessages("BTN0024"); CancelButtonText = Languages.GetMessages("BTN0033"); AllUseButtonText = Languages.GetMessages("LBL0045"); HeaderText = Languages.GetMessages("LBL0135"); ProcessingMessageText = Languages.GetMessages("LBL0087"); ProcessingGuide = Languages.GetMessages("LBL0028"); ErrorGuide = Languages.GetMessages("LBL0028"); PointPadMaxLength = 9; PointPadGuidText = Languages.GetMessages("LBL0059"); TotalPayHeaderText = Languages.GetMessages("LBL0112"); posHappyPoint = new posHappyPoint(); } /// /// Dispose /// public new void Dispose() { TitileImage = string.Empty; PointImage = string.Empty; ProcessingGIF = string.Empty; ErrorImage = string.Empty; AllUseNoramlBrush = string.Empty; AllUseSwitchOnBrush = string.Empty; this.PropertyChanged -= VmHappyPointUsing_PropertyChanged; if (ProcessTask != null) { ProcessTask.Dispose(); ProcessTask = null; } if (posHappyPoint != null) { HappyPointRemoveEvents(); posHappyPoint.Dispose(); posHappyPoint = null; } base.Dispose(); } #endregion Ctor #region [ Methods ] private void TestDelay() { Thread.Sleep(3000); } private void HappyPointCreateEvents() { if (posHappyPoint != null) { posHappyPoint.ReadStart += PosHappyPoint_ReadStart; posHappyPoint.ReadEnd += PosHappyPoint_ReadEnd; posHappyPoint.ErrorEvent += PosHappyPoint_ErrorEvent; posHappyPoint.ErrorMessageEvent += PosHappyPoint_ErrorMessageEvent; } } private void HappyPointRemoveEvents() { if (posHappyPoint != null) { posHappyPoint.ReadStart -= PosHappyPoint_ReadStart; posHappyPoint.ReadEnd -= PosHappyPoint_ReadEnd; posHappyPoint.ErrorEvent -= PosHappyPoint_ErrorEvent; posHappyPoint.ErrorMessageEvent -= PosHappyPoint_ErrorMessageEvent; } } private void DoHappyPointCertify() { HappyPointCreateEvents(); var processTask = new Task(() => posHappyPoint.StartCertifyProcessing(certifyKey)); processTask.Start(); processTask.Wait(); HappyPointRemoveEvents(); processTask = null; } private void DoHappyPointUsing() { if (posHappyPoint != null) { HappyPointCreateEvents(); var processTask = new Task(() => posHappyPoint.StartPaymentProcessing(HappyPointReturn.MemberCode ,double.Parse(InputPoints) ,CertifyPassword)); processTask.Start(); processTask.Wait(); HappyPointRemoveEvents(); processTask = null; } } private bool PointValueCheck() { bool result = false; try { if (!Payments.Equals(0) && QueryPoints >= 100) { var checkValue = double.Parse(InputPoints); if (checkValue >= 100) { result = (checkValue % 10).Equals(0) && checkValue <= QueryPoints && checkValue <= Payments; } } } catch (Exception ex) { CommonLog.ErrorLogWrite(this, "PointValueCheck()", "Fail", string.Format("{0}\n{1}", ex.Message, ex.StackTrace)); result = false; } return result; } #endregion Methods #region [ Event Handlers ] private void AllUseCommandHandler(object obj) { TimerEnabled = false; if (QueryPoints > 100) { canUseAllPoints = QueryPoints - QueryPoints % 10; if (!Payments.Equals(0)) { if (Payments < canUseAllPoints) { InputPoints = Payments.ToString(); } else { InputPoints = canUseAllPoints.ToString(); } } } TimerEnabled = true; } private void CancelCommandHandler(object obj) { TimerEnabled = false; ReturnValue = new M_PopupReturn { OKAnswer = false, TimeOut = false, ReturnLanguage = ShowLanguageType, PopupArgs = HappyPointReturn is M_HappyPointReturn && HappyPointReturn.ReservePoint ? HappyPointReturn : null }; CanWindowClose = true; } private void OkCommandHandler(object obj) { switch (PointUsingWindow) { case PointUsingWindowType.PointUsing: queryUsingPoints = double.Parse(InputPoints); if (HappyPointReturn is M_HappyPointReturn && HappyPointReturn.NeedPassword) { IsOpenPasswordWindow = true; } else { PointUsingWindow = PointUsingWindowType.Processing; } break; case PointUsingWindowType.ProcessingError: switch (ProcessType) { case ProcessingType.Certify: PointUsingWindow = PointUsingWindowType.CertifyStart; break; case ProcessingType.PointUsing: InputPoints = string.Empty; PointUsingWindow = PointUsingWindowType.PointUsing; break; } break; case PointUsingWindowType.CertifySuccess: if (ProcessTask != null) ProcessTask.Wait(); CanWindowClose = true; break; } } private void PosHappyPoint_ErrorMessageEvent(string ErrorString) { ErrorText = new List { new M_Language { Type = SupportLanguageType.ko, LanguageData = ErrorString.Replace("\n"," ").Replace("\r","") }, }; IsErrorBlink = true; PointUsingWindow = PointUsingWindowType.ProcessingError; } private void PosHappyPoint_ErrorEvent(posHappyPoint.ErrorCode _errorCode) { //TODO: 공통에 에러 메세지 등록 필요 //ErrorText = Languages.GetMessages(_errorCode.ToString()); ErrorText = new List { new M_Language { Type = SupportLanguageType.ko, LanguageData = _errorCode.ToString() }, }; IsErrorBlink = true; PointUsingWindow = PointUsingWindowType.ProcessingError; } private void PosHappyPoint_ReadStart(object sender) { TimerEnabled = false; ProcessStart = true; } private void PosHappyPoint_ReadEnd(object sender) { ProcessStart = false; try { if (sender is posHappyPoint getPosposHappyPoint) { if (getPosposHappyPoint.ProcessOK) { switch (ProcessType) { case ProcessingType.Certify: HappyPointReturn = new M_HappyPointReturn { IsCompanyMember = getPosposHappyPoint.IsCompanyMember, MemberName = getPosposHappyPoint.MemberName, MemberNumber = getPosposHappyPoint.MemberNumber, MemberCode = getPosposHappyPoint.CertifyKey, IsAppMamber = getPosposHappyPoint.IsAppMamber, MemberLevel = getPosposHappyPoint.MemberLevel.StartsWith("VIP") ? HappyPointMemberLevel.VIP : getPosposHappyPoint.MemberLevel.StartsWith("G") ? HappyPointMemberLevel.Gold : HappyPointMemberLevel.Pink, MemberPoint = getPosposHappyPoint.MemberPoint, UseablePoint = getPosposHappyPoint.UseablePoint, NeedPassword = getPosposHappyPoint.NeedPassword, ReservePoint = HappyPointReturn is M_HappyPointReturn ? HappyPointReturn.ReservePoint : false, }; QueryPoints = HappyPointReturn.MemberPoint; PointUsingWindow = PointUsingWindowType.PointUsing; break; case ProcessingType.PointUsing: if (HappyPointReturn is M_HappyPointReturn) { HappyPointReturn.PointUse = true; HappyPointReturn.UsingPoints = getPosposHappyPoint.ApprovalPoint; HappyPointReturn.PaySEQ = getPosposHappyPoint.PaySEQ; } ReturnValue = new M_PopupReturn { OKAnswer = true, TimeOut = false, ReturnLanguage = ShowLanguageType, PopupArgs = HappyPointReturn }; UsingPoints = HappyPointReturn.UsingPoints; PointUsingWindow = PointUsingWindowType.CertifySuccess; break; } } } } catch { throw; } finally { TimerEnabled = true; } } private void VmHappyPointUsing_PropertyChanged(object sender, PropertyChangedEventArgs e) { try { switch (e.PropertyName) { case "IsTimeout": if (IsTimeout) { switch (PointUsingWindow) { case PointUsingWindowType.CertifyStart: case PointUsingWindowType.PointUsing: case PointUsingWindowType.ProcessingError: ReturnValue = new M_PopupReturn { OKAnswer = false, TimeOut = true, ReturnLanguage = ShowLanguageType, PopupArgs = HappyPointReturn }; break; } if (ProcessTask != null) ProcessTask.Wait(); CanWindowClose = true; } break; case "PointUsingWindow": CertifyStartVisible = false; PointUsingVisible = false; ProcessingVisible = false; ProcessingErrorVisible = false; CertifySuccessVisible = false; switch (PointUsingWindow) { case PointUsingWindowType.CertifyStart: CertifyStartVisible = true; break; case PointUsingWindowType.PointUsing: PointUsingVisible = true; break; case PointUsingWindowType.Processing: ProcessingVisible = true; break; case PointUsingWindowType.ProcessingError: ProcessingErrorVisible = true; break; case PointUsingWindowType.CertifySuccess: CertifySuccessVisible = true; break; } break; case "CertifyStartVisible": if (CertifyStartVisible) { ScanGuidText = Languages.GetMessages("LBL0129"); InfoGuid1Text = new List(); InfoGuid2Text = Languages.GetMessages("LBL0003"); ProcessType = ProcessingType.Certify; CancelButtonWidth = PopupWidth; OKButtonWidth = 0d; OKButtonEnabled = false; CancelButtonText = Languages.GetMessages("BTN0033"); TimeOutSeconds = CommonValue.TimeOutSeconds; } break; case "PointUsingVisible": if (PointUsingVisible) { ScanGuidText = Languages.GetMessages("LBL0063"); InfoGuid1Text = new List(); InfoGuid2Text = Languages.GetMessages("LBL0001"); ProcessType = ProcessingType.PointUsing; CancelButtonWidth = PopupWidth / 2; OKButtonWidth = PopupWidth / 2; OKButtonEnabled = false; OkButtonText = Languages.GetMessages("BTN0024"); CancelButtonText = Languages.GetMessages("BTN0033"); TimeOutSeconds = CommonValue.TimeOutSeconds; } break; case "ProcessingVisible": if (ProcessingVisible) { ScanGuidText = new List(); InfoGuid1Text = new List(); InfoGuid2Text = new List(); CancelButtonWidth = 0d; OKButtonWidth = 0d; if (ProcessTask != null) ProcessTask.Wait(); switch (ProcessType) { case ProcessingType.Certify: ProcessTask = new Task(DoHappyPointCertify); break; case ProcessingType.PointUsing: ProcessTask = new Task(DoHappyPointUsing); break; } ProcessTask.Start(); } break; case "ProcessingErrorVisible": if (ProcessingErrorVisible) { switch (ProcessType) { case ProcessingType.Certify: ScanGuidText = Languages.GetMessages("LBL0077"); break; case ProcessingType.PointUsing: ScanGuidText = Languages.GetMessages("LBL0077"); break; } InfoGuid1Text = new List(); InfoGuid2Text = new List(); CancelButtonWidth = PopupWidth / 2; OKButtonWidth = PopupWidth / 2; OKButtonEnabled = true; OkButtonText = Languages.GetMessages("BTN0007"); CancelButtonText = Languages.GetMessages("BTN0033"); TimeOutSeconds = 10d; } break; case "CertifySuccessVisible": if (CertifySuccessVisible) { if (HappyPointReturn is M_HappyPointReturn && HappyPointReturn.PointUse && HappyPointReturn.UsingPoints.Equals(Payments)) { ScanGuidText = new List(); SuccessText = Languages.GetMessages("LBL0093"); InfoGuid1Text = new List(); InfoGuid2Text = new List(); ImmediatelyClose = true; TimeOutSeconds = 0d; } else { ScanGuidText = new List(); SuccessText = Languages.GetMessages("LBL0093"); InfoGuid1Text = new List(); InfoGuid2Text = new List(); CancelButtonWidth = 0d; OkButtonText = Languages.GetMessages("BTN0039"); OKButtonWidth = PopupWidth; OKButtonEnabled = true; TimeOutSeconds = 5d; } } break; case "HappyPointReturn": if (HappyPointReturn != null) { QueryPoints = HappyPointReturn.MemberPoint; } break; case "NumPadInputVisible": NumPadGuideVisible = !NumPadInputVisible; break; case "InputPoints": if (canUseAllPoints > 0d) { AllUseSwitch = canUseAllPoints.ToString().Equals(InputPoints); } else { AllUseSwitch = false; } NumPadInputVisible = InputPoints.Length > 0; OKButtonEnabled = PointValueCheck(); break; case "ReadBarCode": if (!ProcessStart) { switch (ReadBarCode.Length) { case 16: case 17: certifyKey = ReadBarCode.Substring(0, 16); InputPoints = string.Empty; FormatedBarCode = ReadBarCode.GetEncriptCardNo(); PointUsingWindow = PointUsingWindowType.Processing; break; default: FormatedBarCode = string.Empty; OKButtonEnabled = false; break; } } break; case "CertifyPassword": if (HappyPointReturn is M_HappyPointReturn && HappyPointReturn.NeedPassword && CertifyPassword.Length.Equals(passCheckLength)) { PointUsingWindow = PointUsingWindowType.Processing; } break; } } catch (Exception ex) { CommonLog.ErrorLogWrite(this, "VmHappyPointUsing_PropertyChanged()", "Fail !!", string.Format("{0}\n{1}", ex.Message, ex.StackTrace)); } } #endregion Event Handlers } }