1
0
Fork 0
mirror of https://github.com/hawkeye-stan/msfs-popout-panel-manager.git synced 2024-10-16 14:10:45 +00:00

Version 3.3

This commit is contained in:
hawkeye 2022-04-17 23:38:33 -04:00
parent 973ebd3dd7
commit a90077b44c
33 changed files with 551 additions and 178 deletions

View file

@ -23,6 +23,7 @@ namespace MSFSPopoutPanelManager.FsConnector
SIMSTOP,
FLIGHTLOADED,
VIEW,
PAUSED
PAUSED,
NONE
};
}

View file

@ -5,13 +5,15 @@
<AssemblyName>FsConnector</AssemblyName>
<PackageId>MSFS 2020 Popout Panel Manager FsConnector</PackageId>
<Product>MSFS 2020 Popout Panel Manager FsConnector</Product>
<Version>3.2.0</Version>
<Version>3.3.0.0</Version>
<Authors>Stanley Kwok</Authors>
<Company>Stanley Kwok</Company>
<Copyright>Stanley Kwok 2021</Copyright>
<PackageProjectUrl>https://github.com/hawkeye-stan/msfs-popout-panel-manager</PackageProjectUrl>
<RootNamespace>MSFSPopoutPanelManager.FsConnector</RootNamespace>
<Platforms>x64;AnyCPU</Platforms>
<AssemblyVersion>3.3.0.0</AssemblyVersion>
<FileVersion>3.3.0.0</FileVersion>
</PropertyGroup>
<ItemGroup>

View file

@ -19,7 +19,7 @@ namespace MSFSPopoutPanelManager.FsConnector
public event EventHandler<EventArgs<dynamic>> OnReceivedData;
public event EventHandler OnConnected;
public event EventHandler OnDisconnected;
public event EventHandler<EventArgs<SimConnectSystemEvent>> OnReceiveSystemEvent;
public event EventHandler<EventArgs<Tuple<SimConnectSystemEvent, uint>>> OnReceiveSystemEvent;
public dynamic SimData { get; set; }
@ -183,12 +183,9 @@ namespace MSFSPopoutPanelManager.FsConnector
private void HandleOnReceiveEvent(SimConnect sender, SIMCONNECT_RECV_EVENT data)
{
var systemEvent = ((SimConnectSystemEvent)data.uEventID);
var tuple = new Tuple<SimConnectSystemEvent, uint>(systemEvent, data.uEventID);
// Only look at VIEW for cockpit view during loading of flight (dwData = 2)
if (systemEvent == SimConnectSystemEvent.VIEW && data.dwData != 2)
return;
OnReceiveSystemEvent?.Invoke(this, new EventArgs<SimConnectSystemEvent>(systemEvent));
OnReceiveSystemEvent?.Invoke(this, new EventArgs<Tuple<SimConnectSystemEvent, uint>>(tuple));
}
}
}

View file

@ -24,10 +24,12 @@ namespace MSFSPopoutPanelManager.Model
MinimizeToTray = false;
AlwaysOnTop = true;
UseAutoPanning = true;
AutoPanningKeyBinding = "0";
StartMinimized = false;
IncludeBuiltInPanel = false;
AutoPopOutPanels = false;
AutoPopOutPanelsWaitDelay = new AutoPopOutPanelsWaitDelay();
}
public void Load()
@ -36,10 +38,12 @@ namespace MSFSPopoutPanelManager.Model
this.MinimizeToTray = appSetting.MinimizeToTray;
this.AlwaysOnTop = appSetting.AlwaysOnTop;
this.UseAutoPanning = appSetting.UseAutoPanning;
this.AutoPanningKeyBinding = appSetting.AutoPanningKeyBinding;
this.StartMinimized = appSetting.StartMinimized;
this.IncludeBuiltInPanel = appSetting.IncludeBuiltInPanel;
this.AutoPopOutPanels = appSetting.AutoPopOutPanels;
this.AutoPopOutPanelsWaitDelay = appSetting.AutoPopOutPanelsWaitDelay;
AutoPopOutPanelsWaitDelay.DataChanged += (e, source) => WriteAppSetting(this);
_saveEnabled = true;
}
@ -61,19 +65,14 @@ namespace MSFSPopoutPanelManager.Model
}
}
//[OnDeserialized]
//private void OnDeserialized(StreamingContext context)
//{
// // Allow save data
// _saveEnabled = true;
//}
public bool MinimizeToTray { get; set; }
public bool AlwaysOnTop { get; set; }
public bool UseAutoPanning { get; set; }
public string AutoPanningKeyBinding { get; set; }
public bool StartMinimized { get; set; }
public bool IncludeBuiltInPanel { get; set; }
@ -130,18 +129,23 @@ namespace MSFSPopoutPanelManager.Model
serializer.Serialize(file, appSetting);
}
}
catch
catch(Exception ex)
{
Logger.LogStatus($"Unable to write app setting data file: {APP_SETTING_DATA_FILENAME}", StatusMessageType.Error);
}
}
}
public class AutoPopOutPanelsWaitDelay
public class AutoPopOutPanelsWaitDelay : INotifyPropertyChanged
{
// Using PropertyChanged.Fody
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler DataChanged;
public AutoPopOutPanelsWaitDelay()
{
ReadyToFlyButton = 4;
ReadyToFlyButton = 6;
InitialCockpitView = 2;
InstrumentationPowerOn = 2;
}
@ -151,5 +155,10 @@ namespace MSFSPopoutPanelManager.Model
public int InitialCockpitView { get; set; }
public int InstrumentationPowerOn { get; set; }
public void OnPropertyChanged(string propertyName, object before, object after)
{
DataChanged?.Invoke(this, null);
}
}
}

View file

@ -5,7 +5,7 @@
<RootNamespace>MSFSPopoutPanelManager.Model</RootNamespace>
<AssemblyName>Model</AssemblyName>
<PackageId>MSFS 2020 Popout Panel Manager Model</PackageId>
<Version>3.2.0</Version>
<Version>3.3.0.0</Version>
<Authors>Stanley Kwok</Authors>
<Company>Stanley Kwok</Company>
<Copyright>Stanley Kwok 2021</Copyright>

View file

@ -1,4 +1,6 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
@ -12,6 +14,7 @@ namespace MSFSPopoutPanelManager.Model
{
PanelSourceCoordinates = new ObservableCollection<PanelSourceCoordinate>();
PanelConfigs = new ObservableCollection<PanelConfig>();
BindingPlaneTitle = new ObservableCollection<string>();
IsLocked = false;
}
@ -21,7 +24,8 @@ namespace MSFSPopoutPanelManager.Model
public bool IsDefaultProfile { get; set; }
public string BindingPlaneTitle { get; set; }
[JsonConverter(typeof(SingleValueArrayConvertor<string>))]
public ObservableCollection<string> BindingPlaneTitle { get; set; }
public bool IsLocked { get; set; }
@ -44,7 +48,41 @@ namespace MSFSPopoutPanelManager.Model
[JsonIgnore]
public bool HasBindingPlaneTitle
{
get { return !string.IsNullOrEmpty(BindingPlaneTitle); }
get { return BindingPlaneTitle.Count > 0; }
}
}
public class SingleValueArrayConvertor<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object val = new object();
if(reader.TokenType == JsonToken.String)
{
var instance = (string)serializer.Deserialize(reader, typeof(string));
val = new ObservableCollection<string>() { instance };
}
else if(reader.TokenType == JsonToken.StartArray)
{
val = serializer.Deserialize(reader, objectType);
}
else if(reader.TokenType == JsonToken.Null)
{
val = new ObservableCollection<string>();
}
return val;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
}

View file

