Windows向けスクリーンキャプチャアプリケーションを作った。今回はスクリプトとともに技術的なことをメモ。

画像のように、デスクトップ上の録画領域の位置を調整する際に、半透明でなにもコントロールのないウィンドウを表示することで、録画領域がどこかわかりやすくなるようにしている。この半透明領域はウィンドウだが、領域内でクリックをしてもスルーして、下のウィンドウにマウスイベントをパスするようになっている。この技術はC#で見つけることができた。
http://stackoverflow.com/questions/2842667/how-to-create-a-semi-transparent-window-in-wpf-that-allows-mouse-events-to-passC#でクラスを作成してdll化し、それをIronPythonから利用している。
using System;
using System.Windows.Interop;
using System.Runtime.InteropServices;
namespace WindowServices
{
public static class WindowsServices
{
const int WS_EX_TRANSPARENT = 0x00000020;
const int GWL_EXSTYLE = (-20);
[DllImport("user32.dll")]
static extern int GetWindowLong(IntPtr hwnd, int index);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);
public static void SetWindowExTransparent(IntPtr hwnd)
{
var extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_TRANSPARENT);
}
}
}
from System.Windows.Interop import *
from WindowsServices import WindowsServices
hwnd = WindowInteropHelper(window).Handle
WindowsServices.SetWindowExTransparent(hwnd)
Window1.py
import wpf, clr
clr.AddReference('System.Drawing')
clr.AddReference('Microsoft.Expression.Encoder')
clr.AddReference('WindowServices')
import System
from System.Drawing import *
from System.IO import *
from System.Threading import *
from System.Windows import *
from Microsoft.Expression.Encoder.ScreenCapture import *
from Microsoft.Expression.Encoder.Devices import *
from Microsoft.Expression.Encoder.Profiles import *
from System.Windows.Interop import *
from System.Runtime.InteropServices import *
from WindowServices import WindowsServices
class Window1 (Window):
def __init__(self):
wpf.LoadComponent(self, 'Window1.xaml')
self.textBoxX = self.FindName('textBoxX')
self.textBoxX.TextChanged += self.AdjustTransparentWindow
self.textBoxY = self.FindName('textBoxY')
self.textBoxY.TextChanged += self.AdjustTransparentWindow
self.textBoxWidth = self.FindName('textBoxWidth')
self.textBoxWidth.TextChanged += self.AdjustTransparentWindow
self.textBoxHeight = self.FindName('textBoxHeight')
self.textBoxHeight.TextChanged += self.AdjustTransparentWindow
self.recButton = self.FindName('RecButton')
self.stopButton = self.FindName('StopButton')
self.StateChanged += self.StateChange
self.job = ScreenCaptureJob()
self.Show()
self.tWindow = Window()
wpf.LoadComponent(self.tWindow, 'TransparentWindow.xaml')
self.tWindow.Show()
self.AdjustTransparentWindow()
hwnd = WindowInteropHelper(self.tWindow).Handle
WindowsServices.SetWindowExTransparent(hwnd)
def StateChange(self, target, e):
if self.WindowState == WindowState.Minimized:
self.tWindow.WindowState = WindowState.Minimized
elif self.WindowState == WindowState.Normal:
self.tWindow.WindowState = WindowState.Normal
def AdjustTransparentWindow(self, *args):
try:
x0 = int(self.textBoxX.Text)
except ValueError:
return
try:
y0 = int(self.textBoxY.Text)
except ValueError:
return
try:
width = int(self.textBoxWidth.Text)
except ValueError:
return
try:
height = int(self.textBoxHeight.Text)
except ValueError:
return
if (x0 < 0) or (y0 < 0) or (width < 0) or (height < 0):
return
self.tWindow.Left = x0
self.tWindow.Top = y0
self.tWindow.Width = width
self.tWindow.Height = height
self.tWindow.WindowState = System.Windows.WindowState.Normal
def OnClose(self, sender, e):
if (self.job.Status == RecordStatus.Running):
result = System.Windows.MessageBox.Show("Capturing in Progress. Are You Sure You Want To Quit?", "Capturing", MessageBoxButton.YesNo)
if (result == MessageBoxResult.No):
e.Cancel = True
return
self.job.Stop()
self.job.Dispose()
Application.Current.Shutdown()
def RecButton_Checked(self, sender, e):
try:
x0 = int(self.textBoxX.Text)
except ValueError:
return
try:
y0 = int(self.textBoxY.Text)
except ValueError:
return
try:
width = int(self.textBoxWidth.Text)
except ValueError:
return
try:
height = int(self.textBoxHeight.Text)
except ValueError:
return
width = width + (4 - width % 4)
height = height + (4 - height % 4)
if (x0 < 0) or (y0 < 0) or (width < 0) or (height < 0):
return
capRect = Rectangle(x0, y0, width, height)
audioDevices = EncoderDevices.FindDevices(EncoderDeviceType.Audio)
self.job.AddAudioDeviceSource(audioDevices[len(audioDevices)-1])
self.tWindow.Opacity = 0.0
self.job.ScreenCaptureVideoProfile.Bitrate = ConstantBitrate(300 * 10 ** 3)
self.job.CaptureRectangle = capRect
self.job.OutputPath = Directory.GetCurrentDirectory()
self.recButton.Width = 0
self.stopButton.Width = 112
self.job.Start()
def RecButton_UnChecked(self, sender, e):
self.job.Stop()
self.recButton.Width = 112
self.stopButton.Width = 0
self.tWindow.Opacity = 0.7
def IncreaseX(self, sender, e):
try:
x0 = int(self.textBoxX.Text)
except ValueError:
return
x0 += 1
self.textBoxX.Text = x0.ToString()
self.tWindow.Left = x0
def DecreaseX(self, sender, e):
try:
x0 = int(self.textBoxX.Text)
except ValueError:
return
x0 -= 1
self.textBoxX.Text = x0.ToString()
self.tWindow.Left = x0
def IncreaseY(self, sender, e):
try:
y0 = int(self.textBoxY.Text)
except ValueError:
return
y0 += 1
self.textBoxY.Text = y0.ToString()
self.tWindow.Top = y0
def DecreaseY(self, sender, e):
try:
y0 = int(self.textBoxY.Text)
except ValueError:
return
y0 -= 1
self.textBoxY.Text = y0.ToString()
self.tWindow.Top = y0
def IncreaseWidth(self, sender, e):
try:
width = int(self.textBoxWidth.Text)
except ValueError:
return
width += 1
self.textBoxWidth.Text = width.ToString()
self.tWindow.Width = width
def DecreaseWidth(self, sender, e):
try:
width = int(self.textBoxWidth.Text)
except ValueError:
return
width -= 1
self.textBoxWidth.Text = width.ToString()
self.tWindow.Width = width
def IncreaseHeight(self, sender, e):
try:
height = int(self.textBoxHeight.Text)
except ValueError:
return
height += 1
self.textBoxHeight.Text = height.ToString()
self.tWindow.Height = height
def DecreaseHeight(self, sender, e):
try:
height = int(self.textBoxHeight.Text)
except ValueError:
return
height -= 1
self.textBoxHeight.Text = height.ToString()
self.tWindow.Height = height
Window1.xaml
<?xml version="1.0" encoding="utf-8"?>
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Screen Capture"
Closing="OnClose"
mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Height="200"
Width="150"
WindowStartupLocation="Manual"
Top="150"
Left="700"
ResizeMode="CanMinimize"
HorizontalContentAlignment="Left">
<WrapPanel HorizontalAlignment="Center">
<Button
Name="RecButton"
Content="Rec Start"
Click="RecButton_Checked"
Height="30"
VerticalAlignment="Top"
Width="140" />
<Button
Name="StopButton"
Content="Stop"
Click="RecButton_UnChecked"
Height="30"
VerticalAlignment="Top"
Width="0" />
<TextBlock
Height="23"
Name="textBlock1"
Text="X"
Width="50"
TextAlignment="Center" />
<Button
Content="-"
Width="13"
Height="23"
Click="DecreaseX" />
<TextBox
Height="22"
HorizontalAlignment="Left"
Name="textBoxX"
VerticalAlignment="Top"
Width="50"
TextAlignment="Right"
Text="100" />
<Button
Content="+"
Width="13"
Height="23"
Click="IncreaseX" />
<TextBlock
Height="23"
Name="textBlock2"
Text="Y"
Width="50"
TextAlignment="Center" />
<Button
Content="-"
Width="13"
Height="23"
Click="DecreaseY" />
<TextBox
Height="22"
HorizontalAlignment="Left"
Name="textBoxY"
VerticalAlignment="Top"
Width="50"
TextAlignment="Right"
Text="100" />
<Button
Content="+"
Width="13"
Height="23"
Click="IncreaseY" />
<TextBlock
Height="23"
Name="textBlock3"
Text="Width"
Width="50"
TextAlignment="Center" />
<Button
Content="-"
Width="13"
Height="23"
Click="DecreaseWidth" />
<TextBox
Height="22"
HorizontalAlignment="Left"
Name="textBoxWidth"
VerticalAlignment="Top"
Width="50"
TextAlignment="Right"
Text="600" />
<Button
Content="+"
Width="13"
Height="23"
Click="IncreaseWidth" />
<TextBlock
Height="23"
Name="textBlock4"
Text="Height"
Width="50"
TextAlignment="Center" />
<Button
Content="-"
Width="13"
Height="23"
Click="DecreaseHeight" />
<TextBox
Height="22"
HorizontalAlignment="Left"
Name="textBoxHeight"
VerticalAlignment="Top"
Width="50"
TextAlignment="Right"
Text="360" />
<Button
Content="+"
Width="13"
Height="23"
Click="IncreaseHeight" />
</WrapPanel>
</Window>
TransparentWindow.xaml
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
WindowStyle="None"
Opacity="0.7"
ShowInTaskbar="False"
AllowsTransparency="True" Background="Black"
Topmost="True" WindowState="Normal"
WindowStartupLocation="Manual"
Left="0" Top="0">
</Window>
Application.py
import wpf
from System.Windows import Application
from Window1 import Window1
window = Window1()
app = Application()
app.Run(window)