/// <summary> /// 分岐棋譜の時だけ、「消分岐」「次分岐」ボタンを有効にする。 /// </summary> private void UpdateButtonState() { using (var slb = new SuspendLayoutBlock(this)) { var index = GetListViewSelectedIndex(); var s = index < 0 ? null : listView1.Items[index]; if (s != null) { var item = (s as ListViewItem).SubItems[0].Text; // 本譜ボタン var e = item.StartsWith(">"); button1.Enabled = e; if (e) { item = item.Substring(1, 1); // 1文字目をskipして2文字目を取得 } var e2 = item.StartsWith("+") || item.StartsWith("*"); button2.Enabled = e2; button3.Enabled = e2; } // Items[0] == "開始局面"なので、そこ以降に指し手があればundo出来るのではないかと。(special moveであろうと) button4.Enabled = listView1.Items.Count > 1; } }
// -- handlers /// <summary> /// [UI thread] : リストが1行追加されたときに呼び出されるハンドラ /// </summary> private void KifuListAdded(PropertyChangedEventArgs args) { using (var slb = new SuspendLayoutBlock(this)) { // 増えた1行がargs.valueに入っているはず。 var row = args.value as KifuListRow; listView1.ItemSelectionChanged -= listView1_ItemSelectionChanged; listView1.Items.Add(KifuListRowToListItem(row)); ViewModel.KifuListCount = listView1.Items.Count; ViewModel.KifuList.Add(row); // ここも同期させておく。 var lastIndex = listView1.Items.Count - 1; ViewModel.SetKifuListSelectedIndex(lastIndex); // 末尾の項目を選択しておく。 SetListViewSelectedIndex(lastIndex); listView1.ItemSelectionChanged += listView1_ItemSelectionChanged; // item数が変化したはずなので、「消一手」ボタンなどを更新する。 UpdateButtonState(); } }
/// <summary> /// ViewModel.InTheGameの値が変更になったときに呼び出されるハンドラ /// </summary> /// <param name="args"></param> private void InTheGameChanged(PropertyChangedEventArgs args) { using (var s = new SuspendLayoutBlock(this)) { UpdateButtonLocation(); UpdateButtonState(); } }
/// <summary> /// リサイズ。 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void KifuControl_SizeChanged(object sender, EventArgs e) { if (TheApp.app.DesignMode) { return; } // メインウインドウに埋め込み時もフォント固定する。 //if (ViewModel.DockState != DockState.InTheMainWindow) using (var slb = new SuspendLayoutBlock(this)) { UpdateButtonLocation(); UpdateListViewColumnWidth(); } }
/// <summary> /// ListViewの4列目(総消費時間)を残りいっぱいサイズにする。 /// /// ※ 4列目を表示していない時は3列目が残り幅いっぱいになるようにする。 /// </summary> private void UpdateListViewColumnWidth() { var col = listView1.Columns; // Column 生成前 if (col.Count == 0) { return; } using (var sbc = new SuspendLayoutBlock(this)) { var last = enable_total_time ? 3 : 2; var sum = 0; // listView1.Margin.Left + listView1.Margin.Right; foreach (var i in All.Int(last)) { sum += col[i].Width; } // これ、ちゃんと設定してやらないと水平スクロールバーが出てきてしまう。 // ClientSizeはスクロールバーを除外したサイズ。 // → スクロールバーが出ていない状態で計算して、 // そのあとスクロールバーが出ると困るのだが…。押し戻す処理をするか。 // スクロールバーを常に表示するモードがないので、スクロールバーが出ているならその分を控えて計算するか。 // スクロールバーが10pxより小さいことはありえないので、それを超えているならスクロールバーが出ている。 /* * var scrollbar = listView1.Width - listView1.ClientSize.Width > 10; * var width = scrollbar ? * listView1.ClientSize.Width : * listView1.ClientSize.Width - 22; // 実測でこれくらい high dpiだと変わるかも.. */ // → これやめる。スクロールバーが出るとClientSizeが変化するのだから、そのときリサイズイベントが起きるのでは。 var width = listView1.ClientSize.Width; var newWidth = Math.Max(width - sum, 0); // Widthにはマイナスの値を設定しても0に補整される。この結果、上のMax()がないと、newWidthがマイナスだと // このifは成立してしまい、代入によってイベントが生起されるので無限再帰となる。 if (col[last].Width != newWidth) { col[last].Width = newWidth; //残りいっぱい分にする } } }
// 持ち時間が減っていくときに、持ち時間の部分だけの再描画をしたいのでそのためのフラグ //public bool DirtyRestTime //{ // get { return ViewModel.dirtyRestTime; } //} /// <summary> /// [UI thread] : Formのリサイズに応じて棋譜コントロールの移動などを行う。 /// </summary> public void ResizeKifuControl() { var kifu = kifuControl; if (kifu.ViewModel.DockState == DockState.InTheMainWindow) { using (var slb = new SuspendLayoutBlock(kifu)) { // (起動時) 先にLocationを移動させておかないとSizeの変更で変なところに表示されて見苦しい。 // ※ 理由はよくわからない。DockStyle.Noneにした影響か何か。 kifu.Size = Size.Empty; // resizeイベントが生起するようにいったん(0,0)にする。 kifu.Location = CalcKifuWindowLocation(); kifu.Size = CalcKifuWindowSize(); } } // 駒台が縦長のモードのときは、このコントロールは非表示にする。 // (何か別の方法で描画する) UpdateKifuControlVisibility(); }
/// <summary> /// 検討ウインドウのControlのサイズを調整。 /// (このウインドウに埋め込んでいるとき) /// </summary> public void ResizeConsiderationControl(PropertyChangedEventArgs args = null) { if (first_tick) { return; } // first_tick前だとengineConsiderationMainControl == nullだったりしてまずいのだ。 // 検討ウインドウをこのウインドウに埋め込んでいるときに、検討ウインドウをリサイズする。 using (var slb1 = new SuspendLayoutBlock(this)) using (var slb2 = new SuspendLayoutBlock(gameScreenControl1)) { int w = ClientSize.Width; int h = ClientSize.Height - gameScreenControl1.Location.Y; // メニューとToolStripの高さを引き算する。 var config = TheApp.app.Config; var dockManager = config.EngineConsiderationWindowDockManager; // 非表示のときはないものとして扱う。 // すなわち、DockWindow側にあるものとして、gameScreenControl1をDockStyle.Fillにする必要がある。 if (dockManager.DockState == DockState.InTheMainWindow && dockManager.Visible) { // メインウインドウに埋め込み時の検討ウインドウ高さの倍率 float height_rate = 1 + 0.25f * config.ConsiderationWindowHeightType; // 検討ウインドウの縦幅 var ch = (int)(height_rate * h / 4); DockUtility.Change(gameScreenControl1, DockStyle.None, new Size(w, h - ch), null /*いまの場所*/); DockUtility.Change(engineConsiderationMainControl, DockStyle.None, new Size(w, ch), new Point(0, ClientSize.Height - ch)); } else { DockUtility.Change(gameScreenControl1, DockStyle.Fill, new Size(w, h), null); DockUtility.Change(engineConsiderationMainControl, DockStyle.Fill, null, null); } } }
private void InitView() { #if false var file_name = TheApp.app.Config.CommercialVersion == 0 ? "html/about_dialog_opensource_version.html" : "html/about_dialog_commercial_version.html"; webBrowser1.Navigate(Path.Combine(Application.StartupPath, file_name).ToString()); // -- バージョン文字列をJavaScript経由でインジェクションする。 // 1回メッセージループを処理しないとNavigate()が終わらない。 Application.DoEvents(); var version_string = "MyShogi Version " + GlobalConfig.MYSHOGI_VERSION_STRING; webBrowser1.Document.InvokeScript("add_version_string", new string[] { version_string }); #endif // → Mac/Linuxで動作しないのでWebBrowserを使わない実装に変更。 using (var slb = new SuspendLayoutBlock(this)) { var commercial = TheApp.app.Config.CommercialVersion; var message1 = "MyShogi Version " + GlobalConfig.MYSHOGI_VERSION_STRING; var message2 = commercial == 0 ? "MyShogiは、2018年にマイナビ出版から発売する『将棋神やねうら王』用のGUIとして開発をスタートしました。" + "オープンソースの将棋ソフト用GUIです。(ただし画面素材等の単体配布、流用は不可です。)\r\n" + "MyShogi Project: https://github.com/yaneurao/MyShogi" : "(C)2018 Mynavi Publishing Corporation / MyShogi Project\r\nMyShogi Project : https://github.com/yaneurao/MyShogi"; label1.Text = message1; textBox1.Text = message2; pictureBox1.Image = TheApp.app.ImageManager.GameLogo.image; } }
/// <summary> /// 最後のDispatchした時刻。 /// </summary> //private Stopwatch lastDispatchTime = new Stopwatch(); /// <summary> /// [UI thread] : 一定時間ごとに(MainDialogなどのidle状態のときに)このメソッドを呼び出す。 /// /// queueに積まれているUsiThinkReportMessageを処理する。 /// </summary> public void OnIdle() { #if false // 前回dispatch(≒表示)してから200[ms]以上経過するまでメッセージをbufferingしておく。 // (このメソッドが呼び出されるごとに1つずつメッセージが増えているような状況は好ましくないため) if (lastDispatchTime.ElapsedMilliseconds < 200) { return; } #endif var queue = thinkQuque.GetList(); if (queue.Count > 0) { using (var slb = new SuspendLayoutBlock(this)) { // 子コントロールも明示的にSuspendLayout()~ResumeLayout()しないといけないらしい。 // Diagnosing Performance Problems with Layout : https://blogs.msdn.microsoft.com/jfoscoding/2005/03/04/suggestions-for-making-your-managed-dialogs-snappier/ foreach (var c in All.Int(2)) { ConsiderationInstance(c).SuspendLayout(); } // → これでも処理、間に合わないぎみ // 以下、メッセージのシュリンクを行う。 // 要らない部分をskipDisplay == trueにしてしまう。 // j : 前回のNumberOfInstanceの位置(ここ以降しか見ない) for (int i = 0, j = 0; i < queue.Count; ++i) { var e = queue[i]; switch (e.type) { case UsiEngineReportMessageType.NumberOfInstance: // ここ以前のやつ要らん。 for (var k = j; k < i; ++k) { queue[k].skipDisplay = true; } j = i; break; case UsiEngineReportMessageType.SetRootSfen: // rootが変わるので、以前のrootに対する思考内容は不要になる。 for (var k = j; k < i; ++k) { var q = queue[k]; var t = q.type; if ((t == UsiEngineReportMessageType.SetRootSfen || t == UsiEngineReportMessageType.UsiThinkReport) && q.number == e.number ) { q.skipDisplay = true; } } j = i; break; } } foreach (var e in queue) { DispatchThinkReportMessage(e); } foreach (var c in All.Int(2)) { ConsiderationInstance(c).ResumeLayout(); } } } #if false // 次回に呼び出された時のためにDispatchした時刻からの時間を計測する。 lastDispatchTime.Reset(); lastDispatchTime.Start(); #endif }
/// <summary> /// [UI thread] : 内部状態が変わったのでボタンの有効、無効を更新するためのハンドラ。 /// /// ViewModel.InTheGameが変更になった時に呼び出される。 /// </summary> /// <param name="inTheGame"></param> private void UpdateButtonLocation() { // 最小化したのかな? if (Width == 0 || Height == 0 || listView1.ClientSize.Width == 0) { return; } using (var slb = new SuspendLayoutBlock(this)) { UpdateListViewColumnWidth(); var inTheGame = ViewModel.InTheGame; // 非表示だったものを表示したのであれば、これによって棋譜が隠れてしまう可能性があるので注意。 //var needScroll = !button1.Visible && !inTheGame; // ボタンの表示は対局外のときのみ button1.Visible = !inTheGame; button2.Visible = !inTheGame; button3.Visible = !inTheGame; button4.Visible = !inTheGame; // フォントサイズ変更ボタンが有効か // 「+」「-」ボタンは、メインウインドウに埋め込んでいないときのみ // → やめよう メインウインドウ埋め込み時も有効にしよう。 var font_button_enable = !inTheGame; // && ViewModel.DockState != DockState.InTheMainWindow; button5.Visible = font_button_enable; button6.Visible = font_button_enable; // -- ボタンなどのリサイズ // ボタン高さ // 対局中は非表示。 int bh = button1.Height; int x = font_button_enable ? Width / 5 : Width / 4; int y = button1.Visible ? Height - bh : Height; listView1.Location = new Point(0, 0); listView1.Size = new Size(Width, y); if (!inTheGame) { button1.Location = new Point(x * 0, y); button1.Size = new Size(x, bh); button2.Location = new Point(x * 1, y); button2.Size = new Size(x, bh); button3.Location = new Point(x * 2, y); button3.Size = new Size(x, bh); button4.Location = new Point(x * 3, y); button4.Size = new Size(x, bh); button5.Location = new Point(x * 4, y); button5.Size = new Size(x / 2, bh); button6.Location = new Point((int)(x * 4.5), y); button6.Size = new Size(x / 2, bh); } //if (needScroll) { // 選択行が隠れていないことを確認しなければ..。 // SelectedIndexを変更すると、SelectedIndexChangedイベントが発生してしまうので良くない。 // 現在は、対局が終了した瞬間であるから、棋譜の末尾に移動して良い。 //SetListViewSelectedIndex(listView1.Items.Count - 1); // → こことは限らない。DockWindow化しただけかも知れない。 var index = ViewModel.KifuListSelectedIndex; EnsureVisible(index); } } }