@ -60,8 +60,10 @@ namespace MSFSPopoutPanelManager.Provider
Thread.Sleep(200);
}
public static void SaveCustomViewZero(IntPtr hwnd)
public static void SaveCustomView(IntPtr hwnd, string keybinding)
{
uint customViewKey = (uint) (Convert.ToInt32(keybinding) + KEY_0);
PInvoke.SetForegroundWindow(hwnd);
Thread.Sleep(500);
@ -71,15 +73,17 @@ namespace MSFSPopoutPanelManager.Provider
// Set view using Ctrl-Alt-0
PInvoke.keybd_event(Convert.ToByte(VK_LCONTROL), 0, KEYEVENTF_KEYDOWN, 0);
PInvoke.keybd_event(Convert.ToByte(VK_LMENU), 0, KEYEVENTF_KEYDOWN, 0);
PInvoke.keybd_event(Convert.ToByte(KEY_0), 0, KEYEVENTF_KEYDOWN, 0);
PInvoke.keybd_event(Convert.ToByte(customViewKey), 0, KEYEVENTF_KEYDOWN, 0);
Thread.Sleep(200);
PInvoke.keybd_event(Convert.ToByte(KEY_0), 0, KEYEVENTF_KEYUP, 0);
PInvoke.keybd_event(Convert.ToByte(customViewKey), 0, KEYEVENTF_KEYUP, 0);
PInvoke.keybd_event(Convert.ToByte(VK_LMENU), 0, KEYEVENTF_KEYUP, 0);
PInvoke.keybd_event(Convert.ToByte(VK_LCONTROL), 0, KEYEVENTF_KEYUP, 0);
}
public static void LoadCustomViewZero(IntPtr hwnd)
public static void LoadCustomView(IntPtr hwnd, string keybinding)
{
uint customViewKey = (uint)(Convert.ToInt32(keybinding) + KEY_0);
PInvoke.SetForegroundWindow(hwnd);
Thread.Sleep(500);
@ -96,9 +100,9 @@ namespace MSFSPopoutPanelManager.Provider
// Then load view using Alt-0
PInvoke.keybd_event(Convert.ToByte(VK_LMENU), 0, KEYEVENTF_KEYDOWN, 0);
PInvoke.keybd_event(Convert.ToByte(KEY_0), 0, KEYEVENTF_KEYDOWN, 0);
PInvoke.keybd_event(Convert.ToByte(customViewKey), 0, KEYEVENTF_KEYDOWN, 0);
Thread.Sleep(200);
PInvoke.keybd_event(Convert.ToByte(KEY_0), 0, KEYEVENTF_KEYUP, 0);
PInvoke.keybd_event(Convert.ToByte(customViewKey), 0, KEYEVENTF_KEYUP, 0);
PInvoke.keybd_event(Convert.ToByte(VK_LMENU), 0, KEYEVENTF_KEYUP, 0);
}

View file

@ -21,13 +21,20 @@ namespace MSFSPopoutPanelManager.Provider
public const int SWP_NOMOVE = 0x0002;
public const int SWP_NOSIZE = 0x0001;
public const int SWP_FRAMECHANGED = 0x0020;
public const int SWP_NOREDRAW = 0x0008;
public const int SWP_ASYNCWINDOWPOS = 0x4000;
public const int SWP_ALWAYS_ON_TOP = SWP_NOMOVE | SWP_NOSIZE;
public const int GWL_STYLE = -16;
public const int WS_SIZEBOX = 0x00040000;
public const int WS_BORDER = 0x00800000;
public const int WS_DLGFRAME = 0x00400000;
public const int WS_CAPTION = WS_BORDER | WS_DLGFRAME;
public const int GWL_EXSTYLE = -20;
public const uint WS_SIZEBOX = 0x00040000;
public const uint WS_BORDER = 0x00800000;
public const uint WS_DLGFRAME = 0x00400000;
public const uint WS_CAPTION = WS_BORDER | WS_DLGFRAME;
public const uint WS_POPUP = 0x80000000;
public const uint WS_EX_DLGMODALFRAME = 0x00000001;
public const uint WS_THICKFRAME = 0x00040000;
public const int HWND_TOPMOST = -1;
public const int HWND_NOTOPMOST = -2;

View file

@ -61,21 +61,20 @@ namespace MSFSPopoutPanelManager.Provider
break;
case PanelConfigPropertyName.Left:
case PanelConfigPropertyName.Top:
// Do not allow changes to panel if title bar is hidden. This will cause the panel to resize incorrectly
if (panelConfig.HideTitlebar)
return;
PInvoke.MoveWindow(panelConfig.PanelHandle, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height, true);
break;
case PanelConfigPropertyName.Width:
case PanelConfigPropertyName.Height:
// Do not allow changes to panel if title bar is hidden. This will cause the panel to resize incorrectly
if (panelConfig.HideTitlebar)
return;
WindowManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, false);
int orignalLeft = panelConfig.Left;
PInvoke.MoveWindow(panelConfig.PanelHandle, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height, true);
MSFSBugPanelShiftWorkaround(panelConfig.PanelHandle, orignalLeft, panelConfig.Top, panelConfig.Width, panelConfig.Height);
if (panelConfig.HideTitlebar)
WindowManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, true);
break;
case PanelConfigPropertyName.AlwaysOnTop:
WindowManager.ApplyAlwaysOnTop(panelConfig.PanelHandle, panelConfig.AlwaysOnTop, new Rectangle(panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height));
@ -99,32 +98,43 @@ namespace MSFSPopoutPanelManager.Provider
if (index > -1)
{
var panelConfig = UserProfile.PanelConfigs[index];
// Do not allow changes to panel if title bar is hidden. This will cause the panel to resize incorrectly
if (panelConfig.HideTitlebar)
return;
int orignalLeft = panelConfig.Left;
switch (panelConfigItem.PanelConfigProperty)
{
case PanelConfigPropertyName.Left:
PInvoke.MoveWindow(panelConfig.PanelHandle, panelConfig.Left + changeAmount, panelConfig.Top, panelConfig.Width, panelConfig.Height, false);
panelConfig.Left += changeAmount;
PInvoke.MoveWindow(panelConfig.PanelHandle, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height, false);
break;
case PanelConfigPropertyName.Top:
PInvoke.MoveWindow(panelConfig.PanelHandle, panelConfig.Left, panelConfig.Top + changeAmount, panelConfig.Width, panelConfig.Height, false);
panelConfig.Top += changeAmount;
PInvoke.MoveWindow(panelConfig.PanelHandle, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height, false);
break;
case PanelConfigPropertyName.Width:
PInvoke.MoveWindow(panelConfig.PanelHandle, panelConfig.Left, panelConfig.Top, panelConfig.Width + changeAmount, panelConfig.Height, false);
MSFSBugPanelShiftWorkaround(panelConfig.PanelHandle, orignalLeft, panelConfig.Top, panelConfig.Width + changeAmount, panelConfig.Height);
panelConfig.Width += changeAmount;
if (panelConfig.HideTitlebar)
WindowManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, false);
PInvoke.MoveWindow(panelConfig.PanelHandle, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height, false);
MSFSBugPanelShiftWorkaround(panelConfig.PanelHandle, orignalLeft, panelConfig.Top, panelConfig.Width, panelConfig.Height);
if (panelConfig.HideTitlebar)
WindowManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, true);
break;
case PanelConfigPropertyName.Height:
PInvoke.MoveWindow(panelConfig.PanelHandle, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height + changeAmount, false);
MSFSBugPanelShiftWorkaround(panelConfig.PanelHandle, orignalLeft, panelConfig.Top, panelConfig.Width, panelConfig.Height + changeAmount);
panelConfig.Height += changeAmount;
if (panelConfig.HideTitlebar)
WindowManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, false);
PInvoke.MoveWindow(panelConfig.PanelHandle, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height, false);
MSFSBugPanelShiftWorkaround(panelConfig.PanelHandle, orignalLeft, panelConfig.Top, panelConfig.Width, panelConfig.Height);
if (panelConfig.HideTitlebar)
WindowManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, true);
break;
default:
return;
@ -211,8 +221,17 @@ namespace MSFSPopoutPanelManager.Provider
panelConfig.Left = winRectangle.Left;
panelConfig.Top = winRectangle.Top;
panelConfig.Width = clientRectangle.Width + 16;
panelConfig.Height = clientRectangle.Height + 39;
if (panelConfig.HideTitlebar)
{
panelConfig.Width = clientRectangle.Width;
panelConfig.Height = clientRectangle.Height;
}
else
{
panelConfig.Width = clientRectangle.Width + 16;
panelConfig.Height = clientRectangle.Height + 39;
}
// Detect if window is maximized, if so, save settings
WINDOWPLACEMENT wp = new WINDOWPLACEMENT();

View file

@ -52,7 +52,7 @@ namespace MSFSPopoutPanelManager.Provider
var simualatorProcess = DiagnosticManager.GetSimulatorProcess();
if (simualatorProcess != null)
{
InputEmulationManager.LoadCustomViewZero(simualatorProcess.Handle);
InputEmulationManager.LoadCustomView(simualatorProcess.Handle, AppSetting.AutoPanningKeyBinding);
Thread.Sleep(500);
}
}

View file

