Filter quick switch items to only show buttons for monitors that are currently connected. This prevents users from attempting to switch inputs on monitors that are no longer available.
648 lines
24 KiB
C#
648 lines
24 KiB
C#
using CMM.Library.Config;
|
|
using CMM.Library.Method;
|
|
using CMM.Library.ViewModel;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Interop;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Animation;
|
|
using System.Windows.Threading;
|
|
|
|
namespace MonitorControl;
|
|
|
|
public partial class MainWindow : Window
|
|
{
|
|
private List<(XMonitor Monitor, List<InputSourceOption> Options)> _loadedMonitors = new();
|
|
private Storyboard? _spinnerStoryboard;
|
|
private DispatcherTimer? _showLogButtonTimer;
|
|
private UpdateInfo? _pendingUpdate;
|
|
private DateTime _lastUpdateCheck = DateTime.MinValue;
|
|
|
|
// WM_DISPLAYCHANGE: sent when display resolution changes or monitors are connected/disconnected
|
|
private const int WM_DISPLAYCHANGE = 0x007E;
|
|
private bool _displayChangeReloadPending;
|
|
|
|
public MainWindow()
|
|
{
|
|
InitializeComponent();
|
|
_spinnerStoryboard = (Storyboard)FindResource("SpinnerAnimation");
|
|
SourceInitialized += MainWindow_SourceInitialized;
|
|
DebugLogger.Log("MainWindow initialized");
|
|
}
|
|
|
|
private void MainWindow_SourceInitialized(object? sender, EventArgs e)
|
|
{
|
|
var source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
|
|
source?.AddHook(WndProc);
|
|
}
|
|
|
|
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
|
{
|
|
if (msg == WM_DISPLAYCHANGE && !_displayChangeReloadPending)
|
|
{
|
|
_displayChangeReloadPending = true;
|
|
DebugLogger.Log("WM_DISPLAYCHANGE received - scheduling monitor refresh");
|
|
|
|
// Debounce: display changes often fire multiple messages in quick succession.
|
|
// Wait 2 seconds for things to settle, then reload if window is visible.
|
|
var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };
|
|
timer.Tick += async (s, args) =>
|
|
{
|
|
timer.Stop();
|
|
_displayChangeReloadPending = false;
|
|
|
|
if (IsVisible)
|
|
{
|
|
DebugLogger.Log("Reloading monitors after display change");
|
|
await LoadMonitors();
|
|
}
|
|
};
|
|
timer.Start();
|
|
}
|
|
|
|
return IntPtr.Zero;
|
|
}
|
|
|
|
public void ShowNearTray()
|
|
{
|
|
DebugLogger.Log("ShowNearTray called");
|
|
var workArea = SystemParameters.WorkArea;
|
|
Left = workArea.Right - Width - 10;
|
|
Top = workArea.Bottom - 350;
|
|
Show();
|
|
Activate();
|
|
|
|
// Start spinner
|
|
_spinnerStoryboard?.Begin(this, true);
|
|
|
|
// Show log button after 3 seconds if still loading
|
|
showLogButton.Visibility = Visibility.Collapsed;
|
|
_showLogButtonTimer?.Stop();
|
|
_showLogButtonTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(3) };
|
|
_showLogButtonTimer.Tick += (s, e) =>
|
|
{
|
|
_showLogButtonTimer.Stop();
|
|
if (loadingPanel.Visibility == Visibility.Visible || sp.Children.Contains(loadingPanel))
|
|
{
|
|
showLogButton.Visibility = Visibility.Visible;
|
|
DebugLogger.Log("Show Log button displayed (loading took > 3s)");
|
|
}
|
|
};
|
|
_showLogButtonTimer.Start();
|
|
|
|
// Check for updates in background (max once per hour)
|
|
if ((DateTime.Now - _lastUpdateCheck).TotalMinutes > 60)
|
|
{
|
|
_lastUpdateCheck = DateTime.Now;
|
|
_ = CheckForUpdatesAsync();
|
|
}
|
|
|
|
Dispatcher.BeginInvoke(new Action(() =>
|
|
{
|
|
Top = workArea.Bottom - ActualHeight - 10;
|
|
}), DispatcherPriority.Loaded);
|
|
}
|
|
|
|
private async Task CheckForUpdatesAsync()
|
|
{
|
|
try
|
|
{
|
|
var update = await UpdateChecker.CheckForUpdateAsync();
|
|
if (update != null)
|
|
{
|
|
_pendingUpdate = update;
|
|
await Dispatcher.InvokeAsync(() => ShowUpdateBanner(update));
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Silently ignore update check failures
|
|
}
|
|
}
|
|
|
|
private void ShowUpdateBanner(UpdateInfo update)
|
|
{
|
|
updateBanner.Visibility = Visibility.Visible;
|
|
updateText.Text = $"v{update.LatestVersion} available - Click to update";
|
|
}
|
|
|
|
private void UpdateBanner_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
|
{
|
|
if (_pendingUpdate != null && !string.IsNullOrEmpty(_pendingUpdate.DownloadUrl))
|
|
{
|
|
UpdateChecker.OpenDownloadPage(_pendingUpdate.DownloadUrl);
|
|
}
|
|
}
|
|
|
|
private void Window_Deactivated(object sender, EventArgs e)
|
|
{
|
|
Hide();
|
|
_spinnerStoryboard?.Stop(this);
|
|
}
|
|
|
|
private void ExitButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
Application.Current.Shutdown();
|
|
}
|
|
|
|
private void AboutButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
|
|
var versionStr = $"{version?.Major}.{version?.Minor}.{version?.Build}";
|
|
|
|
var aboutWindow = new Window
|
|
{
|
|
Title = "About Monitor Control",
|
|
Width = 300,
|
|
Height = 240,
|
|
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
|
ResizeMode = ResizeMode.NoResize,
|
|
WindowStyle = WindowStyle.None,
|
|
AllowsTransparency = true,
|
|
Background = Brushes.Transparent
|
|
};
|
|
|
|
var border = new Border
|
|
{
|
|
Background = new SolidColorBrush(Color.FromArgb(0xF0, 0x33, 0x33, 0x33)),
|
|
CornerRadius = new CornerRadius(8),
|
|
BorderBrush = new SolidColorBrush(Color.FromRgb(0x55, 0x55, 0x55)),
|
|
BorderThickness = new Thickness(1),
|
|
Padding = new Thickness(20)
|
|
};
|
|
|
|
var stack = new StackPanel { VerticalAlignment = VerticalAlignment.Center };
|
|
|
|
var logo = new System.Windows.Controls.Image
|
|
{
|
|
Source = new System.Windows.Media.Imaging.BitmapImage(new Uri("pack://application:,,,/MonitorIcon.png")),
|
|
Width = 48,
|
|
Height = 48,
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
Margin = new Thickness(0, 0, 0, 10)
|
|
};
|
|
stack.Children.Add(logo);
|
|
|
|
stack.Children.Add(new TextBlock
|
|
{
|
|
Text = "Monitor Control",
|
|
Foreground = Brushes.White,
|
|
FontSize = 18,
|
|
FontWeight = FontWeights.SemiBold,
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
Margin = new Thickness(0, 0, 0, 5)
|
|
});
|
|
|
|
stack.Children.Add(new TextBlock
|
|
{
|
|
Text = $"Version {versionStr}",
|
|
Foreground = Brushes.LightGray,
|
|
FontSize = 12,
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
Margin = new Thickness(0, 0, 0, 10)
|
|
});
|
|
|
|
stack.Children.Add(new TextBlock
|
|
{
|
|
Text = "by David H. Friedel Jr",
|
|
Foreground = Brushes.Gray,
|
|
FontSize = 11,
|
|
HorizontalAlignment = HorizontalAlignment.Center
|
|
});
|
|
|
|
stack.Children.Add(new TextBlock
|
|
{
|
|
Text = "MarketAlly",
|
|
Foreground = Brushes.Gray,
|
|
FontSize = 11,
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
Margin = new Thickness(0, 0, 0, 15)
|
|
});
|
|
|
|
var closeBtn = new Button
|
|
{
|
|
Content = "OK",
|
|
Width = 80,
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
Style = (Style)FindResource("DarkButton")
|
|
};
|
|
closeBtn.Click += (s, args) => aboutWindow.Close();
|
|
stack.Children.Add(closeBtn);
|
|
|
|
border.Child = stack;
|
|
aboutWindow.Content = border;
|
|
aboutWindow.ShowDialog();
|
|
}
|
|
|
|
private void ShowLogButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
var logWindow = new Window
|
|
{
|
|
Title = "Debug Log",
|
|
Width = 600,
|
|
Height = 400,
|
|
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
|
Background = new SolidColorBrush(Color.FromRgb(45, 45, 45))
|
|
};
|
|
|
|
var grid = new Grid();
|
|
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
|
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
|
|
|
var textBox = new TextBox
|
|
{
|
|
Text = DebugLogger.GetLogs(),
|
|
IsReadOnly = true,
|
|
Background = new SolidColorBrush(Color.FromRgb(30, 30, 30)),
|
|
Foreground = Brushes.LightGray,
|
|
FontFamily = new FontFamily("Consolas"),
|
|
FontSize = 11,
|
|
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
|
|
HorizontalScrollBarVisibility = ScrollBarVisibility.Auto,
|
|
TextWrapping = TextWrapping.NoWrap,
|
|
Margin = new Thickness(10)
|
|
};
|
|
Grid.SetRow(textBox, 0);
|
|
grid.Children.Add(textBox);
|
|
|
|
var buttonPanel = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right, Margin = new Thickness(10) };
|
|
|
|
var copyBtn = new Button { Content = "Copy to Clipboard", Padding = new Thickness(10, 5, 10, 5), Margin = new Thickness(0, 0, 10, 0) };
|
|
copyBtn.Click += (s, args) =>
|
|
{
|
|
Clipboard.SetText(DebugLogger.GetLogs());
|
|
MessageBox.Show("Log copied to clipboard!", "Debug Log", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
};
|
|
buttonPanel.Children.Add(copyBtn);
|
|
|
|
var openFileBtn = new Button { Content = "Open Log File", Padding = new Thickness(10, 5, 10, 5), Margin = new Thickness(0, 0, 10, 0) };
|
|
openFileBtn.Click += (s, args) =>
|
|
{
|
|
try
|
|
{
|
|
System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{DebugLogger.LogFilePath}\"");
|
|
}
|
|
catch { }
|
|
};
|
|
buttonPanel.Children.Add(openFileBtn);
|
|
|
|
var closeBtn = new Button { Content = "Close", Padding = new Thickness(10, 5, 10, 5) };
|
|
closeBtn.Click += (s, args) => logWindow.Close();
|
|
buttonPanel.Children.Add(closeBtn);
|
|
|
|
Grid.SetRow(buttonPanel, 1);
|
|
grid.Children.Add(buttonPanel);
|
|
|
|
logWindow.Content = grid;
|
|
logWindow.Show();
|
|
}
|
|
|
|
public async Task LoadMonitors()
|
|
{
|
|
DebugLogger.Log("LoadMonitors started");
|
|
var newChildren = new List<UIElement>();
|
|
_loadedMonitors.Clear();
|
|
|
|
try
|
|
{
|
|
DebugLogger.Log("Scanning for monitors...");
|
|
await CMMCommand.ScanMonitor();
|
|
DebugLogger.Log("Scan complete, reading monitor data...");
|
|
var monitors = (await CMMCommand.ReadMonitorsData()).ToList();
|
|
DebugLogger.Log($"Found {monitors.Count} monitor(s)");
|
|
|
|
if (!monitors.Any())
|
|
{
|
|
DebugLogger.Log("No DDC/CI monitors detected");
|
|
newChildren.Add(new TextBlock
|
|
{
|
|
Text = "No DDC/CI monitors detected",
|
|
Foreground = Brushes.LightGray,
|
|
FontSize = 12,
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
Margin = new Thickness(0, 30, 0, 30)
|
|
});
|
|
}
|
|
else
|
|
{
|
|
foreach (var m in monitors)
|
|
{
|
|
DebugLogger.Log($"Processing monitor: {m.MonitorName} (SN: {m.SerialNumber})");
|
|
|
|
DebugLogger.Log($" Getting brightness...");
|
|
var brightness = await CMMCommand.GetBrightness(m.SerialNumber) ?? 50;
|
|
DebugLogger.Log($" Brightness: {brightness}");
|
|
|
|
DebugLogger.Log($" Getting contrast...");
|
|
var contrast = await CMMCommand.GetContrast(m.SerialNumber) ?? 50;
|
|
DebugLogger.Log($" Contrast: {contrast}");
|
|
|
|
DebugLogger.Log($" Getting input source...");
|
|
var inputSource = await CMMCommand.GetInputSource(m.SerialNumber);
|
|
DebugLogger.Log($" Input source: {inputSource}");
|
|
|
|
DebugLogger.Log($" Getting input options...");
|
|
var inputOptions = await CMMCommand.GetInputSourceOptions(m.SerialNumber);
|
|
DebugLogger.Log($" Input options count: {inputOptions.Count}");
|
|
|
|
// Add any previously discovered ports from config
|
|
var discoveredPorts = MonitorConfigManager.GetDiscoveredPorts(m.SerialNumber, inputOptions);
|
|
foreach (var port in discoveredPorts)
|
|
{
|
|
inputOptions.Insert(0, port);
|
|
DebugLogger.Log($" Added discovered port from config: {port.Value} ({port.Name})");
|
|
}
|
|
|
|
// Some monitors don't report current input in their possible values list
|
|
// Add it if missing and save to config for future
|
|
if (inputSource.HasValue && !inputOptions.Any(o => o.Value == inputSource.Value))
|
|
{
|
|
var currentInputName = CMMCommand.GetInputSourceName(inputSource.Value);
|
|
inputOptions.Insert(0, new InputSourceOption(inputSource.Value, currentInputName));
|
|
DebugLogger.Log($" Added missing current input: {inputSource.Value} ({currentInputName})");
|
|
|
|
// Save this discovered port so it appears in future even when not current
|
|
MonitorConfigManager.AddDiscoveredPort(m.SerialNumber, m.MonitorName, inputSource.Value, currentInputName);
|
|
DebugLogger.Log($" Saved discovered port to config");
|
|
}
|
|
|
|
DebugLogger.Log($" Getting power status...");
|
|
var powerStatus = await CMMCommand.GetMonPowerStatus(m.SerialNumber) ?? "Unknown";
|
|
DebugLogger.Log($" Power status: {powerStatus}");
|
|
|
|
_loadedMonitors.Add((m, inputOptions));
|
|
|
|
// Apply config to filter hidden ports and use custom labels
|
|
// Pass currentInput so we never hide the currently active port
|
|
var filteredOptions = MonitorConfigManager.ApplyConfigToOptions(m.SerialNumber, inputOptions, inputSource);
|
|
DebugLogger.Log($" Filtered options count: {filteredOptions.Count}");
|
|
|
|
// Monitor name header with Config button
|
|
newChildren.Add(CreateMonitorHeader(m, inputOptions));
|
|
|
|
newChildren.Add(CreateSliderRow("Brightness", brightness, m.SerialNumber, CMMCommand.SetBrightness));
|
|
newChildren.Add(CreateSliderRow("Contrast", contrast, m.SerialNumber, CMMCommand.SetContrast));
|
|
|
|
if (filteredOptions.Count > 0)
|
|
newChildren.Add(CreateInputRow(inputSource, filteredOptions, m.SerialNumber));
|
|
|
|
newChildren.Add(CreatePowerRow(powerStatus, m.SerialNumber));
|
|
|
|
// Separator
|
|
newChildren.Add(new Border { Height = 1, Background = new SolidColorBrush(Color.FromRgb(80, 80, 80)), Margin = new Thickness(0, 10, 0, 2) });
|
|
|
|
DebugLogger.Log($" Monitor {m.MonitorName} processing complete");
|
|
}
|
|
|
|
// Remove last separator
|
|
if (newChildren.Count > 0 && newChildren.Last() is Border)
|
|
newChildren.RemoveAt(newChildren.Count - 1);
|
|
}
|
|
|
|
// Load quick-switch toolbar
|
|
DebugLogger.Log("Loading quick-switch toolbar...");
|
|
LoadQuickSwitchToolbar();
|
|
DebugLogger.Log("Quick-switch toolbar loaded");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
DebugLogger.LogError("LoadMonitors failed", ex);
|
|
newChildren.Clear();
|
|
newChildren.Add(new TextBlock { Text = $"Error: {ex.Message}", Foreground = Brushes.OrangeRed, FontSize = 11, TextWrapping = TextWrapping.Wrap });
|
|
}
|
|
|
|
// Stop spinner and timer
|
|
_spinnerStoryboard?.Stop(this);
|
|
_showLogButtonTimer?.Stop();
|
|
|
|
sp.VerticalAlignment = VerticalAlignment.Top;
|
|
sp.Children.Clear();
|
|
foreach (var child in newChildren)
|
|
sp.Children.Add(child);
|
|
|
|
DebugLogger.Log("LoadMonitors complete, UI updated");
|
|
|
|
Dispatcher.BeginInvoke(new Action(() =>
|
|
{
|
|
var workArea = SystemParameters.WorkArea;
|
|
Top = workArea.Bottom - ActualHeight - 10;
|
|
}), DispatcherPriority.Loaded);
|
|
}
|
|
|
|
private void LoadQuickSwitchToolbar()
|
|
{
|
|
quickSwitchPanel.Children.Clear();
|
|
var quickItems = MonitorConfigManager.GetQuickSwitchItems();
|
|
|
|
// Only show buttons for monitors that are currently connected
|
|
var connectedSerials = new HashSet<string>(_loadedMonitors.Select(m => m.Monitor.SerialNumber));
|
|
quickItems = quickItems.Where(i => connectedSerials.Contains(i.MonitorSerialNumber)).ToList();
|
|
|
|
if (quickItems.Count == 0)
|
|
{
|
|
quickSwitchPanel.Visibility = Visibility.Collapsed;
|
|
return;
|
|
}
|
|
|
|
quickSwitchPanel.Visibility = Visibility.Visible;
|
|
|
|
foreach (var item in quickItems)
|
|
{
|
|
var btn = new Button
|
|
{
|
|
Content = $"{item.PortLabel}",
|
|
ToolTip = $"{item.MonitorName}: {item.PortLabel}",
|
|
Margin = new Thickness(0, 0, 6, 6),
|
|
Style = (Style)FindResource("QuickSwitchButton"),
|
|
Tag = item
|
|
};
|
|
|
|
btn.Click += async (s, e) =>
|
|
{
|
|
if (s is Button b && b.Tag is QuickSwitchItem qsi)
|
|
{
|
|
await CMMCommand.SetInputSource(qsi.MonitorSerialNumber, qsi.PortVcpValue);
|
|
UpdateInputDropdown(qsi.MonitorSerialNumber, qsi.PortVcpValue);
|
|
}
|
|
};
|
|
|
|
quickSwitchPanel.Children.Add(btn);
|
|
}
|
|
}
|
|
|
|
private void UpdateInputDropdown(string serialNumber, int newValue)
|
|
{
|
|
foreach (var child in sp.Children)
|
|
{
|
|
if (child is StackPanel row)
|
|
{
|
|
foreach (var element in row.Children)
|
|
{
|
|
if (element is ComboBox cb && cb.Tag is string sn && sn == serialNumber)
|
|
{
|
|
var options = cb.ItemsSource as List<InputSourceOption>;
|
|
if (options != null)
|
|
{
|
|
var index = options.FindIndex(o => o.Value == newValue);
|
|
if (index >= 0)
|
|
cb.SelectedIndex = index;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private StackPanel CreateMonitorHeader(XMonitor monitor, List<InputSourceOption> allOptions)
|
|
{
|
|
var row = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 8, 0, 6) };
|
|
|
|
row.Children.Add(new TextBlock
|
|
{
|
|
Text = monitor.MonitorName,
|
|
Foreground = Brushes.White,
|
|
FontSize = 13,
|
|
FontWeight = FontWeights.SemiBold,
|
|
VerticalAlignment = VerticalAlignment.Center
|
|
});
|
|
|
|
var configBtn = new Button
|
|
{
|
|
Content = "Config",
|
|
Margin = new Thickness(10, 0, 0, 0),
|
|
FontSize = 11,
|
|
Style = (Style)FindResource("DarkButton"),
|
|
Tag = (monitor, allOptions)
|
|
};
|
|
|
|
configBtn.Click += ConfigButton_Click;
|
|
row.Children.Add(configBtn);
|
|
|
|
return row;
|
|
}
|
|
|
|
private async void ConfigButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (sender is Button btn && btn.Tag is ValueTuple<XMonitor, List<InputSourceOption>> data)
|
|
{
|
|
var (monitor, options) = data;
|
|
var configWindow = new ConfigWindow(monitor.SerialNumber, monitor.MonitorName, options);
|
|
configWindow.Owner = this;
|
|
configWindow.ShowDialog();
|
|
|
|
if (configWindow.ConfigChanged)
|
|
{
|
|
MonitorConfigManager.ClearCache();
|
|
await LoadMonitors();
|
|
}
|
|
}
|
|
}
|
|
|
|
private StackPanel CreateSliderRow(string label, int value, string serialNumber, Func<string, int, Task> setCommand)
|
|
{
|
|
var row = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 3, 0, 3) };
|
|
row.Children.Add(new TextBlock { Text = label, Foreground = Brushes.LightGray, Width = 70, FontSize = 12, VerticalAlignment = VerticalAlignment.Center });
|
|
|
|
var slider = new Slider { Minimum = 0, Maximum = 100, Value = value, Width = 140, Tag = serialNumber };
|
|
var valueText = new TextBlock { Text = value.ToString(), Foreground = Brushes.White, Width = 30, FontSize = 12, Margin = new Thickness(5, 0, 0, 0) };
|
|
|
|
slider.ValueChanged += (s, e) => valueText.Text = ((int)e.NewValue).ToString();
|
|
slider.PreviewMouseUp += async (s, e) =>
|
|
{
|
|
if (s is Slider sl && sl.Tag is string sn)
|
|
await setCommand(sn, (int)sl.Value);
|
|
};
|
|
|
|
row.Children.Add(slider);
|
|
row.Children.Add(valueText);
|
|
return row;
|
|
}
|
|
|
|
private StackPanel CreateInputRow(int? currentInput, List<InputSourceOption> options, string serialNumber)
|
|
{
|
|
DebugLogger.Log($" CreateInputRow: currentInput={currentInput}, optionCount={options.Count}");
|
|
foreach (var opt in options)
|
|
DebugLogger.Log($" Option: Value={opt.Value}, Name={opt.Name}");
|
|
|
|
var row = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 3, 0, 3) };
|
|
row.Children.Add(new TextBlock { Text = "Input", Foreground = Brushes.LightGray, Width = 70, FontSize = 12, VerticalAlignment = VerticalAlignment.Center });
|
|
|
|
var combo = new ComboBox
|
|
{
|
|
Width = 170,
|
|
ItemsSource = options,
|
|
DisplayMemberPath = "Name",
|
|
Tag = serialNumber,
|
|
Style = (Style)FindResource("DarkComboBox"),
|
|
ItemContainerStyle = (Style)FindResource("DarkComboBoxItem")
|
|
};
|
|
|
|
row.Children.Add(combo);
|
|
|
|
// Set selection AFTER adding to visual tree, BEFORE adding event handler
|
|
if (currentInput.HasValue)
|
|
{
|
|
var index = options.FindIndex(o => o.Value == currentInput.Value);
|
|
DebugLogger.Log($" Selection: Looking for Value={currentInput.Value}, found at index={index}");
|
|
if (index >= 0)
|
|
combo.SelectedIndex = index;
|
|
}
|
|
else
|
|
{
|
|
DebugLogger.Log($" Selection: currentInput is null, no selection");
|
|
}
|
|
|
|
// Add event handler AFTER setting the initial selection
|
|
combo.SelectionChanged += async (s, e) =>
|
|
{
|
|
// Only trigger if user actually changed the selection (not initial load)
|
|
if (s is ComboBox cb && cb.Tag is string sn && cb.SelectedItem is InputSourceOption opt && e.AddedItems.Count > 0 && e.RemovedItems.Count > 0)
|
|
await CMMCommand.SetInputSource(sn, opt.Value);
|
|
};
|
|
|
|
return row;
|
|
}
|
|
|
|
private StackPanel CreatePowerRow(string status, string serialNumber)
|
|
{
|
|
var row = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 3, 0, 3) };
|
|
row.Children.Add(new TextBlock { Text = "Power", Foreground = Brushes.LightGray, Width = 70, FontSize = 12, VerticalAlignment = VerticalAlignment.Center });
|
|
|
|
var isUnsupported = string.IsNullOrEmpty(status) || status == "Unknown";
|
|
|
|
var btn = new Button
|
|
{
|
|
Content = isUnsupported ? "Power Unsupported" : status,
|
|
Width = 170,
|
|
Tag = serialNumber,
|
|
Style = (Style)FindResource("DarkButton"),
|
|
IsEnabled = !isUnsupported
|
|
};
|
|
|
|
if (!isUnsupported)
|
|
{
|
|
btn.Click += async (s, e) =>
|
|
{
|
|
if (s is Button b && b.Tag is string sn)
|
|
{
|
|
var current = await CMMCommand.GetMonPowerStatus(sn);
|
|
if (current == "Sleep" || current == "PowerOff")
|
|
await CMMCommand.PowerOn(sn);
|
|
else
|
|
await CMMCommand.Sleep(sn);
|
|
await Task.Delay(1000);
|
|
b.Content = await CMMCommand.GetMonPowerStatus(sn);
|
|
}
|
|
};
|
|
}
|
|
|
|
row.Children.Add(btn);
|
|
return row;
|
|
}
|
|
}
|