/// <summary> /// トークテキスト設定の実処理を行う。 /// </summary> /// <param name="text">設定するトークテキスト。</param> /// <returns>成功したならば true 。そうでなければ false 。</returns> protected override async Task <bool> DoSetTalkText(string text) { var edit = this.TalkEdit; if (edit == null) { return(false); } // 500文字あたり1ミリ秒をタイムアウト値に追加 var timeout = UIControlTimeout + (text.Length / 500); try { if (!(await Task.Run(() => edit.SetText(text, timeout)))) { ThreadTrace.WriteLine( @"トークテキスト設定処理がタイムアウトしました。 " + nameof(text) + @".Length=" + text.Length); return(false); } } catch (Exception ex) { ThreadTrace.WriteException(ex); return(false); } return(true); }
/// <summary> /// WAVEファイルパスをファイル名エディットへ設定する処理を行う。 /// </summary> /// <param name="fileNameEdit">ファイル名エディット。</param> /// <param name="filePath">WAVEファイルパス。</param> /// <returns>成功したならば true 。そうでなければ false 。</returns> private async Task <bool> DoSetFilePathToEditTask( AutomationElement fileNameEdit, string filePath) { if (fileNameEdit == null || string.IsNullOrWhiteSpace(filePath)) { return(false); } // 入力可能状態まで待機 if (!(await this.WhenForInputIdle())) { ThreadTrace.WriteLine(@"入力可能状態になりません。"); return(false); } // フォーカス try { fileNameEdit.SetFocus(); } catch (Exception ex) { ThreadTrace.WriteException(ex); return(false); } // VOICEROID2ライクソフトウェアでは、 // Windowsのフォルダーオプションで拡張子を表示しない設定にしていると、 // ValuePattern.SetValue によるファイルパス設定が無視され、 // 元々入力されている前回保存時の名前が参照されてしまう。 // 一旦キー入力を行うことで回避できるようなので、適当な文字を送ってみる。 // 適当な文字をファイル名エディットへ送信 // 失敗しても先へ進む try { var editWin = new Win32Window(new IntPtr(fileNameEdit.Current.NativeWindowHandle)); editWin.SendMessage( WM_CHAR, new IntPtr('x'), IntPtr.Zero, UIControlTimeout); } catch { } // ファイルパス設定 if (!SetElementValue(fileNameEdit, filePath)) { ThreadTrace.WriteLine(@"ファイルパスを設定できません。"); return(false); } return(true); }
DoFindFileDialogElements(AutomationElement fileDialog) { // 入力可能状態まで待機 if (!(await this.WhenForInputIdle())) { ThreadTrace.WriteLine(@"入力可能状態になりません。"); return(null); } // OKボタン AutomationElement 検索 var okButton = await RepeatUntil( () => FindFirstChildByAutomationId(fileDialog, @"1"), elem => elem != null, 50); if (okButton == null) { return(null); } // ファイル名エディットホスト AutomationElement 検索 var editHost = await RepeatUntil( () => fileDialog.FindFirst( TreeScope.Descendants, new PropertyCondition( AutomationElement.AutomationIdProperty, @"FileNameControlHost")), elem => elem != null, 50); if (editHost == null) { return(null); } // ファイル名エディット AutomationElement 検索 var edit = await RepeatUntil( () => FindFirstChild( editHost, AutomationElement.ClassNameProperty, @"Edit"), elem => elem != null, 50); if (edit == null) { return(null); } return(Tuple.Create(okButton, edit)); }
/// <summary> /// 現在の <see cref="State"/> では処理を行えないことを示すメッセージを作成する。 /// </summary> /// <returns>エラーメッセージ。アイドル状態である場合は null 。</returns> protected string MakeStateErrorMessage() { var state = this.State; if (state == TalkerState.Idle) { return(null); } var stateMessage = this.StateMessage; if (stateMessage != null) { return(stateMessage); } switch (state) { case TalkerState.None: return(@"本体が起動していません。"); case TalkerState.Fail: return(@"不正な状態です。"); case TalkerState.Startup: return(@"本体が起動完了していません。"); case TalkerState.Cleanup: return(@"本体が終了処理中です。"); case TalkerState.Speaking: return(@"トーク中は処理できません。"); case TalkerState.Blocking: return(@"本体が処理できない状態です。"); case TalkerState.FileSaving: return(@"本体の音声保存中は処理できません。"); case TalkerState.Idle: return(null); default: break; } ThreadTrace.WriteLine($@"Invalid talker state. ({(int)this.State})"); return(@"不正な状態です。"); }
/// <summary> /// WAVEファイルの保存確認処理を行う。 /// </summary> /// <param name="filePath">WAVEファイルパス。</param> /// <returns>保存されているならば true 。そうでなければ false 。</returns> private async Task <bool> DoCheckFileSavedTask(string filePath) { if (string.IsNullOrWhiteSpace(filePath)) { return(false); } // ファイル保存完了 or ダイアログ表示 を待つ // ファイル保存完了なら null を返す var dialogs = await RepeatUntil( async() => File.Exists(filePath)?null : (await this.FindDialogs()), dlgs => (dlgs == null || dlgs.Count > 0), 150); if (dialogs != null) { // 保存進捗ダイアログ以外のダイアログが出ているなら失敗 if (!dialogs.ContainsKey(DialogType.SaveProgress)) { ThreadTrace.WriteLine( @"保存進捗ダイアログ以外のダイアログが開いています。 dialogs=" + string.Join( @",", dialogs.Where(v => v.Value != null).Select(v => v.Key))); return(false); } // 保存進捗ダイアログが閉じるまで待つ await RepeatUntil( () => this.FindDialog(DialogType.SaveProgress), (Win32Window d) => d == null); // 改めてファイル保存完了チェック if (!(await RepeatUntil(() => File.Exists(filePath), f => f, 25))) { return(false); } } // 同時にテキストファイルが保存される場合があるため少し待つ // 保存されていなくても失敗にはしない var txtPath = Path.ChangeExtension(filePath, @".txt"); await RepeatUntil(() => File.Exists(txtPath), f => f, 10); return(true); }
/// <summary> /// ファイル名エディットコントロールの入力内容確定処理を行う。 /// </summary> /// <param name="okButton">ファイルダイアログのOKボタン。</param> /// <param name="fileDialogParent">ファイルダイアログの親。</param> /// <returns>成功したならば true 。そうでなければ false 。</returns> private async Task <bool> DoDecideFilePathTask( AutomationElement okButton, AutomationElement fileDialogParent) { if (okButton == null || fileDialogParent == null) { return(false); } // 入力可能状態まで待機 if (!(await this.WhenForInputIdle())) { ThreadTrace.WriteLine(@"入力可能状態になりません。"); return(false); } // フォーカス try { okButton.SetFocus(); } catch (Exception ex) { ThreadTrace.WriteException(ex); return(false); } // OKボタン押下 if (!InvokeElement(okButton)) { ThreadTrace.WriteLine(@"OKボタンを押下できません。"); return(false); } // ファイルダイアログが閉じるまで待つ try { var closed = await RepeatUntil( () => !FindChildWindows( fileDialogParent, SaveFileDialogName) .Any(), f => f, 150); if (!closed) { ThreadTrace.WriteLine(@"ファイルダイアログの終了を確認できません。"); return(false); } } catch (Exception ex) { ThreadTrace.WriteException(ex); return(false); } return(true); }
/// <summary> /// 音声保存オプションウィンドウのOKボタンを押下し、ファイルダイアログを取得する。 /// </summary> /// <param name="dialog">音声保存オプションウィンドウ。</param> /// <returns>ファイルダイアログ。見つからなければ null 。</returns> private async Task <AutomationElement> DoPushOkButtonOfSaveOptionDialogTask( AutomationElement optionDialog) { if (optionDialog == null) { throw new ArgumentNullException(nameof(optionDialog)); } // 入力可能状態まで待機 if (!(await this.WhenForInputIdle())) { ThreadTrace.WriteLine(@"入力可能状態になりません。"); return(null); } // OKボタン検索 var okButton = await RepeatUntil( () => FindFirstChild( optionDialog, AutomationElement.NameProperty, @"OK"), elem => elem != null, 50); if (okButton == null) { ThreadTrace.WriteLine(@"OKボタンが見つかりません。"); return(null); } AutomationElement fileDialog = null; try { // OKボタン押下 if (!InvokeElement(okButton)) { ThreadTrace.WriteLine(@"OKボタンを押下できません。"); return(null); } // ファイルダイアログ検索 fileDialog = await RepeatUntil( () => FindFirstChildByControlType( optionDialog, ControlType.Window), elem => elem != null, 150); if (fileDialog?.Current.Name != SaveFileDialogName) { ThreadTrace.WriteLine(@"ファイルダイアログが見つかりません。"); return(null); } } catch (Exception ex) { ThreadTrace.WriteException(ex); return(null); } return(fileDialog); }
/// <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="uiAutomationEnabled"> /// UI Automation の利用を許可するならば true 。 /// </param> /// <returns> /// 保存ダイアログアイテムセット。 /// ファイル名エディットが見つからなければ null 。 /// </returns> private async Task <SaveDialogItemSet> DoFindSaveDialogItemSetTask( bool uiAutomationEnabled) { // いずれかのダイアログが出るまで待つ var dialogs = await RepeatUntil(this.FindDialogs, dlgs => dlgs.Count > 0, 150); if (dialogs.Count <= 0) { ThreadTrace.WriteLine(@"ダイアログ検索処理がタイムアウトしました。"); return(null); } // 保存ダイアログ取得 Win32Window dialog = null; if (!dialogs.TryGetValue(DialogType.Save, out dialog)) { ThreadTrace.WriteLine(@"音声保存ダイアログが見つかりません。"); return(null); } // AutomationElement 検索 AutomationElement okButtonElem = null; AutomationElement editElem = null; if (uiAutomationEnabled) { var dialogElem = MakeElementFromHandle(dialog.Handle); if (dialogElem != null) { var elems = await this.DoFindFileDialogElements(dialogElem); if (elems != null) { okButtonElem = elems.Item1; editElem = elems.Item2; } } } // ファイル名エディットコントロール検索 var editControl = await RepeatUntil( () => FindFileDialogFileNameEdit(dialog), (Win32Window c) => c != null, 50); if (editControl == null) { if (uiAutomationEnabled && okButtonElem == null) { ThreadTrace.WriteLine(@"OKボタンが見つかりません。"); return(null); } if (editElem == null) { ThreadTrace.WriteLine(@"ファイル名入力欄が見つかりません。"); return(null); } } return (new SaveDialogItemSet( uiAutomationEnabled, editElem, okButtonElem, editControl)); }
/// <summary> /// 再生処理を行う。 /// </summary> /// <param name="parameter">コマンドパラメータ。</param> /// <returns>コマンドの戻り値。</returns> private async Task <CommandResult> ExecutePlay(CommandParameter parameter) { var process = parameter.Process; // 本体側のテキストを使わない場合のみテキスト設定を行う if (!parameter.UseTargetText) { // テキスト取得 var text = parameter.TalkText; if (text == null) { return (MakeResult( parameter, AppStatusType.Fail, @"再生処理を開始できませんでした。")); } // テキスト置換 text = parameter.VoiceReplaceItems?.Replace(text) ?? text; // テキスト設定 bool setOk = await process.SetTalkText(text); if (!setOk && process.Id.IsVoiceroid2LikeSoftware()) { // VOICEROID2ライクの場合、本体の入力欄が読み取り専用になることがある。 // 一旦 再生→停止 の操作を行うことで解除を試みる if (!(await process.Play())) { ThreadTrace.WriteLine(@"VOICEROID2文章入力欄の復旧(再生)に失敗"); return (MakeResult( parameter, AppStatusType.Fail, @"再生処理に失敗しました。")); } setOk = (await process.Stop()) && (await process.SetTalkText(text)); if (!setOk) { ThreadTrace.WriteLine(@"VOICEROID2文章入力欄の復旧に失敗"); } } if (!setOk) { return (MakeResult( parameter, AppStatusType.Fail, @"文章の設定に失敗しました。")); } } // 再生 var success = await process.Play(); return (MakeResult( parameter, success ? AppStatusType.Success : AppStatusType.Fail, success ? @"再生処理に成功しました。" : @"再生処理に失敗しました。")); }