@ -86,7 +86,7 @@ namespace MSFSPopoutPanelManager.Provider
var simualatorProcess = DiagnosticManager.GetSimulatorProcess();
if (simualatorProcess != null)
{
InputEmulationManager.SaveCustomViewZero(simualatorProcess.Handle);
InputEmulationManager.SaveCustomView(simualatorProcess.Handle, AppSetting.AutoPanningKeyBinding);
}
}

View file

@ -7,7 +7,7 @@
<RootNamespace>MSFSPopoutPanelManager.Provider</RootNamespace>
<PackageId>MSFS 2020 Popout Panel Manager Provider</PackageId>
<Product>MSFS 2020 Popout Panel Manager Provider</Product>
<Version>3.2.0</Version>
<Version>3.3.0.0</Version>
<Authors>Stanley Kwok</Authors>
<Copyright>Stanley Kwok 2021</Copyright>
<PackageProjectUrl>https://github.com/hawkeye-stan/msfs-popout-panel-manager</PackageProjectUrl>

View file

@ -17,6 +17,7 @@ namespace MSFSPopoutPanelManager.Provider
private System.Timers.Timer _requestDataTimer;
private SimConnectSystemEvent _lastSystemEvent;
private bool _isSimActive;
private bool _isPowerOnForPopOut;
public event EventHandler OnConnected;
@ -43,16 +44,19 @@ namespace MSFSPopoutPanelManager.Provider
_requestDataTimer.Elapsed += HandleMessageReceived;
};
_isSimActive = false;
_simConnector.Start();
}
public void Stop()
{
_isSimActive = false;
_simConnector.Stop();
}
public void Restart()
{
_isSimActive = false;
_simConnector.StopAndReconnect();
}
@ -115,17 +119,32 @@ namespace MSFSPopoutPanelManager.Provider
OnSimConnectDataRefreshed?.Invoke(this, new EventArgs<dynamic>(e.Value));
}
private void HandleReceiveSystemEvent(object sender, EventArgs<SimConnectSystemEvent> e)
private void HandleReceiveSystemEvent(object sender, EventArgs<Tuple<SimConnectSystemEvent, uint>> e)
{
var systemEvent = e.Value.Item1;
var dwData = e.Value.Item2;
Debug.WriteLine($"SimConnectSystemEvent Received: {systemEvent} dwData: {dwData}");
// to detect flight start at the "Ready to Fly" screen, it has a SIMSTART follows by a VIEW event
if(_lastSystemEvent == SimConnectSystemEvent.SIMSTART && e.Value == SimConnectSystemEvent.VIEW)
if (_lastSystemEvent == SimConnectSystemEvent.SIMSTART && systemEvent == SimConnectSystemEvent.VIEW && dwData == 4)
{
_isSimActive = true;
OnFlightStarted?.Invoke(this, null);
return;
}
if (e.Value == SimConnectSystemEvent.SIMSTOP)
// look for pair of events denoting sim ended after sim is active
if ((_isSimActive && _lastSystemEvent == SimConnectSystemEvent.SIMSTOP && systemEvent == SimConnectSystemEvent.VIEW && dwData == 4) ||
(_isSimActive && _lastSystemEvent == SimConnectSystemEvent.SIMSTOP && systemEvent == SimConnectSystemEvent.SIMSTART && dwData == 1))
{
_isSimActive = false;
_lastSystemEvent = SimConnectSystemEvent.NONE;
OnFlightStopped?.Invoke(this, null);
return;
}
Debug.WriteLine($"SimConnectSystemEvent Received: {e.Value.ToString()}");
_lastSystemEvent = e.Value;
_lastSystemEvent = systemEvent;
}
}
}

View file

@ -30,7 +30,7 @@ namespace MSFSPopoutPanelManager.Provider
var copiedProfile = matchedProfile.Copy<UserProfile>(); // Using Shared/ObjectExtensions.cs extension method
copiedProfile.IsDefaultProfile = false;
copiedProfile.BindingPlaneTitle = null;
copiedProfile.BindingPlaneTitle = new ObservableCollection<string>();
return AddProfile(copiedProfile, newProfileName);
}
@ -83,21 +83,22 @@ namespace MSFSPopoutPanelManager.Provider
public void AddProfileBinding(string planeTitle, int activeProfileId)
{
var bindedProfile = UserProfiles.FirstOrDefault(p => p.BindingPlaneTitle == planeTitle);
var bindedProfile = UserProfiles.FirstOrDefault(p => p.BindingPlaneTitle.ToList().Exists(p => p == planeTitle));
if (bindedProfile != null)
{
Logger.LogStatus($"Unable to add binding to the profile because '{planeTitle}' was already bound to profile '{bindedProfile.ProfileName}'.", StatusMessageType.Error);
return;
}
UserProfiles.First(p => p.ProfileId == activeProfileId).BindingPlaneTitle = planeTitle;
UserProfiles.First(p => p.ProfileId == activeProfileId).BindingPlaneTitle.Add(planeTitle);
WriteUserProfiles();
Logger.LogStatus($"Binding for the profile has been added successfully.", StatusMessageType.Info);
}
public void DeleteProfileBinding(int activeProfileId)
public void DeleteProfileBinding(string planeTitle, int activeProfileId)
{
UserProfiles.First(p => p.ProfileId == activeProfileId).BindingPlaneTitle = null;
UserProfiles.First(p => p.ProfileId == activeProfileId).BindingPlaneTitle.Remove(planeTitle);
WriteUserProfiles();
Logger.LogStatus($"Binding for the profile has been deleted successfully.", StatusMessageType.Info);
}
@ -111,7 +112,7 @@ namespace MSFSPopoutPanelManager.Provider
UserProfiles = new ObservableCollection<UserProfile>(JsonConvert.DeserializeObject<List<UserProfile>>(reader.ReadToEnd()));
}
}
catch
catch(Exception ex)
{
UserProfiles = new ObservableCollection<UserProfile>(new List<UserProfile>());
}

View file

@ -1,5 +1,4 @@
using MSFSPopoutPanelManager.Shared;
using System;
using System;
using System.Drawing;
using System.Runtime.InteropServices;
@ -45,7 +44,7 @@ namespace MSFSPopoutPanelManager.Provider
{
Rectangle rectangle;
PInvoke.GetClientRect(handle, out rectangle);
PInvoke.MoveWindow(handle, x, y, rectangle.Width, rectangle.Height, false);
PInvoke.MoveWindow(handle, x, y, rectangle.Width, rectangle.Height, true);
}
public static void MinimizeWindow(IntPtr handle)

View file

@ -154,6 +154,8 @@ The user plane profile data and application settings data are stored as JSON fil
* If you encounter application crashes or unknown error, please help my continuing development effort by attaching the file **error.log** in the application folder and open an issue ticket in github repo for this project. This is going to help me troubleshoot the issue and provide hotfixes.
* If you encounter an issue with panels that are not restored back to your saved profile locations, please check if you have other apps such as Sizer or Windows PowerToys that may have conflict with Pop Out Manager.
## Author
Stanley Kwok

View file

@ -9,7 +9,7 @@
<Company>Stanley Kwok</Company>
<Copyright>Stanley Kwok 2021</Copyright>
<PackageProjectUrl>https://github.com/hawkeye-stan/msfs-popout-panel-manager</PackageProjectUrl>
<Version>3.2.0</Version>
<Version>3.3.0.0</Version>
<Platforms>AnyCPU;x64</Platforms>
<UseWPF>true</UseWPF>
</PropertyGroup>

View file

@ -2,7 +2,7 @@
<hr/>
## Version 3.2.0 (Release)
## Version 3.2.0.1 (v3.2 Release)
* Added application auto update support. By installing this version of the app, auto update functionality will be available for all future versions of the application.
* Disabled panel adjustments when Hide Title Bar is checked for a panel. This is to fix an issue where panel adjustments (X-Pos, Y-Pos, Width, and Height) will behave erratically when Hide Title Bar is checked.
* Increased default delay for auto-clicking "Ready to Fly" button from 2 seconds to 4 seconds in regard to Auto Pop Out Panels feature . [Fixed Issue #9](https://github.com/hawkeye-stan/msfs-popout-panel-manager/issues/9)

View file

@ -2,6 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:mah="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:local="clr-namespace:MSFSPopoutPanelManager.WpfApp"
StartupUri="ApplicationWindow.xaml">
<Application.Resources>

View file

@ -124,7 +124,7 @@ namespace MSFSPopoutPanelManager.WpfApp
var process = DiagnosticManager.GetApplicationProcess();
PROCESS_DPI_AWARENESS outValue;
GetProcessDpiAwareness(process.Handle, out outValue);
log4net.Info($"DPI Awareness is set to: {outValue}");
//log4net.Info($"DPI Awareness is set to: {outValue}");
}
[DllImport("User32.dll")]

