/// <summary> /// 設定を書き出す。 /// </summary> /// <returns>成功したならば true 。失敗したならば false 。</returns> public bool Save() { lock (this.LockObject) { try { // 親ディレクトリ作成 var dirPath = Path.GetDirectoryName(Path.GetFullPath(this.FilePath)); if (!Directory.Exists(dirPath)) { Directory.CreateDirectory(dirPath); } // 書き出し using (var stream = File.Create(this.FilePath)) { this.Serializer.WriteObject(stream, this.Value); } } catch (Exception ex) { ThreadDebug.WriteException(ex); return(false); } } return(true); }
/// <summary> /// タイムラインウィンドウの追加ボタンを押下する。 /// </summary> /// <returns>成功したならば true 。そうでなければ false 。</returns> public async Task <bool> ClickTimelineSpeechAddButton() { if (!this.IsTimelineElementFound) { return(false); } // InvokePattern 取得 var button = GetPattern <InvokePattern>(this.AddButtonElement, InvokePattern.Pattern); if (button == null) { ThreadDebug.WriteLine( @"YMM3 : AddButtonElement から InvokePattern を取得できない。"); return(false); } // 押下 try { await this.WhenForInputIdle(); button.Invoke(); } catch (Exception ex) { ThreadDebug.WriteException(ex); return(false); } return(true); }
/// <summary> /// 音声保存処理の完了を待機する。 /// </summary> /// <param name="mainWindow">メインウィンドウ。</param> /// <param name="saveMenuAsync"> /// WAV書き出しメニュークリック処理に用いた非同期オブジェクト。 /// </param> /// <returns>成功したならば true 。そうでなければ false 。</returns> private Result <bool> SaveFileImpl_WaitSaving(dynamic mainWindow, Async saveMenuAsync) { Debug.Assert((DynamicAppVar)mainWindow != null); Debug.Assert(saveMenuAsync != null); try { // WAV書き出しメニュークリック処理とビジー表示が両方完了するまで待機 while (!saveMenuAsync.IsCompleted || IsBusyIndicatorVisible(mainWindow)) { Thread.Yield(); } // 保存完了ダイアログを探す var completeDialog = WaitUntil( () => { var win = WindowControl.FromZTop(this.TargetApp); return ((win.GetWindowText() == SaveCompleteDialogTitle) ? win : null); }, win => win != null); if (completeDialog == null) { return(false, @"本体の保存完了ダイアログが見つかりません。"); } // 保存完了ダイアログのOKボタンを押下する // 失敗してもよい try { var vtree = this.TargetAppVisualTree; var border = vtree.GetDescendant(completeDialog, 0); var buttonParent = border .Child // StackPanel .Children[1] // Border .Child // StackPanel .Children[1]; // Control var button = vtree.GetDescendant(buttonParent, 0); PerformClick(button); } catch (Exception ex) { ThreadDebug.WriteException(ex); } } catch (Exception ex) { ThreadTrace.WriteException(ex); return(false, @"音声保存処理の完了待機に失敗しました。"); } return(true); }
/// <summary> /// 現在のパラメータ一覧を取得する。 /// </summary> /// <param name="targetParameterIds"> /// 取得対象のパラメータID列挙。 null ならば存在する全パラメータを対象とする。 /// </param> /// <returns> /// パラメータIDとその値のディクショナリ。取得できなかった場合は null 。 /// </returns> protected override Result <Dictionary <ParameterId, decimal> > GetParametersImpl( IEnumerable <ParameterId> targetParameterIds) { // パラメータ値を取得するローカルメソッド Result <decimal> getParameter(ParameterId id, dynamic slider) { try { return((decimal)(double)slider.Value); } catch (Exception ex) { ThreadDebug.WriteException(ex); } return( 0, GetParameterTabItemName(id) + @"タブの" + id.GetInfo().DisplayName + @"値を取得できませんでした。"); } try { return(this.ProcessParameterSliders(getParameter, targetParameterIds)); } catch (Exception ex) { ThreadTrace.WriteException(ex); } return(null, @"パラメータの取得に失敗しました。"); }
/// <summary> /// AutomationElement の ValuePattern に文字列値を設定する。 /// </summary> /// <param name="element">設定対象の AutomationElement 。</param> /// <param name="value">設定する文字列値。</param> /// <returns>成功したならば true 。そうでなければ false 。</returns> protected static bool SetElementValue(AutomationElement element, string value) { if (element == null) { throw new ArgumentNullException(nameof(element)); } try { if (!element.TryGetCurrentPattern(ValuePattern.Pattern, out var pattern)) { return(false); } var vp = (ValuePattern)pattern; if (vp.Current.IsReadOnly) { ThreadDebug.WriteLine(@"The element is readonly."); return(false); } vp.SetValue(value); } catch (Exception ex) { ThreadTrace.WriteException(ex); return(false); } return(true); }
/// <summary> /// タイムラインウィンドウのセリフエディットにテキストを設定する。 /// </summary> /// <param name="text">設定するテキスト。</param> /// <returns>成功したならば true 。そうでなければ false 。</returns> public async Task <bool> SetTimelineSpeechEditValue(string text) { if (!this.IsTimelineElementFound) { return(false); } // ValuePattern 取得 var edit = GetPattern <ValuePattern>(this.SpeechEditElement, ValuePattern.Pattern); if (edit == null || edit.Current.IsReadOnly) { ThreadDebug.WriteLine( @"YMM3 : SpeechEditElement から ValuePattern を取得できない。"); return(false); } // テキスト設定 try { await this.WhenForInputIdle(); edit.SetValue(text); } catch (Exception ex) { ThreadDebug.WriteException(ex); return(false); } return(true); }
/// <summary> /// 設定を読み取る。 /// </summary> /// <returns>成功したならば true 。失敗したならば false 。</returns> public bool Load() { // ファイルがなければ読み取れない if (!File.Exists(this.FilePath)) { return(false); } lock (this.LockObject) { try { // 読み取り using (var stream = File.OpenRead(this.FilePath)) { var value = this.Serializer.ReadObject(stream); if (!(value is T)) { return(false); } this.Value = (T)value; } } catch (Exception ex) { ThreadDebug.WriteException(ex); return(false); } } return(true); }
/// <summary> /// AutomationElement 群を更新する。 /// </summary> /// <param name="timelineWindow">タイムラインウィンドウ AutomationElement 。</param> /// <returns>成功したならば true 。そうでなければ false 。</returns> private bool UpdateElements(AutomationElement timelineWindow) { if (timelineWindow == null) { throw new ArgumentNullException(nameof(timelineWindow)); } // タイムラインコントロール検索 var tlCtrl = FindFirstChild( timelineWindow, AutomationElement.ClassNameProperty, @"TimelineControl"); if (tlCtrl == null) { ThreadDebug.WriteLine(@"The timeline control is not found."); return(false); } // セリフエディット検索 var speechEdit = FindFirstChild(tlCtrl, AutomationElement.AutomationIdProperty, @"SerifuTB"); if (speechEdit == null) { ThreadDebug.WriteLine(@"The speech edit is not found."); return(false); } // キャラ選択コンボボックス検索 var charaCombo = FindFirstChild( tlCtrl, AutomationElement.AutomationIdProperty, @"CharactersCB"); if (charaCombo == null) { ThreadDebug.WriteLine(@"The character combo-box is not found."); return(false); } // 追加ボタン検索 var addButton = FindFirstChild(tlCtrl, AutomationElement.NameProperty, @"追加"); if (addButton == null) { ThreadDebug.WriteLine(@"The add button is not found."); return(false); } // 更新 this.SpeechEditElement = speechEdit; this.CharaComboElement = charaCombo; this.AddButtonElement = addButton; return(true); }
/// <summary> /// キャストコンボボックスからキャスト名キャッシュを作成する。 /// </summary> /// <param name="castComboBox">キャストコンボボックス。</param> /// <returns>成功したならば true 。そうでなければ false 。</returns> private Result <bool> CreateCastNamesCache(dynamic castComboBox) { if (castComboBox == null) { return(false, @"本体のキャスト一覧が見つかりません。"); } // castComboBox.Items[N].Name でキャスト名を取得できるが、 // CeVIO定義の型を参照することになるのでやめておく。 var casts = new List <string>(); int selectedIndex = -1; try { // 選択中インデックスを保存 selectedIndex = (int)castComboBox.SelectedIndex; // 各キャストを選択して ComboBox.Text からキャスト名を取得 var count = (int)castComboBox.Items.Count; for (int ii = 0; ii < count; ++ii) { castComboBox.SelectedIndex = ii; casts.Add((string)castComboBox.Text); } } catch (Exception ex) { ThreadTrace.WriteException(ex); return(false, @"キャスト一覧の取得に失敗しました。"); } finally { // 選択中インデックスを元に戻す // 失敗してもよい if (selectedIndex >= 0) { try { castComboBox.SelectedIndex = selectedIndex; } catch (Exception ex) { ThreadDebug.WriteException(ex); } } } this.CastNamesCache = casts.AsReadOnly(); return(true); }
/// <summary> /// WAVEファイルパスをファイル名エディットへ設定する処理を行う。 /// </summary> /// <param name="itemSet">保存ダイアログアイテムセット。</param> /// <param name="filePath">WAVEファイルパス。</param> /// <returns>成功したならば true 。そうでなければ false 。</returns> private async Task <bool> DoSetFilePathToEditTask( SaveDialogItemSet itemSet, string filePath) { if (itemSet == null || string.IsNullOrWhiteSpace(filePath)) { return(false); } // まず UI Automation を試す Exception exAuto = null; if (itemSet.IsUIAutomationEnabled) { try { await this.DoSetFilePathToEditTaskByAutomation(itemSet, filePath); return(true); } catch (Exception ex) { exAuto = ex; } } // コントロール操作を試す try { await this.DoSetFilePathToEditTaskByControl(itemSet, filePath); if (exAuto != null) { ThreadDebug.WriteException(exAuto); } return(true); } catch (Exception exCtrl) { if (exAuto != null) { ThreadTrace.WriteException(exAuto); } ThreadTrace.WriteException(exCtrl); } return(false); }
/// <summary> /// ビジー表示中であるか否かを取得する。 /// </summary> /// <param name="mainWindow">メインウィンドウ。</param> /// <returns>ビジー表示中ならば true 。そうでなければ false 。</returns> private static bool IsBusyIndicatorVisible(dynamic mainWindow) { if (mainWindow != null) { try { // Xceed.Wpf.Toolkit.BusyIndicator.IsBusy により調べる return((bool)mainWindow.Content.Children[1].IsBusy); } catch (Exception ex) { ThreadDebug.WriteException(ex); } } return(false); }
/// <summary> /// セリフデータグリッドを取得し、単一行を選択する。 /// </summary> /// <returns>セリフデータグリッド。失敗したならば null 。</returns> /// <remarks> /// セリフデータグリッド自体は単一行選択設定なのだが、 /// CeVIO独自のコントロール拡張により追加の行選択が行われている場合がある。 /// そのまま音声保存しようとすると連続書き出しになってしまうため、 /// 一旦別の行を選択することで追加の行選択状態を解除する。 /// </remarks> private Result <WpfDataGrid> SaveFileImpl_SelectSingleDataGridRow() { // セリフデータグリッドを取得 var(grid, failMessage) = this.SpeechDataGrid.Get(); if (grid == null) { return(null, failMessage); } int selectedRow = -1; try { selectedRow = grid.SelectedRow; // 一旦別の行を選択して元の行に戻す grid.Focus(); grid.SelectedRow = (selectedRow > 0) ? (selectedRow - 1) : 1; grid.SelectedRow = selectedRow; selectedRow = -1; } catch (Exception ex) { ThreadTrace.WriteException(ex); return(null, @"本体のセリフ一覧表を操作できませんでした。"); } finally { // 元の行に戻す // 失敗してもよい if (selectedRow >= 0) { try { grid.SelectedRow = selectedRow; } catch (Exception ex) { ThreadDebug.WriteException(ex); } } } return(grid); }
/// <summary> /// ファイル名エディットコントロールの入力内容確定処理を行う。 /// </summary> /// <param name="itemSet">保存ダイアログアイテムセット。</param> /// <returns>成功したならば true 。そうでなければ false 。</returns> private async Task <bool> DoDecideFilePathTask(SaveDialogItemSet itemSet) { if (itemSet == null) { return(false); } // まず UI Automation を試す Exception exAuto = null; if (itemSet.IsUIAutomationEnabled) { try { await this.DoDecideFilePathTaskByAutomation(itemSet); return(true); } catch (Exception ex) { exAuto = ex; } } // コントロール操作を試す try { await this.DoDecideFilePathTaskByControl(itemSet); if (exAuto != null) { ThreadDebug.WriteException(exAuto); } return(true); } catch (Exception exCtrl) { if (exAuto != null) { ThreadTrace.WriteException(exAuto); } ThreadTrace.WriteException(exCtrl); } return(false); }
/// <summary> /// Func デリゲートを用いてコントロールから要素を取得する。 /// </summary> /// <param name="root">コントロール。</param> /// <param name="func">Func デリゲート。</param> /// <returns>Func デリゲートの戻り値。 root が null ならば default(T) 。</returns> private static T GetByFunc <T>(dynamic root, Func <dynamic, T> func) { Debug.Assert(func != null); if (root != null) { try { return(func(root)); } catch (Exception ex) { ThreadDebug.WriteException(ex); } } return(default(T)); }
/// <summary> /// 状態を更新する。 /// </summary> /// <returns> /// メインウィンドウが開いているならば true 。そうでなければ false 。 /// </returns> public async Task <bool> Update() { // プロセス検索 this.Process = Process.GetProcessesByName(ProcessName).FirstOrDefault(); if (this.Process == null) { this.Reset(); return(false); } // 入力待機状態待ち if (!(await this.WhenForInputIdle())) { ThreadDebug.WriteLine(@"YMM3 : WaitForInputIdle() == false"); this.Reset(); return(false); } // メインウィンドウハンドル確認 this.Process.Refresh(); if (this.MainWindowHandle == IntPtr.Zero) { ThreadDebug.WriteLine(@"YMM3 : process.MainWindowHandle == IntPtr.Zero"); this.Reset(); return(false); } // メインウィンドウが見つかったのでこれ以降は true を返す // タイムラインウィンドウ検索 var tlWin = await this.FindTimelineWindow(); this.IsTimelineWindowFound = (tlWin != null); // AutomationElement 群更新 if (!this.IsTimelineWindowFound || !this.UpdateElements(tlWin)) { this.ResetElements(); } return(true); }
/// <summary> /// 文章入力欄下にあるボタンコントロールを取得する。 /// </summary> /// <param name="parent">ボタン群の親コントロール。</param> /// <param name="button">ボタン種別。</param> /// <returns>コントロール。取得できなかった場合は null 。</returns> private static dynamic GetMainButton(dynamic parent, MainButton button) { if (parent == null) { return(null); } if (!EnumCache <MainButton> .HashSet.Contains(button)) { return(null); } try { return(parent.Children[(int)button]); } catch (Exception ex) { ThreadDebug.WriteException(ex); } return(null); }
/// <summary> /// ボイスプリセットタブコントロールに対する処理を行う。 /// </summary> /// <typeparam name="T">処理結果値の型。</typeparam> /// <param name="processor">処理デリゲート。</param> /// <param name="tabSelectionRollbackDecider"> /// 処理完了時に選択タブアイテムを最初の状態に戻すか否かを判断するデリゲート。 /// 第1引数には processor が完了したならばその戻り値、 /// そうでなければ null が渡される。 /// 第2引数には processor が例外送出したならばその例外、 /// そうでなければ null が渡される。 /// このデリゲートが true を返すならば戻し処理を実施する。 /// null を指定した場合は常に戻し処理を実施する。 /// </param> /// <returns>処理デリゲートの戻り値。開始処理に失敗した場合は default(T) 。</returns> private Result <T> ProcessPresetTabControl <T>( PresetTabControlDelegate <T> processor, Func <Result <T>?, Exception, bool> tabSelectionRollbackDecider = null) { ArgumentValidation.IsNotNull(processor, nameof(processor)); // タブコントロールを取得 var(tabControl, failMessage) = this.GetPresetTabControl(); if (tabControl == null) { return(default(T), failMessage); } // 現在選択中のタブアイテムインデックスとタブアイテム数を保存 int selectedIndex = 0, tabItemCount = 0; try { selectedIndex = (int)tabControl.SelectedIndex; tabItemCount = (int)tabControl.Items.Count; if (selectedIndex < 0 || tabItemCount <= 0) { return(default(T), @"本体のボイスプリセットタブページが見つかりません"); } } catch (Exception ex) { ThreadTrace.WriteException(ex); return( default(T), @"本体のボイスプリセットタブページから情報を取得できませんでした。"); } bool?rollback = false; try { var result = processor(tabControl, tabItemCount, selectedIndex); rollback = tabSelectionRollbackDecider?.Invoke(result, null); return(result); } catch (Exception ex) { rollback = tabSelectionRollbackDecider?.Invoke(null, ex); throw ex; } finally { // 戻り処理有効ならば元のタブアイテムを選択する // 失敗してもよい if (rollback != false) { try { tabControl.SelectedIndex = selectedIndex; } catch (Exception ex) { ThreadDebug.WriteException(ex); } } } }
/// <summary> /// <see cref="Talker.ProcessParameterSliders{T}"/> の実処理を行う。 /// </summary> /// <typeparam name="T">処理結果値の型。</typeparam> /// <param name="mainWindow">メインウィンドウ。</param> /// <param name="visualTree">ビジュアルツリー走査用オブジェクト。</param> /// <param name="executer"> /// 処理デリゲート。 /// 戻り値の Message は成功ならば null 、失敗ならばエラーメッセージとすること。 /// </param> /// <param name="targetIds">処理対象パラメータID列挙。 null ならばすべて対象。</param> /// <returns></returns> public Result <Dictionary <ParameterId, T> > Execute <T>( dynamic mainWindow, WpfVisualTree visualTree, Executer <T> executer, IEnumerable <ParameterId> targetIds = null) { ArgumentValidation.IsNotNull(mainWindow, nameof(mainWindow)); ArgumentValidation.IsNotNull(visualTree, nameof(visualTree)); ArgumentValidation.IsNotNull(executer, nameof(executer)); // タブコントロールを取得 dynamic tabControl; try { tabControl = mainWindow.Content.Children[1].Children[2]; } catch (Exception ex) { ThreadTrace.WriteException(ex); return(null, @"本体のタブページが見つかりません。"); } // タブアイテムコレクションを取得 dynamic tabItems; try { tabItems = tabControl.Items; } catch (Exception ex) { ThreadTrace.WriteException(ex); return(null, @"本体のタブページが見つかりません。"); } var results = new Dictionary <ParameterId, T>(); var targetInfos = MakeGuiGroupTargetInfos(targetIds); // マスタータブ var masterGroups = new[] { GuiGroup.MasterEffect, GuiGroup.MasterPause }; if (masterGroups.Any(g => targetInfos[g].IdIndices.Any())) { var sliders = new Dictionary <ParameterId, dynamic>(); try { var panelsParent = tabItems[0].Content.Content.Children[0].Content.Children; foreach (var group in masterGroups) { AddLogicalSlidersTo(panelsParent, targetInfos[group], sliders); } } catch (Exception ex) { ThreadTrace.WriteException(ex); return(null, @"本体のマスタータブのスライダーが見つかりませんでした。"); } var message = ApplyExecuter(sliders, executer, results); if (message != null) { return(null, message); } } // ボイスタブ var presetEffectTargetInfo = targetInfos[GuiGroup.PresetEffect]; var presetEmotionTargetInfo = targetInfos[GuiGroup.PresetEmotion]; if ( presetEffectTargetInfo.IdIndices.Any() || presetEmotionTargetInfo.IdIndices.Any()) { var sliders = new Dictionary <ParameterId, dynamic>(); int tabIndex = -1; try { try { var panelsParent = tabItems[1].Content.Content.Children[2].Content.Children; // 音声効果 AddLogicalSlidersTo(panelsParent, presetEffectTargetInfo, sliders); // 現在のタブインデックスを保存 tabIndex = (int)tabControl.SelectedIndex; // 感情 AddPresetEmotionSlidersTo( tabControl, panelsParent, visualTree, presetEmotionTargetInfo, sliders); } catch (Exception ex) { ThreadTrace.WriteException(ex); return( null, @"本体のボイスタブのスライダーが見つかりませんでした。"); } var message = ApplyExecuter(sliders, executer, results); if (message != null) { return(null, message); } } finally { // 元のタブを選択する // 失敗してもよい if (tabIndex >= 0) { try { tabControl.SelectedIndex = tabIndex; } catch (Exception ex) { ThreadDebug.WriteException(ex); } } } } return(results); }
/// <summary> /// 現在の文章の読み上げを開始させる。 /// </summary> /// <returns>成功したならば true 。そうでなければ false 。</returns> protected override Result <bool> SpeakImpl() { // メインウィンドウを取得 var mainWin = this.GetMainWindow(); if (mainWin == null) { return(false, @"本体のウィンドウが見つかりません。"); } // ボタン群の親を取得 var parent = GetMainButtonsParent(mainWin); if (parent == null) { return(false, @"本体のボタンが見つかりません。"); } // 再生ボタン取得 var play = GetMainButton(parent, MainButton.Play); if (play == null) { return(false, @"本体の再生ボタンが見つかりません。"); } try { if (!(bool)play.IsEnabled) { return(false, @"本体の再生ボタンがクリックできない状態です。"); } // 先頭ボタン取得してクリック // 失敗しても先へ進む var head = GetMainButton(parent, MainButton.Head); try { if (head != null && (bool)head.IsEnabled) { PerformClick(head); } } catch (Exception ex) { ThreadDebug.WriteException(ex); } // 再生ボタンクリック var playAsync = new Async(); PerformClick(play, playAsync); // フレーズ編集未保存の場合等はダイアログが出るためそれを待つ // ダイアログが出ずに完了した場合は成功 var modalWin = new WindowControl(mainWin).WaitForNextModal(playAsync); if (modalWin != null) { var title = modalWin.GetWindowText(); return(false, $@"本体側で{title}ダイアログが表示されました。"); } } catch (Exception ex) { ThreadTrace.WriteException(ex); return(false, @"本体の再生ボタンをクリックできませんでした。"); } return(true); }
/// <summary> /// タイムラインウィンドウ AutomatinElement を検索する。 /// </summary> /// <returns>AutomatinElement 。見つからなければ null 。</returns> private async Task <AutomationElement> FindTimelineWindow() { if (this.MainWindowHandle == IntPtr.Zero) { return(null); } AutomationElement root = null; try { root = AutomationElement.FromHandle(this.MainWindowHandle); } catch (Exception ex) { ThreadDebug.WriteException(ex); return(null); } // タイムラインウィンドウ検索 var tlWin = FindAllChildren( root, AutomationElement.ControlTypeProperty, ControlType.Window) .FirstOrDefault( e => e.Current.Name.StartsWith(TimelineWindowTitlePrefix)); if (tlWin == null) { // 念のため Win32Window を使って検索してみる var processId = this.Process.Id; var win = await Win32Window.FromDesktop() .FindChildren() .ToObservable() .FirstOrDefaultAsync( w => w.ProcessId == processId && w.GetText(100)?.StartsWith(TimelineWindowTitlePrefix) == true); if (win == null) { return(null); } try { tlWin = AutomationElement.FromHandle(win.Handle); ThreadDebug.WriteLine( @"The timeline window is found by using Win32Window."); } catch (Exception ex) { ThreadDebug.WriteException(ex); return(null); } } return(tlWin); }
/// <summary> /// メインウィンドウがトップレベルである前提で、操作対象アプリの状態を調べる。 /// </summary> /// <param name="mainWindow">メインウィンドウ。必ずトップレベル。</param> /// <returns>状態値。</returns> protected override Result <TalkerState> CheckState(WindowControl mainWindow) { var mainWin = mainWindow.Dynamic(); // ビジー表示中なら音声保存中と判断する if (IsBusyIndicatorVisible(mainWin)) { return(TalkerState.FileSaving); } try { // ビジュアルツリー走査用オブジェクト取得or作成 var vtree = (mainWindow.App == this.TargetApp) ? this.TargetAppVisualTree : null; vtree = vtree ?? new WpfVisualTree(mainWindow.App); (var c, var rootMessage) = this.Root.Get(out bool compacted, (DynamicAppVar)mainWin, vtree); if (c != null) { (c, _) = this.ControlPanel.GetAny(out var kind, (DynamicAppVar)c, vtree); if (c != null) { // コントロールパネルがトーク用以外ならアイドル状態扱い if (kind != ControlPanelKind.Talk) { return(TalkerState.Idle); } (c, _) = this.OperationPanel.Get((DynamicAppVar)c); if (c != null) { var(button, _) = this.PlayStopToggle.Get((DynamicAppVar)c); if (button != null) { // 試聴/停止トグルボタンがONなら読み上げ中 // OFFならアイドル状態 return (((bool?)button.IsChecked == true) ? TalkerState.Speaking : TalkerState.Idle); } } } } else if (compacted) { // コンパクト表示はブロッキング中扱い return(TalkerState.Blocking, rootMessage); } } catch (Exception ex) { ThreadDebug.WriteException(ex); } // いずれかのコントロールが見つからないとここに来る // ウィンドウ構築途中or破棄途中であると判断する // 即ち起動中or終了中 return ((this.State == TalkerState.None || this.State == TalkerState.Startup) ? TalkerState.Startup : TalkerState.Cleanup); }
/// <summary> /// タイムラインウィンドウのキャラ選択コンボボックスからキャラを選択する。 /// </summary> /// <param name="name">選択するキャラ名。</param> /// <returns> /// 成功したならば true 。 /// キャラ名が存在しないならば null 。 /// どちらでもなければ false 。 /// </returns> public async Task <bool?> SelectTimelineCharaComboBoxItem(string name) { if (!this.IsTimelineElementFound) { return(false); } // すべてのアイテムを有効化させるためにコンボボックスを開閉する var expand = GetPattern <ExpandCollapsePattern>( this.CharaComboElement, ExpandCollapsePattern.Pattern); if (expand == null) { ThreadDebug.WriteLine( @"YMM3 : CharaComboElement から ExpandCollapsePattern を取得できない。"); return(false); } try { await this.WhenForInputIdle(); expand.Expand(); expand.Collapse(); } catch (Exception ex) { ThreadDebug.WriteException(ex); return(false); } // Name がキャラ名の子を持つコンボボックスアイテムUIを探す var nameCond = new PropertyCondition(AutomationElement.NameProperty, name); var itemElem = await FindAllChildren( this.CharaComboElement, AutomationElement.ControlTypeProperty, ControlType.ListItem) .ToObservable() .FirstOrDefaultAsync(i => FindFirstChild(i, nameCond) != null); if (itemElem == null) { // キャラ名存在せず return(null); } // SelectionItemPattern 取得 var item = GetPattern <SelectionItemPattern>(itemElem, SelectionItemPattern.Pattern); if (item == null) { ThreadDebug.WriteLine( @"YMM3 : CharaComboElement から SelectionItemPattern を取得できない。"); return(false); } // アイテム選択 try { await this.WhenForInputIdle(); item.Select(); } catch (Exception ex) { ThreadDebug.WriteException(ex); return(false); } return(true); }
/// <summary> /// セリフデータグリッドのコンテキストメニューを初期化して取得する。 /// </summary> /// <param name="dataGrid">セリフデータグリッド。</param> /// <returns>コンテキストメニュー。</returns> private static dynamic InitializeContextMenu(WpfDataGrid dataGrid) { var menu = dataGrid.Base.ContextMenu; if (menu == null) { ThreadTrace.WriteLine(@"speechDataGrid.ContextMenu is null."); return(null); } foreach (var item in menu.Items) { // メニュー項目名取得 // Separator の場合があるので例外は握り潰す string header; try { header = (string)item.Header; } catch { continue; } // メニュー項目に null があるなら要初期化 if (header == null) { bool initOk = false; // うまくいかないことがあるので何度か試す for (int retry = 0; retry <= ContextMenuInitRetryCount; ++retry) { // 初期化 try { dataGrid.Focus(); menu.PlacementTarget = dataGrid.Base; try { menu.IsOpen = true; } finally { menu.IsOpen = false; } } finally { menu.PlacementTarget = null; } // 非 null になったか確認 if ((string)item.Header != null) { initOk = true; #if DEBUG if (retry > 0) { ThreadDebug.WriteLine( @"Retry count of initializing ContextMenu : " + retry); } #endif // DEBUG break; } Thread.Yield(); } if (!initOk) { ThreadTrace.WriteLine(@"item.Header is already null."); return(null); } break; } } return(menu); }
/// <summary> /// 音声保存処理の完了を待機する。 /// </summary> /// <param name="saveButtonAsync"> /// 音声保存ボタンクリック処理に用いた非同期オブジェクト。 /// </param> /// <returns>成功したならば true 。そうでなければ false 。</returns> private Result <bool> SaveFileImpl_WaitSaving(Async saveButtonAsync) { Debug.Assert(saveButtonAsync != null); try { WindowControl completeDialog = null; // 保存完了ダイアログを探す for (; !saveButtonAsync.IsCompleted; Thread.Yield()) { // トップレベルウィンドウ群取得 var topWins = WindowControl.GetTopLevelWindows(this.TargetApp); // 保存完了ダイアログと思われるウィンドウはあるか? var dialog = topWins.FirstOrDefault( win => win.GetWindowText() == SaveCompleteDialogTitle); if (dialog != null) { // 親ウィンドウの型名で調べる方がより正確だが、 // VOICEROID2定義の型名を判定に使うことになるのでやめておく。 // 親ウィンドウが進捗ウィンドウまたはオプションウィンドウか? var parentTitle = dialog.ParentWindow?.GetWindowText(); if ( parentTitle == SaveProgressWindowTitle || parentTitle == SaveOptionWindowTitle) { completeDialog = dialog; break; } // トップレベルに進捗ウィンドウもあるか? // オプションウィンドウを表示している場合はこちらの場合もある if ( topWins.Any( win => win.GetWindowText() == SaveProgressWindowTitle)) { completeDialog = dialog; break; } } } if (completeDialog == null) { return(false, @"本体の保存完了ダイアログが見つかりません。"); } // 保存完了ダイアログのOKボタンを押下する // 失敗してもよい try { var button = completeDialog.IdentifyFromWindowClass(@"Button"); if (button != null) { new NativeButton(button).EmulateClick(); } } catch (Exception ex) { ThreadDebug.WriteException(ex); } } catch (Exception ex) { ThreadTrace.WriteException(ex); return(false, @"音声保存処理の完了待機に失敗しました。"); } return(true); }
/// <summary> /// トーク用コントロールパネルを取得する。 /// </summary> /// <param name="root"> /// ルートコントロール。 null ならばメソッド内で取得される。 /// </param> /// <param name="appVisualTree"> /// ビジュアルツリー走査用オブジェクト。 null ならばメソッド内で取得される。 /// </param> /// <returns>コントロール。見つからないか取得できない状態ならば null 。</returns> public Result <dynamic> GetTalk( dynamic root = null, WpfVisualTree appVisualTree = null) { // ルートコントロールを取得 var rootCtrl = root; if (rootCtrl == null) { var rv = this.Root.Get(); if (rv.Value == null) { return(null, rv.Message); } rootCtrl = rv.Value; } // コントロールパネル取得 var(panel, panelMessage) = this.GetAny(out var kind, (DynamicAppVar)rootCtrl, appVisualTree); if (panel == null) { return(null, panelMessage); } // 既にトーク用トラック選択中ならそのまま返す if (kind == ControlPanelKind.Talk) { return(panel); } // トラック選択変更不可なら失敗扱い if (!this.CanChangeTrackGetter()) { return(null, @"本体のトークトラックが選択されていません。"); } // トラックセレクタ取得 var(trackSelector, trackSelectorMessage) = this.Root.TrackSelector.Get((DynamicAppVar)rootCtrl); if (trackSelector == null) { return(null, trackSelectorMessage); } // トーク用トラックを選択する int?selectedIndex = null; try { // selector の Items[N].Category でトーク用/ソング用を判別できるが、 // CeVIO定義の型を参照することになるのでやめておく。 selectedIndex = (int)trackSelector.SelectedIndex; var count = (int)trackSelector.Items.Count; for (int ii = 0; ii < count; ++ii) { // 元々選択していたものはトーク用ではないのでスキップ if (ii == selectedIndex.Value) { continue; } // トラック選択変更 trackSelector.SelectedIndex = ii; // 改めてコントロールパネル取得 (panel, panelMessage) = this.GetAny(out kind, (DynamicAppVar)rootCtrl, appVisualTree); if (panel == null) { return(null, panelMessage); } if (kind == ControlPanelKind.Talk) { selectedIndex = null; // finally での戻し処理を行わせない return(panel, null); } } } catch (Exception ex) { ThreadTrace.WriteException(ex); } finally { // トーク用トラックが見つからなければ選択を元に戻しておく // 失敗してもよい if (selectedIndex.HasValue) { try { trackSelector.SelectedIndex = selectedIndex.Value; } catch (Exception ex) { ThreadDebug.WriteException(ex); } } } return(null, @"本体にトークトラックが追加されていません。"); }
/// <summary> /// プロセスの起動と、操作対象プロセスであるか否かの確認を行う。 /// </summary> /// <param name="processFilePath">実行ファイルパス。</param> /// <returns>起動したプロセス。失敗したならば null 。</returns> protected Result <Process> StartProcess(string processFilePath) { if (string.IsNullOrWhiteSpace(processFilePath)) { return(null, @"実行ファイルパスが不正です。"); } if (!File.Exists(processFilePath)) { return(null, @"実行ファイルが存在しません。"); } Process process = null; // 起動 try { process = Process.Start(processFilePath); if (process == null) { return(null, @"起動処理に失敗しました。"); } } catch (Exception ex) { ThreadTrace.WriteException(ex); return(null, @"起動処理に失敗しました。"); } bool succeeded = false; try { // 入力待機 try { if (!process.WaitForInputIdle()) { return(null, @"起動待機に失敗しました。"); } } catch (Exception ex) { // 管理者権限で起動する設定になっていてUACが有効だとここに来る ThreadTrace.WriteException(ex); return(null, @"管理者権限で起動した可能性があります。"); } try { // 操作対象プロセスか確認 if ( process.ProcessName != this.ProcessFileName || process.MainModule.FileVersionInfo.ProductName != this.ProcessProduct) { // 終了させる // 失敗してもよい try { if (!process.CloseMainWindow()) { process.Kill(); } } catch (Exception ex) { ThreadDebug.WriteException(ex); } return(null, @"操作対象ではありませんでした。"); } // 終了済みでないか確認 if (process.HasExited) { return(null, @"起動しましたが即終了しました。"); } } catch (Exception ex) { ThreadTrace.WriteException(ex); return(null, @"起動アプリの情報取得に失敗しました。"); } succeeded = true; } finally { if (!succeeded) { process.Dispose(); } } return(process); }