none
ViewBoxを横方向にのみ拡大・縮小させる方法 RRS feed

  • 質問

  • 等倍でウィンドウサイズを変更するように強制する。
    http://qiita.com/yu_ka1984/items/b4a3ce9ed7750bd67b86

    上記サイトとViewBoxを組み合わせて、縦横比率を保って
    画面を縮小・拡大できるアプリ画面を作成しています。

    しかし、ユーザーの要望から
    縦にスクロールできるScrollViewerのある画面では
    ・横方向にサイズ変更した場合には、現在画面の比率で縮小・拡大
    ・縦方向にサイズ変更した場合には、縮小拡大せずに中のコンテンツを可能な限り表示
     (画面を縦方向に拡大した場合は拡大した分、ScrollViewerと中の内容がが拡大され、
       内容が全て表示されれば、ScrollBarが消えるようなイメージ)

    という制御をおこなわなければいけなくなりました。

    ViewBoXだけでは対応できないかと思われるのですが
    上記の要望をWPFで対応できるのでしょうか
    2017年1月26日 5:54

回答

  • 何か勘違いしていたらすみません。

    上部のグレーの領域の高さを固定するには、対応する

    <RowDefinition Height=".5*" />

    の部分を

    <RowDefinition Height="100" />

    とすると高さが100ピクセルに固定されると思います。

    ウィンドウを横方向にサイズ変更した場合に、上部のグレーの領域をサイズ変更前の比率を保って拡縮するのであれば、コードで値を変更する必要がある(?)のかなと思います。

    <Window x:Class="WpfApplication1.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:WpfApplication1"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">
        <Grid >
            <Grid.RowDefinitions>
                <RowDefinition x:Name="topBarDef" Height="100" />
                <RowDefinition Height="5" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
    
            <Grid Grid.Row="0" Background="DimGray">
                <StackPanel Orientation="Horizontal" >
                    <Button Width="100" Height="30" Margin="5" Content="新規"/>
                    <Button Width="100" Height="30" Margin="5" Content="更新"/>
                    <Button Width="100" Height="30" Margin="5" Content="終了" />
                </StackPanel>
            </Grid>
    
            <ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" Grid.Row="2" VerticalAlignment="Stretch" >
                <Viewbox Stretch="Uniform" VerticalAlignment="Top">
                    <Grid Width=" 200" Height="200">
                        <Grid.RowDefinitions >
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>
    
                        <Grid Background="Red" >
                            <Button Width="100" Height="20" Content="仮のボタン" HorizontalAlignment="Left" Margin="10"/>
                        </Grid>
                        <Grid Background="Orange" Grid.Row="1"/>
                        <Grid Background="yellow" Grid.Row="2"/>
                        <Grid Background="GreenYellow" Grid.Row="3"/>
                        <Grid Background="Green" Grid.Row="4"/>
                        <Grid Background="Blue" Grid.Row="5"/>
                    </Grid>
                </Viewbox>
            </ScrollViewer>
        </Grid>
    </Window>
    using System;
    using System.Runtime.InteropServices;
    using System.Windows;
    using System.Windows.Interop;
    
    namespace WpfApplication1
    {
        public partial class MainWindow : Window
        {
            private double fixedRate = 1.0;
            private double topBarRate = 1.0;
            public MainWindow()
            {
                InitializeComponent();
            }
    
            [StructLayout(LayoutKind.Sequential)]
            private struct RECT
            {
                public int left;
                public int top;
                public int right;
                public int bottom;
            }
    
            protected override void OnSourceInitialized(EventArgs e)
            {
                base.OnSourceInitialized(e);
                IntPtr handle = (new WindowInteropHelper(this)).Handle;
                HwndSource hwndSource = (HwndSource)HwndSource.FromVisual(this);
                hwndSource.AddHook(WndHookProc);
            }
    
            const int WM_SIZING = 0x214;
            const int WM_ENTERSIZEMOVE = 0x0231;
            const int WMSZ_LEFT = 1;
            const int WMSZ_RIGHT = 2;
    
            [DllImport("user32.dll", SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            private static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
    
            private IntPtr WndHookProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
            {
                if (msg == WM_SIZING)
                {
                    RECT rect = (RECT)Marshal.PtrToStructure(lParam, typeof(RECT));
                    int width = rect.right - rect.left;
                    int height = rect.bottom - rect.top;
                    double dheight = (width / fixedRate) - height;
                    switch (wParam.ToInt32())
                    {
                        case WMSZ_LEFT:
                        case WMSZ_RIGHT:
                            topBarDef.Height = new System.Windows.GridLength(topBarDef.ActualHeight + dheight * topBarRate);
                            rect.bottom += (int)dheight;
                            break;
                    }
                    Marshal.StructureToPtr(rect, lParam, false);
                }
                else if (msg == WM_ENTERSIZEMOVE)
                {
                    var rect = new RECT();
                    GetWindowRect(hwnd, ref rect);
                    fixedRate = (double)(rect.right - rect.left) / (double)(rect.bottom - rect.top); // アスペクト比を保持
                    topBarRate = topBarDef.ActualHeight / (double)(rect.bottom - rect.top); // ウィンドウ高さに対するトップバーの比率を保持
                }
                return IntPtr.Zero;
            }
        }
    }
    • 編集済み kenjinoteMVP 2017年1月27日 2:03
    • 回答としてマーク XAMLVB 2017年1月27日 6:09
    2017年1月27日 2:02

すべての返信

  • 下記のようなXAMLで目的の動作になりますでしょうか?

    <Window x:Class="WpfApplication1.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:WpfApplication1"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">
        <ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
            <Viewbox Stretch="Uniform">
                <Image Source="image.png" />
            </Viewbox>
        </ScrollViewer>
    </Window>
    using System;
    using System.Runtime.InteropServices;
    using System.Windows;
    using System.Windows.Interop;
    
    namespace WpfApplication1
    {
        public partial class MainWindow : Window
        {
            private double fixedRate = 1.0;
    
            public MainWindow()
            {
                InitializeComponent();
            }
    
            [StructLayout(LayoutKind.Sequential)]
            private struct RECT
            {
                public int left;
                public int top;
                public int right;
                public int bottom;
            }
    
            protected override void OnSourceInitialized(EventArgs e)
            {
                base.OnSourceInitialized(e);
                IntPtr handle = (new WindowInteropHelper(this)).Handle;
                HwndSource hwndSource = (HwndSource)HwndSource.FromVisual(this);
                hwndSource.AddHook(WndHookProc);
            }
    
            const int WM_SIZING = 0x214;
            const int WM_ENTERSIZEMOVE = 0x0231;
            const int WMSZ_LEFT = 1;
            const int WMSZ_RIGHT = 2;
    
            [DllImport("user32.dll", SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            private static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
    
            private IntPtr WndHookProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
            {
                if (msg == WM_SIZING)
                {
                    RECT rect = (RECT)Marshal.PtrToStructure(lParam, typeof(RECT));
                    int width = rect.right - rect.left;
                    int height = rect.bottom - rect.top;
                    int dheight = (int)(width / fixedRate + 0.5) - height;
                    switch (wParam.ToInt32())
                    {
                        case WMSZ_LEFT:
                        case WMSZ_RIGHT:
                            rect.bottom += dheight;
                            break;
                    }
                    Marshal.StructureToPtr(rect, lParam, false);
                }
                else if(msg == WM_ENTERSIZEMOVE)
                {
                    // ウィンドウ幅のドラッグ開始時にアスペクト比を保持
                    var rect = new RECT();
                    GetWindowRect(hwnd, ref rect);
                    fixedRate =  (double)(rect.right - rect.left) / (double)(rect.bottom - rect.top);
                }
                return IntPtr.Zero;
            }
        }
    }
    「横方向にサイズ変更した場合には、現在画面の比率で縮小・拡大」を行うためコード追記しました。
    2017年1月26日 7:58
  • kenjinote 様

    ご回答ありがとうございます。

    早速、ソースを実行しました。XAMLがSCrollViewerだけなら、正に想定通りなのですが

    画面のXAMLは概要ですが下記のようになります。

    <Window x:Class="WpfApplication1.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:WpfApplication1"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">
        <Grid >
            <Grid.RowDefinitions>
                <RowDefinition Height=".5*" />
                <RowDefinition Height=".1*" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>

            <Grid Grid.Row="0" Background="DimGray">
                <StackPanel Orientation="Horizontal" >
                    <Button   Width="100" Height="30" Margin="5" Content="新規"/>
                    <Button   Width="100" Height="30" Margin="5" Content="更新"/>
                    <Button   Width="100" Height="30" Margin="5" Content="終了" />
                </StackPanel>
            </Grid>

            <ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" Grid.Row="2" VerticalAlignment="Stretch" >
                <Viewbox Stretch="Uniform">
                    <Grid Width=" 200" Height="200">
                        <Grid.RowDefinitions >
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>

                        <Grid Background="Red" >
                            <Button Width="100" Height="20" Content="仮のボタン" HorizontalAlignment="Left" Margin="10"/>
                        </Grid>    
                        <Grid Background="Orange"  Grid.Row="1"/>
                        <Grid Background="yellow"  Grid.Row="2"/>
                        <Grid Background="GreenYellow"    Grid.Row="3"/>
                        <Grid Background="Green"   Grid.Row="4"/>
                        <Grid Background="Blue" Grid.Row="5"/>
                    </Grid>
                </Viewbox>
            </ScrollViewer>
        </Grid>
    </Window>

    上記のような画面構成でSceollViewerだけが縦にひっぱった際に広がればと考えています

    Kenjinote様のソースを元にすれば、何とかできそうなのですが、もし良い方法があればご提示頂ければ

    ありがたいです

    2017年1月27日 0:41
  • 何か勘違いしていたらすみません。

    上部のグレーの領域の高さを固定するには、対応する

    <RowDefinition Height=".5*" />

    の部分を

    <RowDefinition Height="100" />

    とすると高さが100ピクセルに固定されると思います。

    ウィンドウを横方向にサイズ変更した場合に、上部のグレーの領域をサイズ変更前の比率を保って拡縮するのであれば、コードで値を変更する必要がある(?)のかなと思います。

    <Window x:Class="WpfApplication1.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:WpfApplication1"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">
        <Grid >
            <Grid.RowDefinitions>
                <RowDefinition x:Name="topBarDef" Height="100" />
                <RowDefinition Height="5" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
    
            <Grid Grid.Row="0" Background="DimGray">
                <StackPanel Orientation="Horizontal" >
                    <Button Width="100" Height="30" Margin="5" Content="新規"/>
                    <Button Width="100" Height="30" Margin="5" Content="更新"/>
                    <Button Width="100" Height="30" Margin="5" Content="終了" />
                </StackPanel>
            </Grid>
    
            <ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" Grid.Row="2" VerticalAlignment="Stretch" >
                <Viewbox Stretch="Uniform" VerticalAlignment="Top">
                    <Grid Width=" 200" Height="200">
                        <Grid.RowDefinitions >
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>
    
                        <Grid Background="Red" >
                            <Button Width="100" Height="20" Content="仮のボタン" HorizontalAlignment="Left" Margin="10"/>
                        </Grid>
                        <Grid Background="Orange" Grid.Row="1"/>
                        <Grid Background="yellow" Grid.Row="2"/>
                        <Grid Background="GreenYellow" Grid.Row="3"/>
                        <Grid Background="Green" Grid.Row="4"/>
                        <Grid Background="Blue" Grid.Row="5"/>
                    </Grid>
                </Viewbox>
            </ScrollViewer>
        </Grid>
    </Window>
    using System;
    using System.Runtime.InteropServices;
    using System.Windows;
    using System.Windows.Interop;
    
    namespace WpfApplication1
    {
        public partial class MainWindow : Window
        {
            private double fixedRate = 1.0;
            private double topBarRate = 1.0;
            public MainWindow()
            {
                InitializeComponent();
            }
    
            [StructLayout(LayoutKind.Sequential)]
            private struct RECT
            {
                public int left;
                public int top;
                public int right;
                public int bottom;
            }
    
            protected override void OnSourceInitialized(EventArgs e)
            {
                base.OnSourceInitialized(e);
                IntPtr handle = (new WindowInteropHelper(this)).Handle;
                HwndSource hwndSource = (HwndSource)HwndSource.FromVisual(this);
                hwndSource.AddHook(WndHookProc);
            }
    
            const int WM_SIZING = 0x214;
            const int WM_ENTERSIZEMOVE = 0x0231;
            const int WMSZ_LEFT = 1;
            const int WMSZ_RIGHT = 2;
    
            [DllImport("user32.dll", SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            private static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
    
            private IntPtr WndHookProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
            {
                if (msg == WM_SIZING)
                {
                    RECT rect = (RECT)Marshal.PtrToStructure(lParam, typeof(RECT));
                    int width = rect.right - rect.left;
                    int height = rect.bottom - rect.top;
                    double dheight = (width / fixedRate) - height;
                    switch (wParam.ToInt32())
                    {
                        case WMSZ_LEFT:
                        case WMSZ_RIGHT:
                            topBarDef.Height = new System.Windows.GridLength(topBarDef.ActualHeight + dheight * topBarRate);
                            rect.bottom += (int)dheight;
                            break;
                    }
                    Marshal.StructureToPtr(rect, lParam, false);
                }
                else if (msg == WM_ENTERSIZEMOVE)
                {
                    var rect = new RECT();
                    GetWindowRect(hwnd, ref rect);
                    fixedRate = (double)(rect.right - rect.left) / (double)(rect.bottom - rect.top); // アスペクト比を保持
                    topBarRate = topBarDef.ActualHeight / (double)(rect.bottom - rect.top); // ウィンドウ高さに対するトップバーの比率を保持
                }
                return IntPtr.Zero;
            }
        }
    }
    • 編集済み kenjinoteMVP 2017年1月27日 2:03
    • 回答としてマーク XAMLVB 2017年1月27日 6:09
    2017年1月27日 2:02
  • Kenjinote様

    頂いたサンプルを元に対応できそうです。

    ありがとうございます

    2017年1月27日 6:10