View file

@ -53,15 +53,7 @@
<MenuItem Header="File">
<MenuItem Name="menuItem_Restart" Header="Restart" Command="{Binding Path=RestartCommand}" IsEnabled="{Binding Path=IsShownPanelConfigurationScreen}"/>
<Separator />
<MenuItem Header="Preferences">
<MenuItem Name="menuItem_AlwaysOnTop" Header="Always on Top" IsChecked="{Binding Path=DataStore.AppSetting.AlwaysOnTop, Mode=TwoWay}" IsCheckable="True"/>
<MenuItem Name="menuItem_AutoPanning" Header="Auto Panning" IsChecked="{Binding Path=DataStore.AppSetting.UseAutoPanning, Mode=TwoWay}" IsCheckable="True"/>
<MenuItem Name="menuItem_AutoPopOutPanels" Header="Auto Pop Out Panels" IsChecked="{Binding Path=DataStore.AppSetting.AutoPopOutPanels, Mode=TwoWay}" IsCheckable="True"/>
<MenuItem Name="menuItem_AutoStart" Header="Auto Start" IsChecked="{Binding Path=DataStore.AppSetting.AutoStart, Mode=TwoWay}" IsCheckable="True"/>
<MenuItem Name="menuItem_IncludeBuiltInPanel" Header="Include Built-in Panels" IsChecked="{Binding Path=DataStore.AppSetting.IncludeBuiltInPanel, Mode=TwoWay}" IsCheckable="True"/>
<MenuItem Name="menuItem_MinimizeToTray" Header="Minimize to Tray" IsChecked="{Binding Path=DataStore.AppSetting.MinimizeToTray, Mode=TwoWay}" IsCheckable="True"/>
<MenuItem Name="menuItem_StartMinimized" Header="Start Minimized" IsChecked="{Binding Path=DataStore.AppSetting.StartMinimized, Mode=TwoWay}" IsCheckable="True"/>
</MenuItem>
<MenuItem Header="Preferences" Click="EditPreferences_Click" />
<Separator />
<MenuItem Name="menuItem_Exit" Header="Exit" Command="{Binding Path=ExitCommand}"/>
</MenuItem>

View file

@ -99,5 +99,15 @@ namespace MSFSPopoutPanelManager.WpfApp
{
_viewModel.ExitCommand.Execute(null);
}
private void EditPreferences_Click(object sender, RoutedEventArgs e)
{
PreferencesDialog dialog = new PreferencesDialog(_viewModel.DataStore.AppSetting);
dialog.Owner = Application.Current.MainWindow;
dialog.Topmost = true;
dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;
dialog.ShowDialog();
}
}
}

View file

@ -31,22 +31,32 @@ namespace MSFSPopoutPanelManager.WpfApp
this.Loaded += (sender, e) =>
{
var dialogHandle = new WindowInteropHelper(Window.GetWindow(this)).Handle;
var simulatorProcessHandle = DiagnosticManager.GetSimulatorProcess().Handle;
var winObj = Window.GetWindow(this);
Rectangle rectangle;
PInvoke.GetWindowRect(DiagnosticManager.GetSimulatorProcess().Handle, out rectangle);
Rectangle clientRectangle;
PInvoke.GetClientRect(DiagnosticManager.GetSimulatorProcess().Handle, out clientRectangle);
if (winObj != null)
{
var window = new WindowInteropHelper(winObj);
var x = Convert.ToInt32(rectangle.X + clientRectangle.Width / 2 - this.Width / 2);
var y = Convert.ToInt32(rectangle.Y + clientRectangle.Height / 2 - this.Height / 2);
if (window != null)
{
var dialogHandle = window.Handle;
var simulatorProcessHandle = DiagnosticManager.GetSimulatorProcess().Handle;
Debug.WriteLine($"Game Location: X:{rectangle.X} Y:{rectangle.Y}");
Debug.WriteLine($"Game Rectangle: Width:{clientRectangle.Width} Height:{clientRectangle.Height}");
Debug.WriteLine($"Message Dialog Location: X:{x} Y:{y}");
Rectangle rectangle;
PInvoke.GetWindowRect(DiagnosticManager.GetSimulatorProcess().Handle, out rectangle);
Rectangle clientRectangle;
PInvoke.GetClientRect(DiagnosticManager.GetSimulatorProcess().Handle, out clientRectangle);
PInvoke.MoveWindow(dialogHandle, x, y, Convert.ToInt32(this.Width), Convert.ToInt32(this.Height), false);
var x = Convert.ToInt32(rectangle.X + clientRectangle.Width / 2 - this.Width / 2);
var y = Convert.ToInt32(rectangle.Y + clientRectangle.Height / 2 - this.Height / 2);
Debug.WriteLine($"Game Location: X:{rectangle.X} Y:{rectangle.Y}");
Debug.WriteLine($"Game Rectangle: Width:{clientRectangle.Width} Height:{clientRectangle.Height}");
Debug.WriteLine($"Message Dialog Location: X:{x} Y:{y}");
PInvoke.MoveWindow(dialogHandle, x, y, Convert.ToInt32(this.Width), Convert.ToInt32(this.Height), false);
}
}
};
System.Timers.Timer timer = new System.Timers.Timer();

View file

