-
Notifications
You must be signed in to change notification settings - Fork 0
物理モニターを選んで配置する機能を実装 #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| <ui:FluentWindow x:Class="WindowController.App.DesktopPickerWindow" | ||
| xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
| xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
| xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" | ||
| Title="仮想デスクトップを選択" | ||
| Width="440" SizeToContent="Height" | ||
| WindowStartupLocation="CenterOwner" | ||
| ResizeMode="NoResize" | ||
| Topmost="True" | ||
| ShowInTaskbar="False" | ||
| ExtendsContentIntoTitleBar="True" | ||
| WindowBackdropType="Mica"> | ||
| <Grid> | ||
| <Grid.RowDefinitions> | ||
| <RowDefinition Height="Auto"/> | ||
| <RowDefinition Height="*"/> | ||
| </Grid.RowDefinitions> | ||
|
|
||
| <ui:TitleBar Grid.Row="0" Title="仮想デスクトップを選択" | ||
| ShowMaximize="False" ShowMinimize="False"/> | ||
|
|
||
| <StackPanel Grid.Row="1" Margin="24,0,24,24"> | ||
| <TextBlock Text="配置先のデスクトップを選択してください" | ||
| Style="{StaticResource SectionHeader}" Margin="0,0,0,4"/> | ||
| <TextBlock x:Name="MonitorInfoText" | ||
| Style="{StaticResource SectionDescription}"/> | ||
|
|
||
| <Border Style="{StaticResource SectionCard}"> | ||
| <ItemsControl x:Name="DesktopList"> | ||
| <ItemsControl.ItemTemplate> | ||
| <DataTemplate> | ||
| <ui:Button Click="DesktopButton_Click" | ||
| Margin="0,3" Padding="16,12" | ||
| HorizontalContentAlignment="Left" | ||
| HorizontalAlignment="Stretch" | ||
| Appearance="Secondary"> | ||
| <ui:Button.Content> | ||
| <StackPanel Orientation="Horizontal"> | ||
| <TextBlock Text="{Binding NumberLabel}" FontSize="14" | ||
| FontWeight="SemiBold" Margin="0,0,12,0" | ||
| VerticalAlignment="Center"/> | ||
| <TextBlock Text="{Binding DisplayName}" FontSize="14" | ||
| VerticalAlignment="Center"/> | ||
| <TextBlock Text="{Binding CurrentBadge}" FontSize="11" | ||
| Foreground="{DynamicResource SystemAccentColorPrimaryBrush}" | ||
| VerticalAlignment="Center" Margin="8,0,0,0"/> | ||
| </StackPanel> | ||
| </ui:Button.Content> | ||
| </ui:Button> | ||
| </DataTemplate> | ||
| </ItemsControl.ItemTemplate> | ||
| </ItemsControl> | ||
| </Border> | ||
|
|
||
| <ui:Button Click="Cancel_Click" | ||
| Margin="0,12,0,0" Padding="16,8" | ||
| HorizontalAlignment="Right" | ||
| Appearance="Secondary"> | ||
| <ui:Button.Icon> | ||
| <ui:SymbolIcon Symbol="Dismiss24"/> | ||
| </ui:Button.Icon> | ||
| <ui:Button.Content> | ||
| <TextBlock Text="キャンセル"/> | ||
| </ui:Button.Content> | ||
| </ui:Button> | ||
| </StackPanel> | ||
| </Grid> | ||
| </ui:FluentWindow> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| using System.Windows; | ||
| using System.Windows.Input; | ||
| using WindowController.Win32; | ||
| using Wpf.Ui.Controls; | ||
|
|
||
| namespace WindowController.App; | ||
|
|
||
| /// <summary> | ||
| /// Item shown in the desktop picker list. | ||
| /// </summary> | ||
| public class DesktopPickerItem | ||
| { | ||
| public int Number { get; init; } | ||
| public Guid DesktopId { get; init; } | ||
| public string NumberLabel => $"{Number}:"; | ||
| public string DisplayName { get; init; } = ""; | ||
| public string CurrentBadge { get; init; } = ""; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Modal dialog that lists available virtual desktops and lets the user pick one. | ||
| /// Supports keyboard shortcuts (1–9, NumPad, Escape). | ||
| /// </summary> | ||
| public partial class DesktopPickerWindow : FluentWindow | ||
| { | ||
| private readonly List<DesktopPickerItem> _items; | ||
| public Guid? SelectedDesktopId { get; private set; } | ||
|
|
||
| public DesktopPickerWindow( | ||
| List<VirtualDesktopService.VirtualDesktopInfo> desktops, | ||
| Guid? currentDesktopId, | ||
| string monitorDescription) | ||
| { | ||
| InitializeComponent(); | ||
| MonitorInfoText.Text = monitorDescription; | ||
|
|
||
| _items = desktops.Select(d => new DesktopPickerItem | ||
| { | ||
| Number = d.Number, | ||
| DesktopId = d.Id, | ||
| DisplayName = string.IsNullOrEmpty(d.Name) | ||
| ? $"デスクトップ {d.Number}" | ||
| : d.Name, | ||
| CurrentBadge = d.Id == currentDesktopId ? "(現在)" : "" | ||
| }).ToList(); | ||
| DesktopList.ItemsSource = _items; | ||
| } | ||
|
|
||
| private void DesktopButton_Click(object sender, RoutedEventArgs e) | ||
| { | ||
| if (sender is FrameworkElement fe && fe.DataContext is DesktopPickerItem item) | ||
| { | ||
| SelectedDesktopId = item.DesktopId; | ||
| DialogResult = true; | ||
| } | ||
| } | ||
|
|
||
| private void Cancel_Click(object sender, RoutedEventArgs e) | ||
| { | ||
| DialogResult = false; | ||
| } | ||
|
|
||
| protected override void OnPreviewKeyDown(KeyEventArgs e) | ||
| { | ||
| base.OnPreviewKeyDown(e); | ||
|
|
||
| int num = -1; | ||
| if (e.Key >= Key.D1 && e.Key <= Key.D9) | ||
| num = e.Key - Key.D0; | ||
| else if (e.Key >= Key.NumPad1 && e.Key <= Key.NumPad9) | ||
| num = e.Key - Key.NumPad0; | ||
| else if (e.Key == Key.Escape) | ||
| { | ||
| DialogResult = false; | ||
| e.Handled = true; | ||
| return; | ||
| } | ||
|
|
||
| if (num >= 1 && num <= _items.Count) | ||
| { | ||
| SelectedDesktopId = _items[num - 1].DesktopId; | ||
| DialogResult = true; | ||
| e.Handled = true; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| <Window x:Class="WindowController.App.MonitorOverlayWindow" | ||
| xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
| xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
| WindowStyle="None" | ||
| AllowsTransparency="True" | ||
| Background="Transparent" | ||
| Topmost="True" | ||
| ShowInTaskbar="False" | ||
| ShowActivated="False" | ||
| Focusable="False" | ||
| ResizeMode="NoResize" | ||
| Width="400" Height="320" | ||
| Left="-10000" Top="-10000"> | ||
| <Border CornerRadius="20" Background="#DD1e1e2e"> | ||
| <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> | ||
| <TextBlock x:Name="NumberText" FontSize="160" FontWeight="Bold" | ||
| Foreground="White" HorizontalAlignment="Center" | ||
| Margin="0,-8,0,0"/> | ||
| <TextBlock x:Name="InfoText" FontSize="15" | ||
| Foreground="#AAAACC" HorizontalAlignment="Center" | ||
| TextAlignment="Center" LineHeight="22"/> | ||
| </StackPanel> | ||
| </Border> | ||
| </Window> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| using System.Windows; | ||
| using System.Windows.Interop; | ||
| using WindowController.Win32; | ||
|
|
||
| namespace WindowController.App; | ||
|
|
||
| /// <summary> | ||
| /// Semi-transparent overlay window that shows a large monitor number. | ||
| /// One instance is placed on each physical monitor while the monitor picker is open. | ||
| /// </summary> | ||
| public partial class MonitorOverlayWindow : Window | ||
| { | ||
| private readonly int _monX, _monY, _monW, _monH; | ||
|
|
||
| public MonitorOverlayWindow(int number, string info, | ||
| int monX, int monY, int monW, int monH) | ||
| { | ||
| InitializeComponent(); | ||
| NumberText.Text = number.ToString(); | ||
| InfoText.Text = info; | ||
| _monX = monX; | ||
| _monY = monY; | ||
| _monW = monW; | ||
| _monH = monH; | ||
| SourceInitialized += OnSourceInitialized; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Position the overlay centered on the target monitor using physical pixel coordinates. | ||
| /// Done in SourceInitialized to avoid flicker (before the window is first rendered). | ||
| /// </summary> | ||
| private void OnSourceInitialized(object? sender, EventArgs e) | ||
| { | ||
| var hwnd = new WindowInteropHelper(this).Handle; | ||
| const int owPx = 400, ohPx = 320; | ||
|
|
||
| // WPF sizes are in DIPs; SetWindowPos expects device pixels. | ||
| // Set Width/Height so the physical size matches owPx/ohPx at current DPI. | ||
| var source = HwndSource.FromHwnd(hwnd); | ||
| var m = source?.CompositionTarget?.TransformToDevice; | ||
| var scaleX = m?.M11 ?? 1.0; | ||
| var scaleY = m?.M22 ?? 1.0; | ||
| if (scaleX <= 0) scaleX = 1.0; | ||
| if (scaleY <= 0) scaleY = 1.0; | ||
| Width = owPx / scaleX; | ||
| Height = ohPx / scaleY; | ||
|
|
||
| int cx = _monX + (_monW - owPx) / 2; | ||
| int cy = _monY + (_monH - ohPx) / 2; | ||
| NativeMethods.SetWindowPos(hwnd, 0, cx, cy, owPx, ohPx, | ||
| NativeMethods.SWP_NOACTIVATE | NativeMethods.SWP_NOZORDER); | ||
|
Comment on lines
34
to
51
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| <ui:FluentWindow x:Class="WindowController.App.MonitorPickerWindow" | ||
| xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
| xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
| xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" | ||
| Title="モニターを選択して配置" | ||
| Width="440" SizeToContent="Height" | ||
| MinHeight="0" | ||
| WindowStartupLocation="Manual" | ||
| ResizeMode="NoResize" | ||
| Topmost="True" | ||
| ShowInTaskbar="False" | ||
| ExtendsContentIntoTitleBar="True" | ||
| WindowBackdropType="Mica"> | ||
| <Grid> | ||
| <Grid.RowDefinitions> | ||
| <RowDefinition Height="Auto"/> | ||
| <RowDefinition Height="Auto"/> | ||
| </Grid.RowDefinitions> | ||
|
|
||
| <ui:TitleBar Grid.Row="0" Title="モニターを選択して配置" | ||
| ShowMaximize="False" ShowMinimize="False"/> | ||
|
|
||
| <StackPanel Grid.Row="1" Margin="24,0,24,24"> | ||
| <TextBlock Text="配置先のモニターを選択してください" | ||
| Style="{StaticResource SectionHeader}" Margin="0,0,0,4"/> | ||
| <TextBlock Text="番号キーまたはクリックで選択できます" | ||
| Style="{StaticResource SectionDescription}"/> | ||
|
|
||
| <Border Style="{StaticResource SectionCard}"> | ||
| <ItemsControl x:Name="MonitorList"> | ||
| <ItemsControl.ItemTemplate> | ||
| <DataTemplate> | ||
| <ui:Button Click="MonitorButton_Click" | ||
| Margin="0,3" Padding="16,12" | ||
| HorizontalContentAlignment="Left" | ||
| HorizontalAlignment="Stretch" | ||
| Appearance="Secondary"> | ||
| <ui:Button.Content> | ||
| <TextBlock Text="{Binding Label}" FontSize="14"/> | ||
| </ui:Button.Content> | ||
| </ui:Button> | ||
| </DataTemplate> | ||
| </ItemsControl.ItemTemplate> | ||
| </ItemsControl> | ||
| </Border> | ||
|
|
||
| <ui:Button Click="Cancel_Click" | ||
| Margin="0,12,0,0" Padding="16,8" | ||
| HorizontalAlignment="Right" | ||
| Appearance="Secondary"> | ||
| <ui:Button.Icon> | ||
| <ui:SymbolIcon Symbol="Dismiss24"/> | ||
| </ui:Button.Icon> | ||
| <ui:Button.Content> | ||
| <TextBlock Text="キャンセル"/> | ||
| </ui:Button.Content> | ||
| </ui:Button> | ||
| </StackPanel> | ||
| </Grid> | ||
| </ui:FluentWindow> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In WPF,
ContextMenudoes not inheritDataContextfrom its placement target, soCommand="{Binding ApplyToMonitorCommand}"will likely resolve to null and the menu items won’t execute. Bind viaPlacementTarget.DataContext(or explicitly setContextMenu.DataContext) so the commands come fromMainViewModel.