LoginSignup
3
2

More than 1 year has passed since last update.

【C#】WPFの自作タイトルバーを簡単にちゃんと実装したい

Last updated at Posted at 2021-01-10

問題

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)
            {
                System.Windows.Forms.Screen s1=null;
                foreach (System.Windows.Forms.Screen s in System.Windows.Forms.Screen.AllScreens)
                {

                    if(Top>=s.WorkingArea.Top&& Top <= s.WorkingArea.Top + s.WorkingArea.Height && Left >= s.WorkingArea.Left && Left <= s.WorkingArea.Left + s.WorkingArea.Width)
                    {
                        s1 = s;
                        break;
                    }

                }
                if (s1 == null)
                {
                    return;
                }

                bTop = Top;
                bLeft = Left;
                bWidth = Width;
                bHeight = Height;
                Top = s1.WorkingArea.Top;
                Left = s1.WorkingArea.Left;

                Width = s1.WorkingArea.Width;
                Height = s1.WorkingArea.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を使わずに比較的理解しやすく実装することができます
タスクバーを消す必要がある場合はぜひ使ってみてください

追記

  • 全画面表示にする際メインWindowでのみ有効だった問題を解決しました
3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2