0

I wrote a program to visualize the state of the keyboard. When I resize the window, the canvas in which I drew the rectangles keeps its size, but I want it to be resized to the window's size. I tried adding a SizeChanged event handler for both the window and the canvas, but that didn't work.

I also tried to set the size of the Lights canvas in the window's SizeChanged event, but then the window filled almost the whole screen at startup.

So how can I reach the following goals?

  • Initially, the client area of the window is 256 × 256.
  • The window is resizable.
  • When the window is resized, its content scales with it.

MainWindow.xaml

<Window x:Class="KeyMonitor.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Key Monitor" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen" ResizeMode="CanResize">
    <Grid>
        <Canvas x:Name="Lights" Width="256" Height="256" />
    </Grid>
</Window>

MainWindow.xaml.cs

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace KeyMonitor
{
    public partial class MainWindow : Window
    {
        private Rectangle[] indicators = new Rectangle[256];

        public MainWindow()
        {
            InitializeComponent();

            for (int i = 0; i < 256; i++)
                Lights.Children.Add(indicators[i] = new Rectangle { Fill = Brushes.Transparent });

            Lights.SizeChanged += Lights_SizeChanged;

            var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
            timer.Tick += Timer_Tick;
            timer.Start();
        }

        private void Lights_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            //Lights.Width = e.NewSize.Width;
            //Lights.Height = e.NewSize.Height;
            for (int y = 0; y < 16; y++)
            {
                var sy = (int)(e.NewSize.Height * y / 16);
                var sy1 = (int)(e.NewSize.Height * (y + 1) / 16);

                for (int x = 0; x < 16; x++)
                {
                    var sx = (int)(e.NewSize.Width * x / 16);
                    var sx1 = (int)(e.NewSize.Width * (x + 1) / 16);

                    var indicator = indicators[16 * y + x];
                    indicator.Width = 1 + sx1 - sx;
                    indicator.Height = 1 + sy1 - sy;
                    Canvas.SetLeft(indicator, sx);
                    Canvas.SetTop(indicator, sy);
                }
            }
        }

        void Timer_Tick(object sender, EventArgs e)
        {
            var keys = new byte[256];
            if (User32.GetKeyboardState(keys) != 0)
            {
                for (int i = 0; i < 256; i++)
                {
                    var r = (byte)((keys[i] & 0x7E) != 0 ? 0xFF : 0x00);
                    var g = (byte)((keys[i] & 0x01) != 0 ? 0xFF : 0x00);
                    var b = (byte)((keys[i] & 0x80) != 0 ? 0xFF : 0x00);
                    indicators[i].Fill = new SolidColorBrush(Color.FromRgb(r, g, b));
                }
            }
        }
    }

    static class User32
    {
        [DllImport("user32")]
        public static extern int GetKeyboardState(byte[] lpKeyState);
    }
}
Roland Illig
  • 40,703
  • 10
  • 88
  • 121

2 Answers2

1

I would set the size of the Window to initial 256*256 and let the Canvas stretch in it:

<Window x:Class="KeyMonitor.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Key Monitor" Width="256" Height="256" 
        WindowStartupLocation="CenterScreen" >
    <!-- Grid is not necessary-->
    <Canvas x:Name="Lights" />
</Window>

The defaults for HorizointalAlignment and VerticalAlignment are Stretch so no additional property must be set.

The default resize mode is already CanResize so it is not necessary too. Also I removed the SizeToContent value because the default value is Manual and this is what you need here.

EDIT: I discovered a little problem with the initial size. Setting Width and Height in xaml means the size including the border which Windows adds. So if Height=Width the content size is not a square.
I found a solution on SO: How to set WPF window's startup ClientSize?. It is not the best to use always the code behind but I think it's a reasonable workaround. And it's only for the initial size.

Community
  • 1
  • 1
Koopakiller
  • 2,838
  • 3
  • 32
  • 47
  • This is almost exactly what I wanted. The only thing missing is the initial size of the _client area_ of the window. Curiously, In the Visual Studio designer, the window including title bar and border is 256 × 256, but when I run the application, the window is only 242 × 249. – Roland Illig Aug 11 '16 at 21:55
  • @RolandIllig You're right, I was able to reproduce this problem. I took a little research and found a working solution on another SO thread. I linked to it in my answer. – Koopakiller Aug 11 '16 at 21:59
  • @RolandIllig you can set the content size _and_ use the `SizeToContent` property. – heltonbiker Aug 11 '16 at 21:59
  • @heltonbiker This won't work. If you set the size of the Canvas or the size of a parent of it, the Canvas won't resize to the window if it changes it size. – Koopakiller Aug 11 '16 at 22:04
1

To scale the content, put the canvas inside a viewbox. This will scale the content appropriately.

    <ViewBox>
<Canvas x:Name="Lights" />
</ViewBox>
jle
  • 9,316
  • 5
  • 48
  • 67
  • This nicely solves half of my problem, namely resizing the canvas. But it doesn't solve the initial window size. The initial window fills approximately half the screen and not a 256 × 256 area. – Roland Illig Aug 11 '16 at 21:50