- 投稿日:2021-01-10T23:23:02+09:00
Prism.WPFのIDialogService.ShowDialogで画面のセンターに表示させる
環境
Visual Studio 2019
Prism 8.0.0.1909
.NET5問題
アプリを起動するときにログインフォームを開くということがあると思います。
このログインフォームをIDialogService.ShowDialogを利用して表示させようとしましたが、オーナーウィンドウから呼び出すと、.Net Framework 4.8 の時はオーナーウィンドウが先に表示されて、それからダイアログが表示されていました。
ところが、.NET5にしたところ、オーナーウィンドウが表示されず、先にダイアログが表示されるようになっていました。
すると、ダイアログが適当な位置に表示されてしまい、画面の中央に表示するということができなくなってしまいました。dialogService.ShowDialog(PageKeys.Login, new DialogParameters { { "Login", inputValue } }, result => dialogResult = result);
よく見ると、オーナーウィンドウはあるようなのですが、透明でまだ表示されていなくて、ダイアログはそれに合わせて中央に表示というようにできないみたいでした。解決
いろいろと調べていたところ、PrismLibraryに解決方法が書かれていました
Style the DialogWindow
You can control the properties of the DialogWindow by using a style via an attatched property on the Dialog UserControl<prism:Dialog.WindowStyle> <Style TargetType="Window"> <Setter Property="prism:Dialog.WindowStartupLocation" Value="CenterScreen" /> <Setter Property="ResizeMode" Value="NoResize"/> <Setter Property="ShowInTaskbar" Value="False"/> <Setter Property="SizeToContent" Value="WidthAndHeight"/> </Style> </prism:Dialog.WindowStyle>いろいろ知らないことがあります(~_~;)
- 投稿日:2021-01-10T17:45:59+09:00
DataTemplateSelectorを使用したときにオブジェクト参照がオブジェクトインスタンスに設定されていませんとなる
はじめに
15年ぶりのプログラミング勉強(C#)を始めて、昨年自社アプリを作りました。
この時使用したのは、C#、WPF、Prism、.NET Framework4.8でした。
勉強しながら半分趣味で作ったので、勉強の進捗に合わせるように古いものほど中身がひどく、作り直したい気分です。最近Microsoftから.NET5が出たので、今度はそのアプリを.NET5対応にしていこうと思っています。
今まで使ったことのない技術など取り入れながら、そこでぶつかった問題や解決したことを描いていければと思います。DataTemplateSelectorを使ったらエラーになってしまった
エラーとなった部分はこんな感じ
<StackPanel Margin="15"> <TextBlock Text="{Binding Message}"/> <ContentControl Content="{Binding}" ContentTemplateSelector="{StaticResource MessageDialogTemplateSelector}"/> </StackPanel>すると、ContentControlのところに波線が出て、"XDG0066 Object reference not set to an instance of an object." (オブジェクト参照がオブジェクトインスタンスに設定されていません)となってしまいました。
利用したDataTemplateは
<UserControl.Resources> <DataTemplate x:Key="OkOnlyTemplate"> <Button Content="OK" Command="{Binding OkCommand}"/> </DataTemplate> <DataTemplate x:Key="YesNoTemplate"> <StackPanel Orientation="Horizontal"> <Button Content="Yes" Command="{Binding YesCommand}"/> <Button Content="No" Command="{Binding NoCommand}"/> </StackPanel> </DataTemplate> <DataTemplate x:Key="OkCancelTemplate"> <StackPanel Orientation="Horizontal"> <Button Content="OK" Command="{Binding OkCommand}"/> <Button Content="Cancel" Command="{Binding CancelCommand}"/> </StackPanel> </DataTemplate> <templateSelectors:MessageDialogTemplateSelector x:Key="MessageDialogTemplateSelector" OkOnlyTemplate="{StaticResource OkOnlyTemplate}" YesNoTemplate="{StaticResource YesNoTemplate}" OkCancelTemplate="{StaticResource OkCancelTemplate}"/> </UserControl.Resources>簡単なメッセージボックスを自作しようと思ったのですが、何がなんだか・・・と困惑。
ただ、ビルド実行はできました。原因
原因としては、Visual Studioのデザインビューを表示しようとしても、ContentControlに表示する内容が、デザイン時には決まっていないため、デザインビューを表示できないということのようでした。
じゃあ ContentTemplateをしてしまえばいいのでは?と思いましたが、MicrosoftのドキュメントでContentTemplateSelectorプロパティとプロパティの両方 ContentTemplate が設定されている場合、このプロパティは無視されますとの記述が・・・
解決
ビルドは実行できるし、このまま放っておこうか(笑)と思っていましたが、XAMLでデザイン時だけ使うというものがあったことを思い出しました。
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"これで、ContentControlのContentTemplateを設定してしまえばいいのでは?
<StackPanel Margin="15"> <TextBlock Text="{Binding Message}"/> <ContentControl Content="{Binding}" d:ContentTemplate="{StaticResource OkOnlyTemplate}" ContentTemplateSelector="{StaticResource MessageDialogTemplateSelector}"/> </StackPanel>これでエラーがなくなり、デザインビューも表示できるようになりました。
- 投稿日:2021-01-10T15:11:13+09:00
Unityで簡単なTPS操作をサクッと実装【キーボードWASD】
はじめに
初心者にとってUnityでキャラクターを操作する方法は結構複雑だなぁと感じたので、今回は簡単な例をスクリプトとしてまとめました。InputSystemの登場によって入力とアクションを疎結合にしやすくなるなど、便利な機能が増える一方、その場限りでちょっと試したい程度のときの実装コストは高くなりがちな印象です。
完成イメージ
WASD-移動
IJKL-視点 Space-ジャンプ 結論
以下のファイル(C#)をつくって、動かしたいキャラクターに貼り付ければ完成。
実際のコードは長くなるので、この記事の一番下の方に置いておきます。DefaultPlayer.csDefaultPlayer.cspublic class DefaultPlayer {...}準備と前提
キーボードからの入力を受け付ける手段として、新しい機能であるInputSystemを採用しています。
これはUnity2019.4以降を主な対象とした、従来のInputManager等(UnityEngine.Input)の代替手段です。
2020年1月現在ではデフォルトでは有効になっていないプロジェクトファイルが多いため、先にこれを有効にしておきます。[1.Window - Package Manager] > [2.Unity Resistry] > [3."inputsystem"と検索] > [4.install]
インストールしたあと、誘導に従っていくことでUnityが再起動する場合があります。(応用) キーによる操作を追加したい場合
移動速度やジャンプの高さを変更したり、任意のキーにオリジナルな操作を割り当てることができます。
先述のDefaultPlayer.csをプロジェクト内に置いたまま、CustomPlayer.csを作りましょう。
DefaultPlayerではなくCustomPlayerの方をキャラクターに貼り付けます。
- onStart() -- 押下開始
- onMiddle() -- 押下中
- onEnd() -- 押下終了
メソッドの中でplayerインスタンスに対して操作ができます。
操作できること一覧は IPlayerインターフェースの定義元を参照してください。CustomPlayerテンプレ.csusing System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using static UnityEngine.InputSystem.Keyboard; // ***************************************************************************** // CustomPlayer.cs // ***************************************************************************** // カスタム設定のプレイヤー 例 public class CustomPlayer : Player { // 前に動くスピード override public float moveSpeed () { return 2.0f; } // プレイヤーの高さ override public float height () { return 2.0f; } // デフォルト値で良いときは省略可 // ジャンプの高さ // override public float jumpHeight () // 重力の大きさ // virtual public float gravity () // カメラ回転の速さ // virtual public float lookSpeed () // 入力に使うキーを列挙 override public Input[] keyInputs () { return new Input[] { ... ここに使用したいカスタムキーを追加していく (例) new T (), // T new E (), // E new S (), // S new Space () // スペース ... }; } } // ***************************************************************************** // T.cs ファイルに分ても良い // ***************************************************************************** // キーボードの「T」を押して入力テストをする例 public class T : Input { public KeyControl key () { return current.tKey; // 入力に使うキーを指定: キーボードの「T」は"tKey" } public void onStart (IPlayer player) { Debug.Log ("テスト 開始"); // キーを押し始めた時: ログ出力 } public void onMiddle (IPlayer player, float deltaTime) { Debug.Log ("テスト 実施中"); // キーを押している間: ログ出力 } public void onEnd (IPlayer player) { Debug.Log ("テスト 終了"); // キーを押し終わった時: ログ出力 } } ... 〜(省略) E, S, スペースキーのクラス〜 ...実装の説明
さて、ここで少しだけ実装に触れておきます。キーワードを箇条書き。
- CharacterController
- Unityでキャラクターを移動操作する方法は大きく分けて3つ
- 1. transformを変更
- 2. RigidBodyで物理演算
- 3. CharacterControllerを使う
- 3を採用、物理演算を無視することで処理自体を軽くし、シンプルでかつ軽快なキャラクター操作ができる反面、氷で滑って移動(摩擦)や壁に押し出される(外部からの応力)ことは自動ではできなくなる。重力もコードによって再現。
- ポーリング
- 一般的な入力処理の分類
- 1. イベント通知型
- 2. ポーリング型
- 例えばボタンが押された時に、ボタンさんが「押されたよ」とわざわざ教えに来てくれるのが1、ボタンが押されていないかどうかをこちらから定期的に確認しにいくのが2。
- イベントの方が無駄な処理は減らしやすいが、今回はキャラクターを毎フレーム移動させるなどの処理をどのみち行うため、フレームごとにキー押下を確認する 2 で実装。特に重い処理はないので誤差の範囲内。
- アニメーション
- キャラクターの体が固まったまま動いてしまうのをなんとかする
- 今後コードを追記予定
- 別の記事にするかも
- マウス入力
- 複雑化するので今回は見送り
- 外付けコントローラーも同様
DefaultPlayer.cs
最後にコピペ用のスクリプトを貼っておきます。
なお筆者は、趣味でC#コードを書くときは一般的な命名ルールやC#特有機能をガン無視すると誓っておりますので、気になる方は大文字小文字や動詞名詞、コメント記法を調整してください。
DefaultPlayer.csusing System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using static UnityEngine.InputSystem.Keyboard; //*************************************************************** // DefaultPlayer.cs //*************************************************************** // デフォルト設定のプレイヤー public class DefaultPlayer : Player { } //*************************************************************** // IPlayer.cs //*************************************************************** // プレイヤーに対する操作一覧 public interface IPlayer { // 足が地面についているかどうかを取得 public bool isGrounded (); // どちらを見ているかを取得 public To direction (); // to方向 に deltaTime秒間 移動する public void move (To to, float deltaTime); // ジャンプ public void jump (); // to方向 に 体を向ける public void turn (To to); // 上方を見る deltaTime秒間 public void lookU (float deltaTime); // 左方を見る deltaTime秒間 public void lookL (float deltaTime); // 下方を見る deltaTime秒間 public void lookD (float deltaTime); // 右方を見る deltaTime秒間 public void lookR (float deltaTime); } public enum To { // Forward F, // Left L, // Right R, // Back B } //*************************************************************** // Input.cs //*************************************************************** public interface Input { // key for input public KeyControl key (); // start of pushing public void onStart (IPlayer player); // pushing public void onMiddle (IPlayer player, float deltaTime); // end of pushing public void onEnd (IPlayer player); } //*************************************************************** // Player.cs //*************************************************************** // abstract Player public abstract partial class Player : MonoBehaviour { // CACHE CharacterController controller; Input[] inputs; float ySpeed = 0; Camera mainCamera; Transform fAnchorTransform; Animator animator; // ToDo: - enable to access animator // SETTING virtual public float moveSpeed () { return 5.0f; } virtual public float jumpHeight () { return 1.0f; } virtual public float gravity () { return 9.8f; } virtual public float lookSpeed () { return 50.0f; } virtual public float height () { return 1.0f; } // keys to input virtual public Input[] keyInputs () { return new Input[] { new DefaultW (), new DefaultA (), new DefaultS (), new DefaultD (), new DefaultSpace (), new DefaultI (), new DefaultL (), new DefaultJ (), new DefaultK () }; } // anchor object for definition the forward derection virtual public GameObject forwardAnchor () { return Camera.main.gameObject; } /// MonoBehaviour Start() void Start () { this.controller = this.gameObject.AddComponent<CharacterController> (); this.controller.center = new Vector3 (0, this.height () / 2, 0); this.controller.height = this.height (); this.inputs = this.keyInputs (); this.fAnchorTransform = this.forwardAnchor ().transform; this.mainCamera = Camera.main; this.animator = this.GetComponent<Animator> (); } /// MonoBehaviour Update() void Update () { // refresh ySpeed if (this.isGrounded () && this.ySpeed < 0) { this.ySpeed = 0; } else { this.ySpeed -= this.gravity () * Time.deltaTime; } // use gravity var v3 = new Vector3 (0, this.ySpeed, 0); this.controller.Move (v3 * Time.deltaTime); // check input foreach (var input in this.inputs) { if (input.key ().wasPressedThisFrame) { input.onStart (this); } if (input.key ().isPressed) { input.onMiddle (this, Time.deltaTime); } if (input.key ().wasReleasedThisFrame) { input.onEnd (this); } } } } //*************************************************************** // Player+IPlayer.cs //*************************************************************** public abstract partial class Player : IPlayer { public bool isGrounded () { return this.controller.isGrounded; } public To direction () { var form = this.transform; var diffF = (-(this.fAnchorTransform.forward) - form.forward).sqrMagnitude; var diffL = (this.fAnchorTransform.right - form.forward).sqrMagnitude; var diffB = (this.fAnchorTransform.forward - form.forward).sqrMagnitude; var diffR = (-(this.fAnchorTransform.right) - form.forward).sqrMagnitude; var minDiff = diffF; var minTo = To.F; if (diffL < minDiff) { minDiff = diffL; minTo = To.L; } if (diffB < minDiff) { minDiff = diffB; minTo = To.B; } if (diffR < minDiff) { minDiff = diffR; minTo = To.R; } return minTo; } public void move (To to, float dTime) { var direction = Vector3.zero; switch (to) { case To.F: direction = this.fAnchorTransform.forward; break; case To.L: direction = -(this.fAnchorTransform.right); break; case To.B: direction = -(this.fAnchorTransform.forward); break; case To.R: direction = this.fAnchorTransform.right; break; } var v3 = direction * this.moveSpeed (); this.controller.Move (v3 * dTime); } public void jump () { if (this.isGrounded ()) { this.ySpeed += Mathf.Sqrt (this.jumpHeight () * 3.0f * this.gravity ()); } } public void turn (To to) { var direction = Vector3.zero; switch (to) { case To.F: direction = this.fAnchorTransform.forward; break; case To.L: direction = -(this.fAnchorTransform.right); break; case To.B: direction = -(this.fAnchorTransform.forward); break; case To.R: direction = this.fAnchorTransform.right; break; } var v3 = new Vector3 (direction.x, 0, direction.z); this.mainCamera.transform.parent = null; this.transform.forward = -(v3); this.mainCamera.transform.parent = this.transform; } public void lookL (float deltaTime) { this.mainCamera.transform.RotateAround (this.transform.position, -(Vector3.up), this.lookSpeed () * deltaTime); } public void lookR (float deltaTime) { this.mainCamera.transform.RotateAround (this.transform.position, Vector3.up, this.lookSpeed () * deltaTime); } public void lookU (float deltaTime) { this.mainCamera.transform.RotateAround (this.transform.position, -(this.fAnchorTransform.right), this.lookSpeed () * deltaTime); } public void lookD (float deltaTime) { this.mainCamera.transform.RotateAround (this.transform.position, this.fAnchorTransform.right, this.lookSpeed () * deltaTime); } } //*************************************************************** // DefaultW.cs //*************************************************************** // W (default) public class DefaultW : Input { public KeyControl key () { return current.wKey; } public void onStart (IPlayer player) { player.turn (To.F); } public void onMiddle (IPlayer player, float deltaTime) { player.move (To.F, deltaTime); } public void onEnd (IPlayer player) { } } //*************************************************************** // DefaultA.cs //*************************************************************** // A (default) public class DefaultA : Input { public KeyControl key () { return current.aKey; } public void onStart (IPlayer player) { player.turn (To.L); } public void onMiddle (IPlayer player, float deltaTime) { player.move (To.L, deltaTime); } public void onEnd (IPlayer player) { } } //*************************************************************** // DefaultS.cs //*************************************************************** // S (default) public class DefaultS : Input { public KeyControl key () { return current.sKey; } public void onStart (IPlayer player) { player.turn (To.B); } public void onMiddle (IPlayer player, float deltaTime) { player.move (To.B, deltaTime); } public void onEnd (IPlayer player) { } } //*************************************************************** // DefaultD.cs //*************************************************************** // D (default) public class DefaultD : Input { public KeyControl key () { return current.dKey; } public void onStart (IPlayer player) { player.turn (To.R); } public void onMiddle (IPlayer player, float deltaTime) { player.move (To.R, deltaTime); } public void onEnd (IPlayer player) { } } //*************************************************************** // DefaultSpace.cs //*************************************************************** // Space (default) public class DefaultSpace : Input { public KeyControl key () { return current.spaceKey; } public void onStart (IPlayer player) { player.jump (); } public void onMiddle (IPlayer player, float deltaTime) { } public void onEnd (IPlayer player) { } } //*************************************************************** // DefaultI.cs //*************************************************************** // I (default) public class DefaultI : Input { public KeyControl key () { return current.iKey; } public void onStart (IPlayer player) { } public void onMiddle (IPlayer player, float deltaTime) { player.lookU (deltaTime); } public void onEnd (IPlayer player) { } } //*************************************************************** // DefaultK.cs //*************************************************************** // K (default) public class DefaultK : Input { public KeyControl key () { return current.kKey; } public void onStart (IPlayer player) { } public void onMiddle (IPlayer player, float deltaTime) { player.lookD (deltaTime); } public void onEnd (IPlayer player) { } } //*************************************************************** // DefaultL.cs //*************************************************************** // L (default) public class DefaultL : Input { public KeyControl key () { return current.lKey; } public void onStart (IPlayer player) { } public void onMiddle (IPlayer player, float deltaTime) { player.lookR (deltaTime); } public void onEnd (IPlayer player) { } } //*************************************************************** // DefaultJ.cs //*************************************************************** // J (default) public class DefaultJ : Input { public KeyControl key () { return current.jKey; } public void onStart (IPlayer player) { } public void onMiddle (IPlayer player, float deltaTime) { player.lookL (deltaTime); } public void onEnd (IPlayer player) { } }以上です。
最後まで読んでくれてありがとう!
- 投稿日:2021-01-10T13:41:49+09:00
棄却サンプリング
棄却サンプリングは実装も簡単だし使い勝手も悪くないし、もっと流行ってもいいと思うの(そもそも最近はサンプラーなんて実装しないような気もするが・・・)
棄却サンプリングのアルゴリズム
アルゴリズム
必要な材料は以下の通り。
- $f(x)$:サンプリングをしたい確率密度関数。
- $g(x)$:提案分布。こちらからサンプリングした結果を使うので、簡単にサンプリングできる関数である必要がある。
- $M$: 定数。ただし両確率密度関数の定義域全体で $f(x)\le Mg(x)$ を満たす。以下のようにサンプリングを行う。
- $x \sim g(x)$
- $u \sim U(0, 1)$
- $r = f(x) / Mg(x) $
- $u \le r$ ならサンプル $x$ を受領。そうでないなら棄却。
ここで $U(0, 1)$ は $[0, 1]$ の一様分布。
なぜワークするか
アルゴリズムでサンプリングされる対象について、特性関数を評価すればよい。
\begin{align} \int dx e^{itx} \int_0^1 du \theta\left(\frac{f(x)}{Mg(x)} - u)\right) g(x) &= \int dx e^{itx} \int_0^{\frac{f(x)}{Mg(x)}} du g(x)\\ &= \int dx e^{itx} \frac{f(x)}{Mg(x)} g(x)\\ &= \int dx e^{itx} \frac{f(x)}{M}\\ &\propto \int dx e^{itx} f(x)=E_X^F\left[e^{itx}\right] \end{align}この通り、サンプリングする対象について特性関数を評価すれば $f(x)$ の特性関数に一致する。
実装
コード
public IEnumerable<TargetDistributionDataType> GetSamples(RejectionSamplerConfig<TargetDistributionDataType, TargetDistributionParameter, ProposalDistributionParameter> samplerConfig) { return Enumerable.Range(0, samplerConfig.Count).Select(i => { var uniformParam = new Probability.Parameter.Uniform(0, 1); var uniform = new Probability.Distribution.Uniform(); var targetProbabilityDensityFunction = samplerConfig.TargetDistribution.GetProbabilityDensityFunction(samplerConfig.TargetDistParameter); var proposalProbabilityDensityFunction = samplerConfig.ProposalDistribution.GetProbabilityDensityFunction(samplerConfig.ProposalDistParameter); while (true) { var sampleFromProposal = samplerConfig.ProposalDistribution.GetSamples(samplerConfig.ProposalDistParameter, 1).First(); var acceptProbability = targetProbabilityDensityFunction(sampleFromProposal) / (proposalProbabilityDensityFunction(sampleFromProposal) * samplerConfig.M); var sampleFromUniform = uniform.GetSamples(uniformParam, 1).First(); if (sampleFromUniform < acceptProbability) return sampleFromProposal; } }); }数値実験
以下では平均値2の指数分布のサンプリングを、提案分布を平均値4の指数分布、 $M=2$ とした棄却サンプリングで行っている。乱数によるが、手元で回した結果は平均値2.0049、標準偏差1.9695が得られた。
static void Main(string[] args) { Console.WriteLine("Hello World!"); var size = 10000; var rejectionSamplerConfig = new Probability.SamplerConfig.RejectionSamplerConfig <double, Probability.Parameter.Exponential, Probability.Parameter.Exponential>(new Probability.Distribution.Exponential(), new Probability.Parameter.Exponential(1), new Probability.Distribution.Exponential(), new Probability.Parameter.Exponential(2), size, 2); var sampler = new Probability.Sampler.RejectionSampler<double, Probability.Parameter.Exponential, Probability.Parameter.Exponential>(); var sampleFromSampler = sampler.GetSamples(rejectionSamplerConfig); Console.WriteLine(sampleFromSampler.Average()); Console.WriteLine(sampleFromSampler.StadardDeviation()); }参考文献
- 投稿日:2021-01-10T11:28:18+09:00
.NET Standard 2.0 でSystem.Numerics.BitOperationsを使う
.NET Core 3.0から、
System.Numerics.BitOperations
クラスが追加されています。
このクラスには、下記のようなビット操作を行うメソッドがあります。
LeadingZeroCount
Log2
PopCount
RotateLeft
TrailingZeroCount
これらのメソッドはIntrinsicsを使って実装している(依存している)ため、.NET Frameworkなどの環境で使用できるバックポートNugetパッケージがありません。
しかし、これらのメソッドにはIntrinsicsが使えない環境用にフォールバック実装があります。
従って、常にフォールバック実装を使用すれば、.NET Standard 2.0でも使用できるようになります。
というわけで、MITライセンスのBitOperations.csをコピペ&細工しましょう。(ライセンスに従って取り扱う必要はがあります。)
#if
ディレクティブで無効にするだけです。したものがこちらです。
一応Nugetパッケージも用意してみましたが、使用するアセンブリに組み込んでしまったほうがいいでしょう。
なお、Unityで動作するかはわかりません。
- 投稿日:2021-01-10T10:51:23+09:00
【C#】WPFの自作タイトルバーを簡単にちゃんと実装したい
問題
WPFでアプリケーションを作ることがありました
その際背景透過をする必要があり
WindowsStyleをNoneにする必要がありました
そのためタイトルバーを作る必要がありました
タイトルバーを作ってcavanにPreviewMouseLeftButtonDownの時にDragMoveを呼び出すだけで移動することはできます
しかし、そうする画面の上に移動して最大化した際にタスクバーが消えてしまいます
またタイトルバーをつかんで移動できなくなります解決法
調べてみたこと
解決法をいろいろネットで調べてみました
解決策としては以下のものが見つかりました
- ResizeModeをNoResizeに変える
- Win32APIでモニタ情報を取得する
一つ目の方法は機能が一部使えなくなりますのでよい方法ではありません
二つ目の方法はWin32APIを使うため簡単なソフトウェアを作る際に向いていませんし
IntPtrを使うのであまり好ましいとは言えません本題
このようにネットに乗っている情報ではあまりいい方法に出会えなかったので
自分で考えた方法をここに記します注意点
System.Windows.FormsとSystem.Drawingを参照へ追加する必要があります
コード
<Window x:Class="Qiita_WPF.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Qiita_WPF" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" SizeChanged="wSizeChanged" WindowStyle="None"> <WindowChrome.WindowChrome> <WindowChrome CaptionHeight="0"/> </WindowChrome.WindowChrome> <Grid Margin="0,0,0,0"> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition Width="135"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="30"/> <RowDefinition/> </Grid.RowDefinitions> <Canvas x:Name="wMove" Tag="dc" Background="#00000000" Height="30" VerticalAlignment="Top"/> <Button x:Name="wMin" Tag="dc" Content="—" Background="{x:Null}" BorderThickness="0" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="42" Height="30" Margin="0,0,0,0"/> <Button x:Name="wSzcg" Tag="dc" Content="☐" Background="{x:Null}" BorderThickness="0" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="45" Height="30" Margin="45,0,0,0"/> <Button x:Name="wCls" Tag="dc" Content="✕" Background="{x:Null}" BorderThickness="0" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="45" Height="30" Margin="90,0,0,0"/> </Grid> </Window>C#コード
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace Qiita_WPF { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { bool max = false; double bTop; double bLeft; double bWidth; double bHeight; bool isMax=false; public MainWindow() { InitializeComponent(); wMove.MouseLeftButtonDown += ThisDragMove; wCls.Click += (o, e) => this.Close(); wSzcg.Click += WindowSizeChange; wMin.Click += (o, e) => WindowState = WindowState.Minimized; WindowState = WindowState.Normal; this.SizeChanged += wSizeChanged; } void ThisDragMove(object o, MouseButtonEventArgs e) { if (isMax) { int x = System.Windows.Forms.Cursor.Position.X; int y = System.Windows.Forms.Cursor.Position.Y; double xm = e.GetPosition((Canvas)o).X; double ym = e.GetPosition((Canvas)o).Y; sizeChenge(); double w = this.Width; this.Left = x - (w / SystemParameters.WorkArea.Width) * xm; this.Top = y - ym; max = false; } DragMove(); if (this.WindowState == WindowState.Maximized) { WindowState = WindowState.Normal; sizeChenge(); max = true; } } private void wSizeChanged(object sender, SizeChangedEventArgs e) { wMove.Width = this.Width - 135; } private void WindowSizeChange(object sender, RoutedEventArgs e) { sizeChenge(); } void sizeChenge() { if (!isMax) { bTop = Top; bLeft = Left; bWidth = Width; bHeight = Height; Top = 0; Left = 0; Width = SystemParameters.WorkArea.Width; Height = SystemParameters.WorkArea.Height; isMax = true; } else { Top = bTop; Left = bLeft; Width = bWidth; Height = bHeight; isMax = false; } } } }解説
通常ならDragMoveをPreviewMouseLeftButtonDownのイベントが呼び出されたときに呼び出すだけで問題ありません
しかし、これだと先ほども言いましたが
画面の上に移動して最大化した際にタスクバーが消えてしまったり
また最大化した後にタイトルバーをつかんで移動できなくなります
そのためDragMoveをラップします
ラップすることでもし最大化された場合はWindowState.Maximizedになりますので
WindowState.MaximizedになったらsizeChengeで最大化しなおします
なので一瞬小さくなりますがすぐに最大化されます
逆に最大化された状態からドラッグで小さくする処理は
sizeChengeを呼び出し小さくします
その際に元のマウスの位置を覚えておきサイズの変化の割合をとってそこからWindowの位置を変更します欠点
- System.Windows.FormsとSystem.Drawingを参照へ追加する必要がある
- ドラッグでの最大化の際一瞬小さくなる
まとめ
このように実装することでWin32APIを使わずに比較的理解しやすく実装することができます
タスクバーを消す必要がある場合はぜひ使ってみてください