@ -0,0 +1,101 @@
<mah:MetroWindow x:Class="MSFSPopoutPanelManager.WpfApp.PreferencesDialog"
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:mah="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:c="clr-namespace:CalcBinding;assembly=CalcBinding"
xmlns:local="clr-namespace:MSFSPopoutPanelManager.WpfApp"
mc:Ignorable="d"
Title="Preferences"
Height="450"
Width="800"
ResizeMode="NoResize"
Background="Transparent">
<Grid>
<DockPanel>
<TreeView Width="250" VerticalAlignment="Stretch" DockPanel.Dock="Left">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True" />
<Setter Property="Foreground" Value="#666666" />
<Setter Property="Background" Value="Transparent" />
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent"/>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="White"/>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey }" Color="Transparent"/>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey }" Color="White"/>
</Style.Resources>
</Style>
</TreeView.ItemContainerStyle>
<TreeViewItem Header="Application Settings" Selected="TreeViewItem_Selected" Margin="0,15,0,10" IsSelected="True"></TreeViewItem>
<TreeViewItem Header="Pop Out Settings" Selected="TreeViewItem_Selected" Margin="0,0,0,10"></TreeViewItem>
<TreeViewItem Header="Auto Pop Out Settings" Selected="TreeViewItem_Selected" Margin="0,0,0,10"></TreeViewItem>
</TreeView>
<WrapPanel Orientation="Vertical" DockPanel.Dock="Right" Margin="20,5,0,0" >
<WrapPanel Orientation="Vertical" Margin="0,10,0,0" Width="500" Visibility="{Binding Path=ApplicationSettingsVisibility, Mode=TwoWay}">
<CheckBox Margin="0,0,0,0" IsChecked="{Binding Path=AppSetting.AlwaysOnTop, Mode=TwoWay}" Content="Always on Top"></CheckBox>
<TextBlock Margin="24,5,0,10" Width="Auto" TextWrapping="Wrap" FontSize="14">Pin the application on top of all open windows.</TextBlock>
<CheckBox Margin="0,0,0,0" IsChecked="{Binding Path=AppSetting.AutoStart, Mode=TwoWay}" Content="Auto Start"></CheckBox>
<TextBlock Margin="24,5,0,10" Width="Auto" TextWrapping="Wrap" FontSize="14">Auto start the application when MSFS starts. This adds a XML config entry in EXE.xml file.</TextBlock>
<CheckBox IsChecked="{Binding Path=AppSetting.MinimizeToTray, Mode=TwoWay}" Content="Minimize to Tray"></CheckBox>
<TextBlock Margin="24,5,0,10" Width="Auto" TextWrapping="Wrap" FontSize="14">Minimize the application to system tray.</TextBlock>
<CheckBox IsChecked="{Binding Path=AppSetting.StartMinimized, Mode=TwoWay}" Content="Start Minimized"></CheckBox>
<TextBlock Margin="24,5,0,10" Width="Auto" TextWrapping="Wrap" FontSize="14">Start the application in minimized mode in system tray.</TextBlock>
</WrapPanel>
<WrapPanel Orientation="Vertical" Margin="0,10,0,0" Visibility="{Binding Path=PopOutSettingsVisibility, Mode=TwoWay}">
<CheckBox IsChecked="{Binding Path=AppSetting.UseAutoPanning, Mode=TwoWay}" Content="Auto Panning"></CheckBox>
<TextBlock Margin="24,5,0,10" Width="Auto" TextWrapping="Wrap" FontSize="14">Enable automatic panning of cockpit view when popping out panels. Auto Panning remembers the custom cockpit camera angle you used when defining the locations of pop out panel.</TextBlock>
<TextBlock Margin="24,10,0,0" Width="Auto" TextWrapping="Wrap" >Key Binding</TextBlock>
<TextBlock Margin="24,5,0,10" Width="Auto" TextWrapping="Wrap" FontSize="14">Configure key binding for saving and recalling of custom MSFS cockpit camera view when defining the locations of pop out panel. Default is Ctrl-Alt-0.</TextBlock>
<WrapPanel Orientation="Horizontal" Margin="20,10,0,0">
<Label Content="Ctrl-Alt-" FontSize="14"></Label>
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Center"
Width="60"
SelectedValuePath="Tag"
SelectedValue="{Binding Path=AppSetting.AutoPanningKeyBinding, Mode=TwoWay}" >
<ComboBoxItem Content="0" Tag="0"></ComboBoxItem>
<ComboBoxItem Content="1" Tag="1"></ComboBoxItem>
<ComboBoxItem Content="2" Tag="2"></ComboBoxItem>
<ComboBoxItem Content="3" Tag="3"></ComboBoxItem>
<ComboBoxItem Content="4" Tag="4"></ComboBoxItem>
<ComboBoxItem Content="5" Tag="5"></ComboBoxItem>
<ComboBoxItem Content="6" Tag="6"></ComboBoxItem>
<ComboBoxItem Content="7" Tag="7"></ComboBoxItem>
<ComboBoxItem Content="8" Tag="8"></ComboBoxItem>
<ComboBoxItem Content="9" Tag="9"></ComboBoxItem>
</ComboBox>
</WrapPanel>
<CheckBox Margin="0,15,0,0" IsChecked="{Binding Path=AppSetting.IncludeBuiltInPanel, Mode=TwoWay}" Content="Include Built-in Panels"></CheckBox>
<TextBlock Margin="24,5,0,10" Width="Auto" TextWrapping="Wrap" FontSize="14">Enable saving and recalling of MSFS built-in panels (ie. ATC, VFR Map, Checklist, etc) as part of profile definition.</TextBlock>
</WrapPanel>
<WrapPanel Orientation="Vertical" Margin="0,10,0,0" Visibility="{Binding Path=AutoPopOutSettingsVisibility, Mode=TwoWay}">
<CheckBox IsChecked="{Binding Path=AppSetting.AutoPopOutPanels, Mode=TwoWay}" Content="Auto Pop Out Panels"></CheckBox>
<TextBlock Margin="24,10,0,10" Width="Auto" TextWrapping="Wrap" >Wait delay for each step during auto pop out process (in seconds)</TextBlock>
<WrapPanel Orientation="Horizontal" Margin="24,0,0,0" >
<mah:NumericUpDown Width="80" Minimum="1" Maximum="30" FontSize="16" Height="32" Value="{Binding Path=AppSetting.AutoPopOutPanelsWaitDelay.ReadyToFlyButton, Mode=TwoWay}"></mah:NumericUpDown>
<Label Margin="10,0,0,0">Ready to Fly</Label>
</WrapPanel>
<TextBlock Margin="119,0,10,10" Width="Auto" TextWrapping="Wrap" FontSize="14">Amount of time to wait for 'Ready to Fly' button to appear before simulating mouse click.<LineBreak/>(Default: 6 seconds)</TextBlock>
<WrapPanel Orientation="Horizontal" Margin="24,0,0,0" >
<mah:NumericUpDown Width="80" Minimum="1" Maximum="30" FontSize="16" Height="32" Value="{Binding Path=AppSetting.AutoPopOutPanelsWaitDelay.InitialCockpitView, Mode=TwoWay}"></mah:NumericUpDown>
<Label Margin="10,0,0,0" >Initial Cockpit View</Label>
</WrapPanel>
<TextBlock Margin="119,0,10,10" Width="Auto" TextWrapping="Wrap" FontSize="14">Amount of time to wait for the cockpit to appear before beginning pop out panel process.<LineBreak/>(Default: 2 seconds)</TextBlock>
<WrapPanel Orientation="Horizontal" Margin="24,0,0,00" >
<mah:NumericUpDown Width="80" Minimum="1" Maximum="30" FontSize="16" Height="32" Value="{Binding Path=AppSetting.AutoPopOutPanelsWaitDelay.InstrumentationPowerOn, Mode=TwoWay}"></mah:NumericUpDown>
<Label Margin="10,0,0,0">Instrumentation Power On</Label>
</WrapPanel>
<TextBlock Margin="119,0,10,10" Width="Auto" TextWrapping="Wrap" FontSize="14">Amount of time to wait for cold start instrumentation power on to complete before beginning pop out panel process.<LineBreak/>(Default: 2 seconds)</TextBlock>
<WrapPanel Orientation="Horizontal"></WrapPanel>
</WrapPanel>
<WrapPanel Orientation="Vertical" Visibility="Visible">
</WrapPanel>
</WrapPanel>
</DockPanel>
</Grid>
</mah:MetroWindow>

View file

@ -0,0 +1,63 @@
using MahApps.Metro.Controls;
using MSFSPopoutPanelManager.Model;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace MSFSPopoutPanelManager.WpfApp
{
/// <summary>
/// Interaction logic for AddProfileDialog.xaml
/// </summary>
public partial class PreferencesDialog : MetroWindow, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public PreferencesDialog(AppSetting appSetting)
{
InitializeComponent();
AppSetting = appSetting;
this.DataContext = this;
ApplicationSettingsVisibility = Visibility.Visible;
PopOutSettingsVisibility = Visibility.Collapsed;
AutoPopOutSettingsVisibility = Visibility.Collapsed;
}
public AppSetting AppSetting { get; set; }
public Visibility ApplicationSettingsVisibility { get; set; }
public Visibility PopOutSettingsVisibility { get; set; }
public Visibility AutoPopOutSettingsVisibility { get; set; }
private void TreeViewItem_Selected(object sender, RoutedEventArgs e)
{
var treeViewItem = (TreeViewItem)e.Source;
ApplicationSettingsVisibility = Visibility.Collapsed;
PopOutSettingsVisibility = Visibility.Collapsed;
AutoPopOutSettingsVisibility = Visibility.Collapsed;
if (treeViewItem.Header.ToString() == "Application Settings")
{
ApplicationSettingsVisibility = Visibility.Visible;
}
else if(treeViewItem.Header.ToString() == "Pop Out Settings")
{
PopOutSettingsVisibility = Visibility.Visible;
}
else if (treeViewItem.Header.ToString() == "Auto Pop Out Settings")
{
AutoPopOutSettingsVisibility = Visibility.Visible;
}
}
}
}

View file

@ -121,7 +121,7 @@
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Name="Left" Width="100" BorderThickness="0" Text="{Binding Path=Left, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=LostFocus}"
SourceUpdated="GridData_SourceUpdated" Style="{StaticResource TextBoxColumnFocus}" IsReadOnly="{c:Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid, AncestorLevel=1}, Path='DataContext.DataStore.ActiveUserProfile.IsLocked or !DataContext.DataStore.AllowEdit'}" IsEnabled="{c:Binding Path='!HideTitlebar', Mode=TwoWay}" />
SourceUpdated="GridData_SourceUpdated" Style="{StaticResource TextBoxColumnFocus}" IsReadOnly="{c:Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid, AncestorLevel=1}, Path='DataContext.DataStore.ActiveUserProfile.IsLocked or !DataContext.DataStore.AllowEdit'}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
@ -129,7 +129,7 @@
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Name="Top" Width="100" BorderThickness="0" Text="{Binding Path=Top, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=LostFocus}"
SourceUpdated="GridData_SourceUpdated" Style="{StaticResource TextBoxColumnFocus}" IsReadOnly="{c:Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid, AncestorLevel=1}, Path='DataContext.DataStore.ActiveUserProfile.IsLocked or !DataContext.DataStore.AllowEdit'}" IsEnabled="{c:Binding Path='!HideTitlebar', Mode=TwoWay}" />
SourceUpdated="GridData_SourceUpdated" Style="{StaticResource TextBoxColumnFocus}" IsReadOnly="{c:Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid, AncestorLevel=1}, Path='DataContext.DataStore.ActiveUserProfile.IsLocked or !DataContext.DataStore.AllowEdit'}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
@ -137,7 +137,7 @@
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Name="Width" Width="100" BorderThickness="0" Text="{Binding Path=Width, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=LostFocus}"
SourceUpdated="GridData_SourceUpdated" Style="{StaticResource TextBoxColumnFocus}" IsReadOnly="{c:Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid, AncestorLevel=1}, Path='DataContext.DataStore.ActiveUserProfile.IsLocked or !DataContext.DataStore.AllowEdit'}" IsEnabled="{c:Binding Path='!HideTitlebar', Mode=TwoWay}" />
SourceUpdated="GridData_SourceUpdated" Style="{StaticResource TextBoxColumnFocus}" IsReadOnly="{c:Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid, AncestorLevel=1}, Path='DataContext.DataStore.ActiveUserProfile.IsLocked or !DataContext.DataStore.AllowEdit'}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
@ -145,7 +145,7 @@
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Name="Height" Width="100" BorderThickness="0" Text="{Binding Path=Height, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=LostFocus}"
SourceUpdated="GridData_SourceUpdated" Style="{StaticResource TextBoxColumnFocus}" IsReadOnly="{c:Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid, AncestorLevel=1}, Path='DataContext.DataStore.ActiveUserProfile.IsLocked or !DataContext.DataStore.AllowEdit'}" IsEnabled="{c:Binding Path='!HideTitlebar', Mode=TwoWay}" />
SourceUpdated="GridData_SourceUpdated" Style="{StaticResource TextBoxColumnFocus}" IsReadOnly="{c:Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid, AncestorLevel=1}, Path='DataContext.DataStore.ActiveUserProfile.IsLocked or !DataContext.DataStore.AllowEdit'}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
@ -157,7 +157,7 @@
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Hide Titlebar" Width="100">
<DataGridTemplateColumn Header="Hide Title Bar" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="HideTitlebar" Width="100" Margin="40 0 0 0" IsChecked="{Binding Path=HideTitlebar, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"

View file

@ -27,23 +27,18 @@
</Style.Triggers>
</Style>
<Style x:Key="ProfileAddPlaneBindingDependency" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Content" Value="Add Binding"/>
<Setter Property="Width" Value="130"/>
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{c:Binding Path='DataStore.HasActiveUserProfileId and DataStore.HasCurrentMsfsPlaneTitle and DataStore.ActiveUserProfile.BindingPlaneTitle != DataStore.CurrentMsfsPlaneTitle', Mode=OneWay}" Value="True">
<DataTrigger Binding="{c:Binding Path='DataStore.HasActiveUserProfileId and DataStore.HasCurrentMsfsPlaneTitle and DataStore.IsAllowedAddAircraftBinding', Mode=OneWay}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{c:Binding Path='DataStore.ActiveUserProfile.HasBindingPlaneTitle and DataStore.ActiveUserProfile.BindingPlaneTitle != DataStore.CurrentMsfsPlaneTitle', Mode=OneWay}" Value="True">
<Setter Property="Content" Value="Replace Binding"/>
<Setter Property="Width" Value="130"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="ProfileDeletePlaneBindingDependency" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{c:Binding Path='DataStore.HasActiveUserProfileId and DataStore.ActiveUserProfile.HasBindingPlaneTitle', Mode=OneWay}" Value="True">
<DataTrigger Binding="{c:Binding Path='DataStore.HasActiveUserProfileId and DataStore.ActiveUserProfile.HasBindingPlaneTitle and DataStore.IsAllowedDeleteAircraftBinding', Mode=OneWay}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
@ -53,42 +48,71 @@
<DockPanel>
<WrapPanel DockPanel.Dock="Left" Orientation="Vertical" Margin="15,10,0,0" Width="585" HorizontalAlignment="Left">
<Label Content="1. Please select a profile you would like to use." HorizontalAlignment="Left" />
<ComboBox HorizontalAlignment="Left"
Margin="20,0,0,0"
VerticalAlignment="Top"
Width="485"
<WrapPanel Orientation="Horizontal" Margin="20,5,0,0" HorizontalAlignment="Left">
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Center"
Width="450"
ItemsSource="{Binding Source={StaticResource UserProfilesViewSource}}"
SelectedValue="{Binding Path=DataStore.ActiveUserProfileId}"
DisplayMemberPath="ProfileName"
SelectedValuePath="ProfileId"/>
<WrapPanel Orientation="Horizontal" Margin="20,10,0,0" HorizontalAlignment="Left">
<Button Content="Add Profile" HorizontalAlignment="Left" Width="130" Click="AddProfile_Click"/>
<Button Content="Delete Profile" HorizontalAlignment="Left" Margin="20,0,0,0" Width="130" Click="DeleteProfile_Click" Style="{StaticResource ProfileSelectedDependency}"/>
<Button Content="Set Default" HorizontalAlignment="Left" Margin="20,0,0,0" Width="130" Command="{Binding Path=SetDefaultProfileCommand}" Style="{StaticResource ProfileSelectedDependency}"/>
<Button Content="+" ToolTip="Add Profile" HorizontalAlignment="Left" Margin="10,0,0,0" Width="40" Click="AddProfile_Click" />
<Button Content="-" ToolTip="Delete Profile" HorizontalAlignment="Left" Margin="10,0,0,0" Width="40" Click="DeleteProfile_Click" Style="{StaticResource ProfileSelectedDependency}"/>
<!--<Button Content="D" ToolTip="Set Default Profile" HorizontalAlignment="Left" Margin="10,0,0,0" Width="40" Command="{Binding Path=SetDefaultProfileCommand}" Style="{StaticResource ProfileSelectedDependency}"/>-->
</WrapPanel>
<WrapPanel Orientation="Horizontal" Margin="15,10,0,0" HorizontalAlignment="Left">
<Label Content="Binding:" HorizontalAlignment="Left"/>
<Label Content="{c:Binding Path='(DataStore.ActiveUserProfile.HasBindingPlaneTitle ? DataStore.ActiveUserProfile.BindingPlaneTitle : &quot;None&quot;)'}" HorizontalContentAlignment="Left" HorizontalAlignment="Left" FontStyle="Italic" Foreground="LightGreen" />
</WrapPanel>
<WrapPanel Orientation="Horizontal" Margin="20,5,0,0" HorizontalAlignment="Left">
<Button HorizontalAlignment="Left" Click="AddBinding_Click" Style="{StaticResource ProfileAddPlaneBindingDependency}"/>
<Button Content="Delete Binding" HorizontalAlignment="Left" Margin="20,0,0,0" Width="130" Click="DeleteBinding_Click" Style="{StaticResource ProfileDeletePlaneBindingDependency}"/>
<CheckBox Margin="40,0,0,0" IsChecked="{Binding Path=DataStore.ActiveUserProfile.PowerOnRequiredForColdStart}" IsEnabled="{Binding Path=DataStore.HasActiveUserProfileId}" Command="{Binding Path=SetPowerOnRequiredCommand}">
<TextBlock Text="Power on required to pop out panels on cold start" TextWrapping="Wrap" Width="200" Margin="5,0,0,3"/>
<Separator Margin="5,10,5,10"/>
<Label Content="2. Optional: Bind active aircraft livery to profile to automatically pop out panels." HorizontalAlignment="Left" Margin="0,0,0,0" />
<WrapPanel Orientation="Vertical" Margin="15,0,0,0" HorizontalAlignment="Left">
<WrapPanel Orientation="Horizontal" Margin="5,5,0,0" HorizontalAlignment="Left">
<Label Content="{c:Binding Path='DataStore.CurrentMsfsPlaneTitle == null ? &quot;Active aircraft livery is unavailable&quot; : DataStore.CurrentMsfsPlaneTitle'}" HorizontalContentAlignment="Left" HorizontalAlignment="Left" FontStyle="Italic" Width="450">
<Label.Style>
<Style TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{c:Binding Path='DataStore.IsAircraftBindedToProfile'}" Value="True">
<Setter Property="Foreground" Value="LightGreen" ></Setter>
</DataTrigger>
<DataTrigger Binding="{c:Binding Path='DataStore.IsAircraftBindedToProfile'}" Value="False">
<Setter Property="Foreground" Value="AntiqueWhite" ></Setter>
</DataTrigger>
<DataTrigger Binding="{c:Binding Path='!DataStore.IsAllowedAddAircraftBinding and !DataStore.IsAllowedDeleteAircraftBinding'}" Value="True">
<Setter Property="Foreground" Value="Red" ></Setter>
<Setter Property="ToolTip" Value="Aircraft Livery is currently binded to another profile"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
<Button Content="+" ToolTip="Add Bidning" Margin="10,0,0,0" Width="40" Click="AddBinding_Click" Style="{StaticResource ProfileAddPlaneBindingDependency}"/>
<Button Content="-" ToolTip="Delete Binding" Margin="10,0,0,0" Width="40" Click="DeleteBinding_Click" Style="{StaticResource ProfileDeletePlaneBindingDependency}"/>
</WrapPanel>
<CheckBox Margin="10,5,0,0" IsChecked="{Binding Path=DataStore.ActiveUserProfile.PowerOnRequiredForColdStart}" IsEnabled="{Binding Path=DataStore.HasActiveUserProfileId}" Command="{Binding Path=SetPowerOnRequiredCommand}">
<TextBlock Text="Power on required to pop out panels on cold start" TextWrapping="Wrap" Margin="5,0,0,3"/>
</CheckBox>
</WrapPanel>
<Label Content="2. Identify pop out panel locations in the game by clicking on them." Margin="0,15,0,0" />
<Separator Margin="5,10,5,10"/>
<Label Content="3. Identify pop out panel locations in the game by clicking on them." Margin="0,0,0,0" />
<WrapPanel Orientation="Vertical" Margin="20,0,0,0" HorizontalAlignment="Left">
<Label Content="LEFT CLICK to add a new panel."/>
<Label Content="CTRL + LEFT CLICK when all panels have been selected or to cancel selections." />
<Label Content="SHIFT + LEFT CLICK to remove the most recently added panel."/>
<WrapPanel Orientation="Horizontal" Margin="0,10,0,0" HorizontalAlignment="Left">
<WrapPanel Orientation="Horizontal">
<Label Content="LEFT CLICK" Foreground="AntiqueWhite"/>
<Label Content="to add a new panel."/>
</WrapPanel>
<WrapPanel Orientation="Horizontal">
<Label Content="CTRL + LEFT CLICK" Foreground="AntiqueWhite" />
<Label Content="when all panels have been selected or to cancel selections." />
</WrapPanel>
<WrapPanel Orientation="Horizontal">
<Label Content="SHIFT + LEFT CLICK" Foreground="AntiqueWhite" />
<Label Content="to remove the most recently added panel."/>
</WrapPanel>
<WrapPanel Orientation="Horizontal" Margin="0,5,0,0" HorizontalAlignment="Left">
<Button Content="Start Panel Selection" HorizontalAlignment="Left" Margin="0,0,0,0" Width="165" Click="StartPanelSelection_Click" Style="{StaticResource ProfileSelectedDependency}"/>
<Button Content="Save Auto Panning Camera" HorizontalAlignment="Left" Margin="20,0,0,0" IsEnabled="{c:Binding Path='DataStore.HasActiveUserProfileId and DataStore.IsFlightActive'}" Width="215" Click="SaveAutoPanningCamera_Click" Style="{StaticResource ProfileSelectedDependency}"/>
</WrapPanel>
</WrapPanel>
<Label Content="3. Start the pop out process for selected panels." Margin="0,15,0,0" />
<Button Content="Start Pop Out" HorizontalAlignment="Left" Margin="20,10,0,0" Width="130" IsEnabled="{c:Binding Path='DataStore.HasActiveUserProfileId and DataStore.IsFlightActive'}" Command="{Binding Path=StartPopOutCommand}" Style="{StaticResource ProfileSelectedDependency}"/>
<Separator Margin="5,10,5,10"/>
<Label Content="4. Start the pop out process for selected panels." Margin="0,0,0,0" />
<Button Content="Start Pop Out" HorizontalAlignment="Left" Margin="20,5,0,0" Width="130" IsEnabled="{c:Binding Path='DataStore.HasActiveUserProfileId and DataStore.IsFlightActive'}" Command="{Binding Path=StartPopOutCommand}" Style="{StaticResource ProfileSelectedDependency}"/>
</WrapPanel>
<DockPanel DockPanel.Dock="Right" Width="325" HorizontalAlignment="Center">
<Label DockPanel.Dock="Top" Content="Panel Locations" HorizontalAlignment="Center" Margin="0,10,0,0"/>

View file

@ -88,7 +88,7 @@ namespace MSFSPopoutPanelManager.WpfApp
private void AddBinding_Click(object sender, RoutedEventArgs e)
{
ConfirmationDialog dialog = new ConfirmationDialog("Confirm Add Binding", $"Are you sure you want to bind the selected profile to the following plane? \n{_panelSelectionViewModel.DataStore.CurrentMsfsPlaneTitle}");
ConfirmationDialog dialog = new ConfirmationDialog("Confirm Add Binding", $"Are you sure you want to bind the aircraft livery below to the active profile? \n{_panelSelectionViewModel.DataStore.CurrentMsfsPlaneTitle}");
dialog.Owner = Application.Current.MainWindow;
dialog.Topmost = true;
dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;
@ -101,7 +101,7 @@ namespace MSFSPopoutPanelManager.WpfApp
private void DeleteBinding_Click(object sender, RoutedEventArgs e)
{
ConfirmationDialog dialog = new ConfirmationDialog("Confirm Delete Binding", $"Are you sure you want to delete the following binding for the selected profile? \n{_panelSelectionViewModel.DataStore.ActiveUserProfile.BindingPlaneTitle}");
ConfirmationDialog dialog = new ConfirmationDialog("Confirm Delete Binding", $"Are you sure you want to delete aircraft livery binding below from the active profile? \n{_panelSelectionViewModel.DataStore.CurrentMsfsPlaneTitle}");
dialog.Owner = Application.Current.MainWindow;
dialog.Topmost = true;
dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;

View file

@ -63,7 +63,13 @@ namespace MSFSPopoutPanelManager.WpfApp.ViewModel
_simConnectManager = new SimConnectManager();
_simConnectManager.OnSimConnectDataRefreshed += (sender, e) =>
{
DataStore.CurrentMsfsPlaneTitle = e.Value.Title;
// Automatic switching of active profile when SimConnect active aircraft livery changes
if(DataStore.CurrentMsfsPlaneTitle != e.Value.Title)
{
DataStore.CurrentMsfsPlaneTitle = e.Value.Title;
AutoSwitchProfile(e.Value.Title);
}
DataStore.ElectricalMasterBatteryStatus = e.Value.ElectricalMasterBattery;
};
_simConnectManager.OnConnected += (sender, e) => { DataStore.IsSimulatorStarted = true; };
@ -242,41 +248,43 @@ namespace MSFSPopoutPanelManager.WpfApp.ViewModel
{
Debug.WriteLine("Flight Started");
DataStore.IsEnteredFlight = true;
ShowPanelSelection(true);
// find the profile with the matching binding plane title
var profile = DataStore.UserProfiles.FirstOrDefault(p => p.BindingPlaneTitle == DataStore.CurrentMsfsPlaneTitle);
if (profile != null)
Application.Current.Dispatcher.Invoke(() =>
{
Thread.Sleep(DataStore.AppSetting.AutoPopOutPanelsWaitDelay.ReadyToFlyButton * 1000); // Wait for the ready to fly button
AutoSwitchProfile(DataStore.CurrentMsfsPlaneTitle);
DataStore.IsEnteredFlight = true;
ShowPanelSelection(true);
// find the profile with the matching binding plane title
var profile = DataStore.UserProfiles.FirstOrDefault(p => p.BindingPlaneTitle.ToList().Exists(p => p == DataStore.CurrentMsfsPlaneTitle));
if (profile == null || profile.PanelSourceCoordinates.Count == 0)
return;
var messageDialog = new OnScreenMessageDialog($"Automatic pop out is starting for profile:\n{profile.ProfileName}", DataStore.AppSetting.AutoPopOutPanelsWaitDelay.ReadyToFlyButton); // Wait for the ready to fly button
messageDialog.ShowDialog();
InputEmulationManager.LeftClickReadyToFly();
Application.Current.Dispatcher.Invoke(() =>
{
var messageDialog = new OnScreenMessageDialog($"Panel pop out in progress for profile:\n{profile.ProfileName}", DataStore.AppSetting.AutoPopOutPanelsWaitDelay.InitialCockpitView);
messageDialog.ShowDialog();
Thread.Sleep(DataStore.AppSetting.AutoPopOutPanelsWaitDelay.InitialCockpitView * 1000); // Wait for the initial cockpit view
// Turn on power if required to pop out panels
_simConnectManager.TurnOnPower(profile.PowerOnRequiredForColdStart);
Thread.Sleep(DataStore.AppSetting.AutoPopOutPanelsWaitDelay.InstrumentationPowerOn * 1000); // Wait for battery to be turned on
// Turn on power if required to pop out panels
_simConnectManager.TurnOnPower(profile.PowerOnRequiredForColdStart);
Thread.Sleep(DataStore.AppSetting.AutoPopOutPanelsWaitDelay.InstrumentationPowerOn * 1000); // Wait for battery to be turned on
DataStore.ActiveUserProfileId = profile.ProfileId;
_panelPopoutManager.UserProfile = profile;
_panelPopoutManager.AppSetting = DataStore.AppSetting;
_panelPopoutManager.StartPopout();
DataStore.ActiveUserProfileId = profile.ProfileId;
_panelPopoutManager.UserProfile = profile;
_panelPopoutManager.AppSetting = DataStore.AppSetting;
_panelPopoutManager.StartPopout();
// Turn off power if needed after pop out
_simConnectManager.TurnOffpower();
});
}
// Turn off power if needed after pop out
_simConnectManager.TurnOffpower();
});
}
private void HandleOnFlightStopped(object sender, EventArgs e)
{
DataStore.IsEnteredFlight = false;
OnRestart(null);
}
private void CheckForAutoUpdate()
@ -285,10 +293,21 @@ namespace MSFSPopoutPanelManager.WpfApp.ViewModel
AutoUpdater.PersistenceProvider = new JsonFilePersistenceProvider(jsonPath);
AutoUpdater.Synchronous = true;
AutoUpdater.AppTitle = "MSFS Pop Out Panel Manager";
AutoUpdater.RunUpdateAsAdmin = false;
//AutoUpdater.RunUpdateAsAdmin = false;
AutoUpdater.UpdateFormSize = new System.Drawing.Size(930, 675);
//AutoUpdater.Start("https://raw.githubusercontent.com/hawkeye-stan/msfs-popout-panel-manager/master/autoupdate.xml");
AutoUpdater.Start("https://raw.githubusercontent.com/hawkeye-stan/AutoUpdateTest/main/autoupdate.xml");
AutoUpdater.Start("https://raw.githubusercontent.com/hawkeye-stan/msfs-popout-panel-manager/master/autoupdate.xml");
//AutoUpdater.Start("https://raw.githubusercontent.com/hawkeye-stan/AutoUpdateTest/main/autoupdate.xml");
}
private void AutoSwitchProfile(string activeAircraftTitle)
{
// Automatic switching of active profile when SimConnect active aircraft livery changes
if (DataStore.UserProfiles != null)
{
var matchedProfile = DataStore.UserProfiles.ToList().Find(p => p.BindingPlaneTitle.ToList().Exists(t => t == activeAircraftTitle));
if (matchedProfile != null)
DataStore.ActiveUserProfileId = matchedProfile.ProfileId;
}
}
}
}

View file

@ -117,6 +117,47 @@ namespace MSFSPopoutPanelManager.WpfApp.ViewModel
get { return !String.IsNullOrEmpty(CurrentMsfsPlaneTitle); }
}
public bool IsAircraftBindedToProfile
{
get
{
if (ActiveUserProfile == null)
return false;
return ActiveUserProfile.BindingPlaneTitle.ToList().Exists(p => p == CurrentMsfsPlaneTitle);
}
}
public bool IsAllowedDeleteAircraftBinding
{
get
{
if (ActiveUserProfile == null || !HasCurrentMsfsPlaneTitle)
return false;
var uProfile = UserProfiles.ToList().Find(u => u.BindingPlaneTitle.ToList().Exists(p => p == CurrentMsfsPlaneTitle));
if (uProfile != null && uProfile.ProfileId != ActiveUserProfileId)
return false;
return ActiveUserProfile.BindingPlaneTitle.ToList().Exists(p => p == CurrentMsfsPlaneTitle);
}
}
public bool IsAllowedAddAircraftBinding
{
get
{
if (ActiveUserProfile == null || !HasCurrentMsfsPlaneTitle)
return false;
var uProfile = UserProfiles.ToList().Find(u => u.BindingPlaneTitle.ToList().Exists(p => p == CurrentMsfsPlaneTitle));
if (uProfile != null && uProfile.ProfileId != ActiveUserProfileId)
return false;
return !ActiveUserProfile.BindingPlaneTitle.ToList().Exists(p => p == CurrentMsfsPlaneTitle);
}
}
public bool ElectricalMasterBatteryStatus { get; set; }
public bool IsSimulatorStarted { get; set; }

View file

@ -97,12 +97,24 @@ namespace MSFSPopoutPanelManager.WpfApp.ViewModel
private void OnAddProfileBinding(object commandParameter)
{
var index = DataStore.ActiveUserProfileId;
_userProfileManager.AddProfileBinding(DataStore.CurrentMsfsPlaneTitle, DataStore.ActiveUserProfileId);
// force profile refresh
DataStore.ActiveUserProfileId = -1;
DataStore.ActiveUserProfileId = index;
}
private void OnDeleteProfileBinding(object commandParameter)
{
_userProfileManager.DeleteProfileBinding(DataStore.ActiveUserProfileId);
var index = DataStore.ActiveUserProfileId;
_userProfileManager.DeleteProfileBinding(DataStore.CurrentMsfsPlaneTitle, DataStore.ActiveUserProfileId);
// force profile refresh
DataStore.ActiveUserProfileId = -1;
DataStore.ActiveUserProfileId = index;
}
private void OnStartPanelSelection(object commandParameter)
@ -150,7 +162,7 @@ namespace MSFSPopoutPanelManager.WpfApp.ViewModel
var simualatorProcess = DiagnosticManager.GetSimulatorProcess();
if (simualatorProcess != null && DataStore.IsFlightActive)
{
InputEmulationManager.SaveCustomViewZero(simualatorProcess.Handle);
InputEmulationManager.SaveCustomView(simualatorProcess.Handle, DataStore.AppSetting.AutoPanningKeyBinding);
Logger.LogStatus("Auto Panning Camera has been saved succesfully.", StatusMessageType.Info);
}
}

View file

@ -4,7 +4,7 @@
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<Version>3.2.0.0</Version>
<Version>3.3.0.0</Version>
<PackageId>MSFS 2020 Popout Panel Manager</PackageId>
<Authors>Stanley Kwok</Authors>
<Product>MSFS 2020 Popout Panel Manager</Product>
@ -75,12 +75,19 @@
</ItemGroup>
<ItemGroup>
<Compile Update="PreferencesDialog.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="UserControlPanelConfiguration.xaml.cs">
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Page Update="PreferencesDialog.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<SubType>Designer</SubType>
</Page>
<Page Update="UserControlPanelConfiguration.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<SubType>Designer</SubType>

View file

@ -1,17 +1,12 @@
- Added per monitor DPI-awareness support. The application should run and display correctly when using combination of mixed monitor (with high-DPI and low-DPI) resolutions and scaling.
Version 3.2.0.1 (v3.2 Release)
- Added system tray icon access. Application can start minimize or minimize to system tray. System tray icon features a context menu to allow quick access to application functions.
- Added application auto update support. By installing this version of the app, auto update
functionality will be available for all future versions of the application.
- Added user requested feature to provide keyboard shortcut (Ctrl-Alt-P) to start panel pop out with either an active profile or a default profile selected.
- Disabled panel adjustments when Hide Title Bar is checked for a panel. This is to fix an
issue where panel adjustments (X-Pos, Y-Pos, Width, and Height) will behave erratically when
Hide Title Bar is checked.
- New copy profile feature. You can reuse your defined panel settings for another plane or plane/livery combination.
- Added quick panel location selection adjustment feature. You can now adjust panel locations without redoing the entire profile.
- Added Save Auto Panning Camera Angle function if you need to adjust the in-game camera angle during panel selection.
- Added application auto update feature.
- New logo icon for the app.
- New dark theme for the entire UI.
- Increased default delay for auto-clicking "Ready to Fly" button from 2 seconds to 4 seconds
in regard to Auto Pop Out Panels feature.
[Fixed Issue #9] (https://github.com/hawkeye-stan/msfs-popout-panel-manager/issues/9