1
0
Fork 0
mirror of https://github.com/hawkeye-stan/msfs-popout-panel-manager.git synced 2024-11-21 21:30:12 +00:00

Version 3.2 Beta

This commit is contained in:
hawkeye 2022-01-27 08:40:04 -05:00
parent 3be805e49a
commit 5aa1e5c64d
125 changed files with 4364 additions and 6126 deletions

View file

@ -0,0 +1,16 @@
namespace MSFSPopoutPanelManager.FsConnector
{
public enum ActionEvent
{
KEY_MASTER_BATTERY_SET,
KEY_ALTERNATOR_ON,
KEY_ALTERNATOR_OFF,
KEY_ALTERNATOR_SET,
KEY_AVIONICS_MASTER_SET,
KEY_AVIONICS_MASTER_1_ON,
KEY_AVIONICS_MASTER_2_ON,
KEY_AVIONICS_MASTER_1_OFF,
KEY_AVIONICS_MASTER_2_OFF
}
}

View file

@ -0,0 +1,20 @@
using Microsoft.FlightSimulator.SimConnect;
using System;
using System.Collections.Generic;
namespace MSFSPopoutPanelManager.FsConnector
{
public class DataDefinition
{
public static List<(string PropName, string SimConnectName, string SimConnectUnit, SIMCONNECT_DATATYPE SimConnectDataType, Type ObjectType)> GetDefinition()
{
var def = new List<(string, string, string, SIMCONNECT_DATATYPE, Type)>
{
("Title", "Title", null, SIMCONNECT_DATATYPE.STRING256, typeof(string)),
("ElectricalMasterBattery", "ELECTRICAL MASTER BATTERY", "Bool", SIMCONNECT_DATATYPE.FLOAT64, typeof(bool))
};
return def;
}
}
}

28
FsConnector/Enums.cs Normal file
View file

@ -0,0 +1,28 @@

namespace MSFSPopoutPanelManager.FsConnector
{
public enum SimConnectDefinition
{
SimConnectDataStruct
}
public enum NotificationGroup
{
GROUP0,
}
public enum DataRequest
{
REQUEST_1
}
public enum SimConnectSystemEvent
{
FOURSECS,
SIMSTART,
SIMSTOP,
FLIGHTLOADED,
VIEW,
PAUSED
};
}

View file

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0-windows</TargetFramework>
<AssemblyName>FsConnector</AssemblyName>
<PackageId>MSFS 2020 Popout Panel Manager FsConnector</PackageId>
<Product>MSFS 2020 Popout Panel Manager FsConnector</Product>
<Version>3.2.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>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.FlightSimulator.SimConnect">
<HintPath>Resources\Managed\Microsoft.FlightSimulator.SimConnect.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="resources\SimConnect.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>SimConnect.dll</TargetPath>
</ContentWithTargetPath>
</ItemGroup>
</Project>

Binary file not shown.

View file

@ -0,0 +1,24 @@

using System.Runtime.InteropServices;
namespace MSFSPopoutPanelManager.FsConnector
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class SimConnectStruct
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x100)]
public string Prop01;
public double Prop02;
public double Prop03;
public double Prop04;
public double Prop05;
public double Prop06;
public double Prop07;
public double Prop08;
public double Prop09;
public double Prop10;
// Add more as DataDefinition grows
}
}

194
FsConnector/SimConnector.cs Normal file
View file

@ -0,0 +1,194 @@
using Microsoft.FlightSimulator.SimConnect;
using MSFSPopoutPanelManager.Shared;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic;
using System.Runtime.InteropServices;
namespace MSFSPopoutPanelManager.FsConnector
{
public class SimConnector
{
private const int MSFS_CONNECTION_RETRY_TIMEOUT = 1000; // timeout to retry connection to MSFS via Simconnect in milliseconds
private const int WM_USER_SIMCONNECT = 0x402;
private SimConnect _simConnect;
private System.Timers.Timer _timer;
public event EventHandler<EventArgs<dynamic>> OnReceivedData;
public event EventHandler OnConnected;
public event EventHandler OnDisconnected;
public event EventHandler<EventArgs<SimConnectSystemEvent>> OnReceiveSystemEvent;
public dynamic SimData { get; set; }
public void Start()
{
_timer = new System.Timers.Timer();
_timer.Interval = MSFS_CONNECTION_RETRY_TIMEOUT;
_timer.Enabled = true;
_timer.Elapsed += (source, e) =>
{
try
{
if (_simConnect == null)
{
_simConnect = new SimConnect("MSFS Pop Out Panel Manager", Process.GetCurrentProcess().MainWindowHandle, WM_USER_SIMCONNECT, null, 0);
_simConnect.OnRecvQuit += HandleOnRecvQuit;
_simConnect.OnRecvException += HandleOnRecvException;
_simConnect.OnRecvSimobjectDataBytype += HandleOnRecvSimobjectDataBytype;
_simConnect.OnRecvEvent += HandleOnReceiveEvent;
_simConnect.SubscribeToSystemEvent(SimConnectSystemEvent.SIMSTART, "SimStart");
_simConnect.SubscribeToSystemEvent(SimConnectSystemEvent.SIMSTOP, "SimStop");
_simConnect.SubscribeToSystemEvent(SimConnectSystemEvent.VIEW, "View");
// Setup SimConnect data structure definition using SimConnectStruct and SimConnect data definitions
var definitions = DataDefinition.GetDefinition();
foreach (var (PropName, SimConnectName, SimConnectUnit, SimConnectDataType, ObjectType) in definitions)
_simConnect.AddToDataDefinition(SimConnectDefinition.SimConnectDataStruct, SimConnectName, SimConnectUnit, SimConnectDataType, 0.0f, SimConnect.SIMCONNECT_UNUSED);
_simConnect.RegisterDataDefineStruct<SimConnectStruct>(SimConnectDefinition.SimConnectDataStruct);
// Setup SimEvent mapping
foreach (var item in Enum.GetValues(typeof(ActionEvent)))
{
if (item.ToString().StartsWith("KEY_"))
_simConnect.MapClientEventToSimEvent((ActionEvent)item, item.ToString()[4..]);
}
_timer.Enabled = false;
System.Threading.Thread.Sleep(2000);
Debug.WriteLine("SimConnect is connected");
OnConnected?.Invoke(this, null);
}
}
catch (COMException)
{
// handle SimConnect instantiation error when MSFS is not connected
}
};
}
public void Stop()
{
_timer.Enabled = false;
_simConnect = null;
}
public void StopAndReconnect()
{
_simConnect = null;
_timer.Enabled = true;
}
public void RequestData()
{
if (_simConnect != null)
try
{
_simConnect.RequestDataOnSimObjectType(DataRequest.REQUEST_1, SimConnectDefinition.SimConnectDataStruct, 0, SIMCONNECT_SIMOBJECT_TYPE.USER);
}
catch (Exception ex)
{
if (ex.Message != "0xC00000B0")
{
Debug.WriteLine($"SimConnect request data exception: {ex.Message}");
StopAndReconnect();
OnDisconnected?.Invoke(this, null);
}
}
}
public void ReceiveMessage()
{
if (_simConnect != null)
try
{
_simConnect.ReceiveMessage();
}
catch (Exception ex)
{
if (ex.Message != "0xC00000B0")
Debug.WriteLine($"SimConnect receive message exception: {ex.Message}");
}
}
public void TransmitActionEvent(ActionEvent eventID, uint data)
{
if (_simConnect != null)
{
try
{
_simConnect.TransmitClientEvent(0U, eventID, data, NotificationGroup.GROUP0, SIMCONNECT_EVENT_FLAG.GROUPID_IS_PRIORITY);
}
catch (Exception ex)
{
var eventName = eventID.ToString()[4..]; // trim out KEY_ prefix
Debug.WriteLine($"SimConnect transmit event exception: EventName: {eventName} - {ex.Message}");
}
}
}
private void HandleOnRecvQuit(SimConnect sender, SIMCONNECT_RECV data)
{
OnDisconnected?.Invoke(this, null);
// Try to reconnect again
_timer.Enabled = true;
}
private void HandleOnRecvException(SimConnect sender, SIMCONNECT_RECV_EXCEPTION data)
{
var exception = (SIMCONNECT_EXCEPTION)data.dwException;
if (exception != SIMCONNECT_EXCEPTION.NAME_UNRECOGNIZED && exception != SIMCONNECT_EXCEPTION.EVENT_ID_DUPLICATE)
{
Debug.WriteLine($"MSFS Error - {exception}");
}
}
private void HandleOnRecvSimobjectDataBytype(SimConnect sender, SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE data)
{
if (data.dwRequestID == 0)
{
try
{
var simConnectStruct = (SimConnectStruct)data.dwData[0];
var simConnectStructFields = typeof(SimConnectStruct).GetFields();
var simData = new ExpandoObject();
var definition = DataDefinition.GetDefinition();
int i = 0;
foreach (var item in definition)
{
simData.TryAdd(item.PropName, Convert.ChangeType(simConnectStructFields[i++].GetValue(simConnectStruct), item.ObjectType));
}
SimData = simData;
OnReceivedData?.Invoke(this, new EventArgs<dynamic>(simData));
}
catch (Exception ex)
{
Debug.WriteLine($"SimConnect receive data exception: {ex.Message}");
}
}
}
private void HandleOnReceiveEvent(SimConnect sender, SIMCONNECT_RECV_EVENT data)
{
var systemEvent = ((SimConnectSystemEvent)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));
}
}
}

View file

@ -1,87 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<Platforms>x64;AnyCPU</Platforms>
<Version>3.1.0</Version>
<AssemblyName>MSFSPopoutPanelManager</AssemblyName>
<RootNamespace>MSFSPopoutPanelManager</RootNamespace>
<ApplicationIcon>WindowManager.ico</ApplicationIcon>
<Authors>Stanley Kwok</Authors>
<Product>MSFS 2020 Popout Panel Manager</Product>
<PackageId>MSFS 2020 Popout Panel Manager</PackageId>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyVersion>3.1.0.0</AssemblyVersion>
<FileVersion>3.1.0.0</FileVersion>
<WeaverConfiguration>
<Weavers>
<PropertyChanged />
</Weavers>
</WeaverConfiguration>
<Copyright>Stanley Kwok 2021</Copyright>
<PackageProjectUrl>https://github.com/hawkeye-stan/msfs-popout-panel-manager</PackageProjectUrl>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Optimize>false</Optimize>
<DebugType>embedded</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<None Remove="log4net.config" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="DarkUI" Version="2.0.2" />
<PackageReference Include="Fody" Version="6.6.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="log4net" Version="2.0.13" />
<PackageReference Include="MouseKeyHook" Version="5.6.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
</ItemGroup>
<ItemGroup>
<Content Include="LICENSE">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="log4net.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="README.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="VERSION.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Update="images\transparent.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Vesion.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Compile Update="UI\ConfirmDialogForm.cs">
<SubType>Form</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\" />
</ItemGroup>
</Project>

View file

@ -1,9 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31919.166
# Visual Studio Version 16
VisualStudioVersion = 16.0.32002.261
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSFSPopoutPanelManager", "MSFSPopoutPanelManager.csproj", "{1E89B7B3-DBD9-4644-A0EB-26924317DD83}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfApp", "WpfApp\WpfApp.csproj", "{54712A0A-B344-45E4-85C4-0A913305A0E6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Model", "Model\Model.csproj", "{4A778C1A-3782-4312-842D-33AA58A9D6D4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "Shared\Shared.csproj", "{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Provider", "Provider\Provider.csproj", "{933E7D03-883D-4970-9AD7-7A2B5D6C3671}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FsConnector", "FsConnector\FsConnector.csproj", "{023426F4-9FD2-4198-B9F4-83F0B55B88FC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{15FC98CD-0A69-437B-A5E5-67D025DB5CDC}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
LICENSE = LICENSE
README.md = README.md
VERSION.md = VERSION.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -11,10 +27,26 @@ Global
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1E89B7B3-DBD9-4644-A0EB-26924317DD83}.Debug|x64.ActiveCfg = Debug|x64
{1E89B7B3-DBD9-4644-A0EB-26924317DD83}.Debug|x64.Build.0 = Debug|x64
{1E89B7B3-DBD9-4644-A0EB-26924317DD83}.Release|x64.ActiveCfg = Release|x64
{1E89B7B3-DBD9-4644-A0EB-26924317DD83}.Release|x64.Build.0 = Release|x64
{54712A0A-B344-45E4-85C4-0A913305A0E6}.Debug|x64.ActiveCfg = Debug|x64
{54712A0A-B344-45E4-85C4-0A913305A0E6}.Debug|x64.Build.0 = Debug|x64
{54712A0A-B344-45E4-85C4-0A913305A0E6}.Release|x64.ActiveCfg = Release|x64
{54712A0A-B344-45E4-85C4-0A913305A0E6}.Release|x64.Build.0 = Release|x64
{4A778C1A-3782-4312-842D-33AA58A9D6D4}.Debug|x64.ActiveCfg = Debug|x64
{4A778C1A-3782-4312-842D-33AA58A9D6D4}.Debug|x64.Build.0 = Debug|x64
{4A778C1A-3782-4312-842D-33AA58A9D6D4}.Release|x64.ActiveCfg = Release|x64
{4A778C1A-3782-4312-842D-33AA58A9D6D4}.Release|x64.Build.0 = Release|x64
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Debug|x64.ActiveCfg = Debug|x64
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Debug|x64.Build.0 = Debug|x64
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Release|x64.ActiveCfg = Release|x64
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Release|x64.Build.0 = Release|x64
{933E7D03-883D-4970-9AD7-7A2B5D6C3671}.Debug|x64.ActiveCfg = Debug|x64
{933E7D03-883D-4970-9AD7-7A2B5D6C3671}.Debug|x64.Build.0 = Debug|x64
{933E7D03-883D-4970-9AD7-7A2B5D6C3671}.Release|x64.ActiveCfg = Release|x64
{933E7D03-883D-4970-9AD7-7A2B5D6C3671}.Release|x64.Build.0 = Release|x64
{023426F4-9FD2-4198-B9F4-83F0B55B88FC}.Debug|x64.ActiveCfg = Debug|x64
{023426F4-9FD2-4198-B9F4-83F0B55B88FC}.Debug|x64.Build.0 = Debug|x64
{023426F4-9FD2-4198-B9F4-83F0B55B88FC}.Release|x64.ActiveCfg = Release|x64
{023426F4-9FD2-4198-B9F4-83F0B55B88FC}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -5,9 +5,9 @@ using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace MSFSPopoutPanelManager.Shared
namespace MSFSPopoutPanelManager.Model
{
public class Autostart
public class AppAutoStart
{
public static void Activate()
{

155
Model/AppSetting.cs Normal file
View file

@ -0,0 +1,155 @@
using MSFSPopoutPanelManager.Shared;
using Newtonsoft.Json;
using System;
using System.ComponentModel;
using System.IO;
namespace MSFSPopoutPanelManager.Model
{
public class AppSetting : INotifyPropertyChanged
{
private const string APP_SETTING_DATA_FILENAME = "appsettingdata.json";
private bool _saveEnabled;
// Using PropertyChanged.Fody
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler<EventArgs<bool>> AlwaysOnTopChanged;
public event EventHandler<EventArgs<bool>> AutoPopOutPanelsChanged;
public AppSetting()
{
// Set defaults
MinimizeToTray = false;
AlwaysOnTop = true;
UseAutoPanning = true;
StartMinimized = false;
IncludeBuiltInPanel = false;
AutoPopOutPanels = false;
AutoPopOutPanelsWaitDelay = new AutoPopOutPanelsWaitDelay();
}
public void Load()
{
var appSetting = ReadAppSetting();
this.MinimizeToTray = appSetting.MinimizeToTray;
this.AlwaysOnTop = appSetting.AlwaysOnTop;
this.UseAutoPanning = appSetting.UseAutoPanning;
this.StartMinimized = appSetting.StartMinimized;
this.IncludeBuiltInPanel = appSetting.IncludeBuiltInPanel;
this.AutoPopOutPanels = appSetting.AutoPopOutPanels;
this.AutoPopOutPanelsWaitDelay = appSetting.AutoPopOutPanelsWaitDelay;
_saveEnabled = true;
}
public void OnPropertyChanged(string propertyName, object before, object after)
{
// Automatic save data
if (_saveEnabled && propertyName != "AutoStart" && before != after)
WriteAppSetting(this);
switch (propertyName)
{
case "AlwaysOnTop":
AlwaysOnTopChanged?.Invoke(this, new EventArgs<bool>((bool)after));
break;
case "AutoPopOutPanels":
AutoPopOutPanelsChanged?.Invoke(this, new EventArgs<bool>((bool)after));
break;
}
}
//[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 bool StartMinimized { get; set; }
public bool IncludeBuiltInPanel { get; set; }
public bool AutoPopOutPanels { get; set; }
public AutoPopOutPanelsWaitDelay AutoPopOutPanelsWaitDelay { get; set; }
[JsonIgnore]
public bool AutoStart
{
get
{
return AppAutoStart.CheckIsAutoStart();
}
set
{
if (value)
AppAutoStart.Activate();
else
AppAutoStart.Deactivate();
}
}
public AppSetting ReadAppSetting()
{
try
{
using (StreamReader reader = new StreamReader(Path.Combine(FileIo.GetUserDataFilePath(), APP_SETTING_DATA_FILENAME)))
{
return JsonConvert.DeserializeObject<AppSetting>(reader.ReadToEnd());
}
}
catch (Exception ex)
{
// if file does not exist, write default data
var appSetting = new AppSetting();
WriteAppSetting(appSetting);
return appSetting;
}
}
public void WriteAppSetting(AppSetting appSetting)
{
try
{
var userProfilePath = FileIo.GetUserDataFilePath();
if (!Directory.Exists(userProfilePath))
Directory.CreateDirectory(userProfilePath);
using (StreamWriter file = File.CreateText(Path.Combine(userProfilePath, APP_SETTING_DATA_FILENAME)))
{
JsonSerializer serializer = new JsonSerializer();
serializer.Serialize(file, appSetting);
}
}
catch
{
Logger.LogStatus($"Unable to write app setting data file: {APP_SETTING_DATA_FILENAME}", StatusMessageType.Error);
}
}
}
public class AutoPopOutPanelsWaitDelay
{
public AutoPopOutPanelsWaitDelay()
{
ReadyToFlyButton = 2;
InitialCockpitView = 2;
InstrumentationPowerOn = 2;
}
public int ReadyToFlyButton { get; set; }
public int InitialCockpitView { get; set; }
public int InstrumentationPowerOn { get; set; }
}
}

3
Model/FodyWeavers.xml Normal file
View file

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<PropertyChanged />
</Weavers>

74
Model/FodyWeavers.xsd Normal file
View file

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="PropertyChanged" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="InjectOnPropertyNameChanged" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if the On_PropertyName_Changed feature is enabled.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="TriggerDependentProperties" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if the Dependent properties feature is enabled.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="EnableIsChangedProperty" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if the IsChanged property feature is enabled.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="EventInvokerNames" type="xs:string">
<xs:annotation>
<xs:documentation>Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="CheckForEquality" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="CheckForEqualityUsingBaseEquals" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if equality checks should use the Equals method resolved from the base class.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="UseStaticEqualsFromBase" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if equality checks should use the static Equals method resolved from the base class.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="SuppressWarnings" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to turn off build warnings from this weaver.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="SuppressOnPropertyNameChangedWarning" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to turn off build warnings about mismatched On_PropertyName_Changed methods.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

30
Model/Model.csproj Normal file
View file

@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0-windows</TargetFramework>
<RootNamespace>MSFSPopoutPanelManager.Model</RootNamespace>
<AssemblyName>Model</AssemblyName>
<PackageId>MSFS 2020 Popout Panel Manager Model</PackageId>
<Version>3.2.0</Version>
<Authors>Stanley Kwok</Authors>
<Company>Stanley Kwok</Company>
<Copyright>Stanley Kwok 2021</Copyright>
<Product>MSFS 2020 Popout Panel Manager Model</Product>
<PackageProjectUrl>https://github.com/hawkeye-stan/msfs-popout-panel-manager</PackageProjectUrl>
<Platforms>x64;AnyCPU</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Fody" Version="6.6.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
</Project>

35
Model/PanelConfig.cs Normal file
View file

@ -0,0 +1,35 @@
using Newtonsoft.Json;
using System;
using System.ComponentModel;
namespace MSFSPopoutPanelManager.Model
{
public class PanelConfig : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int PanelIndex { get; set; }
public string PanelName { get; set; }
public PanelType PanelType { get; set; }
public int Top { get; set; }
public int Left { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public bool AlwaysOnTop { get; set; }
public bool HideTitlebar { get; set; }
[JsonIgnore]
public bool IsCustomPopout { get { return PanelType == PanelType.CustomPopout; } }
[JsonIgnore]
public IntPtr PanelHandle { get; set; }
}
}

View file

@ -0,0 +1,23 @@
using System;
namespace MSFSPopoutPanelManager.Model
{
public enum PanelConfigPropertyName
{
PanelName,
Left,
Top,
Width,
Height,
AlwaysOnTop,
HideTitlebar,
Invalid
}
public class PanelConfigItem
{
public int PanelIndex { get; set; }
public PanelConfigPropertyName PanelConfigProperty { get; set; }
}
}

View file

@ -0,0 +1,20 @@
using Newtonsoft.Json;
using System;
using System.ComponentModel;
namespace MSFSPopoutPanelManager.Model
{
public class PanelSourceCoordinate : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int PanelIndex { get; set; }
public int X { get; set; }
public int Y { get; set; }
[JsonIgnore]
public IntPtr PanelHandle { get; set; }
}
}

View file

@ -1,4 +1,4 @@
namespace MSFSPopoutPanelManager.Shared
namespace MSFSPopoutPanelManager.Model
{
public enum PanelType
{

50
Model/UserProfile.cs Normal file
View file

@ -0,0 +1,50 @@
using Newtonsoft.Json;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace MSFSPopoutPanelManager.Model
{
public class UserProfile : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public UserProfile()
{
PanelSourceCoordinates = new ObservableCollection<PanelSourceCoordinate>();
PanelConfigs = new ObservableCollection<PanelConfig>();
IsLocked = false;
}
public int ProfileId { get; set; }
public string ProfileName { get; set; }
public bool IsDefaultProfile { get; set; }
public string BindingPlaneTitle { get; set; }
public bool IsLocked { get; set; }
public ObservableCollection<PanelSourceCoordinate> PanelSourceCoordinates;
public ObservableCollection<PanelConfig> PanelConfigs { get; set; }
public bool PowerOnRequiredForColdStart { get; set; }
public void Reset()
{
PanelSourceCoordinates.Clear();
PanelConfigs.Clear();
IsLocked = false;
}
[JsonIgnore]
public bool IsActive { get; set; }
[JsonIgnore]
public bool HasBindingPlaneTitle
{
get { return !string.IsNullOrEmpty(BindingPlaneTitle); }
}
}
}

View file

@ -1,83 +0,0 @@
using log4net;
using log4net.Config;
using MSFSPopoutPanelManager.Shared;
using MSFSPopoutPanelManager.UI;
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager
{
static class Program
{
private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
bool createNew;
using var mutex = new Mutex(true, typeof(Program).Namespace, out createNew);
var logRepository = LogManager.GetRepository(Assembly.GetEntryAssembly());
XmlConfigurator.Configure(logRepository, new FileInfo("log4net.config"));
if (createNew)
{
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.ThreadException += new ThreadExceptionEventHandler(HandleThreadException);
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledDomainException);
Application.Run(new StartupForm());
}
else
{
var current = Process.GetCurrentProcess();
foreach (var process in Process.GetProcessesByName(current.ProcessName))
{
if (process.Id == current.Id) continue;
PInvoke.SetForegroundWindow(process.MainWindowHandle);
break;
}
}
void HandleThreadException(object sender, ThreadExceptionEventArgs e)
{
Log.Error(e.Exception.Message, e.Exception);
ShowExceptionForm();
}
void UnhandledDomainException(object sender, UnhandledExceptionEventArgs e)
{
var exception = (Exception)e.ExceptionObject;
Log.Error(exception.Message, exception);
ShowExceptionForm();
}
void ShowExceptionForm()
{
var title = "Critical Error";
var message = "Application has encountered a critical error and will be closed. Please see the file error.log for information.";
using (var form = new ConfirmDialogForm(title, message, false) { StartPosition = FormStartPosition.CenterParent })
{
var dialogResult = form.ShowDialog();
if (dialogResult == DialogResult.OK)
{
Application.Exit();
}
}
}
}
}
}

View file

@ -0,0 +1,72 @@
using MSFSPopoutPanelManager.Shared;
using System;
using System.Diagnostics;
using System.Timers;
namespace MSFSPopoutPanelManager.Provider
{
public class DiagnosticManager
{
public static event EventHandler<EventArgs<bool>> OnPollMsfsConnectionResult;
public static string GetApplicationVersion()
{
var systemAssemblyVersion = System.Reflection.Assembly.GetEntryAssembly().GetName().Version;
var appVersion = $"{systemAssemblyVersion.Major}.{systemAssemblyVersion.Minor}.{systemAssemblyVersion.Build}";
if (systemAssemblyVersion.Revision > 0)
appVersion += "." + systemAssemblyVersion.Revision;
return appVersion;
}
public static WindowProcess GetSimulatorProcess()
{
return GetProcess("FlightSimulator");
}
public static WindowProcess GetApplicationProcess()
{
return GetProcess("MSFSPopoutPanelManager");
}
public static void StartPollingMsfsConnection()
{
Timer timer = new Timer();
timer.Interval = 2000;
timer.Elapsed += (sender, e) =>
{
OnPollMsfsConnectionResult?.Invoke(null, new EventArgs<bool>(GetSimulatorProcess() != null));
};
timer.Enabled = true;
}
public static void OpenOnlineUserGuide()
{
Process.Start(new ProcessStartInfo("https://github.com/hawkeye-stan/msfs-popout-panel-manager#msfs-pop-out-panel-manager") { UseShellExecute = true });
}
public static void OpenOnlineLatestDownload()
{
Process.Start(new ProcessStartInfo("https://github.com/hawkeye-stan/msfs-popout-panel-manager/releases") { UseShellExecute = true });
}
private static WindowProcess GetProcess(string processName)
{
foreach (var process in Process.GetProcesses())
{
if (process.ProcessName == processName)
{
return new WindowProcess()
{
ProcessId = process.Id,
ProcessName = process.ProcessName,
Handle = process.MainWindowHandle
};
}
}
return null;
}
}
}

View file

@ -1,93 +0,0 @@
using MSFSPopoutPanelManager.Shared;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager.Provider
{
public class FileManager
{
private static string UserProfileDataPath;
private const string APP_SETTING_DATA_FILENAME = "appsettingdata.json";
private const string USER_PROFILE_DATA_FILENAME = "userprofiledata.json";
static FileManager()
{
FileManager.UserProfileDataPath = Path.Combine(Application.StartupPath, "userdata");
}
public static List<UserProfileData> ReadUserProfileData()
{
try
{
using (StreamReader reader = new StreamReader(Path.Combine(UserProfileDataPath, USER_PROFILE_DATA_FILENAME)))
{
return JsonConvert.DeserializeObject<List<UserProfileData>>(reader.ReadToEnd());
}
}
catch
{
return new List<UserProfileData>();
}
}
public static void WriteUserProfileData(List<UserProfileData> userProfileData)
{
try
{
if (!Directory.Exists(UserProfileDataPath))
Directory.CreateDirectory(UserProfileDataPath);
using (StreamWriter file = File.CreateText(Path.Combine(UserProfileDataPath, USER_PROFILE_DATA_FILENAME)))
{
JsonSerializer serializer = new JsonSerializer();
serializer.Serialize(file, userProfileData);
}
}
catch
{
Logger.BackgroundStatus($"Unable to write user data file: {USER_PROFILE_DATA_FILENAME}", StatusMessageType.Error);
}
}
public static AppSettingData ReadAppSettingData()
{
try
{
using (StreamReader reader = new StreamReader(Path.Combine(UserProfileDataPath, APP_SETTING_DATA_FILENAME)))
{
return JsonConvert.DeserializeObject<AppSettingData>(reader.ReadToEnd());
}
}
catch (Exception ex)
{
// if file does not exist, write default data
var appSettings = new AppSettingData();
WriteAppSettingData(appSettings);
return appSettings;
}
}
public static void WriteAppSettingData(AppSettingData appSettingData)
{
try
{
if (!Directory.Exists(UserProfileDataPath))
Directory.CreateDirectory(UserProfileDataPath);
using (StreamWriter file = File.CreateText(Path.Combine(UserProfileDataPath, APP_SETTING_DATA_FILENAME)))
{
JsonSerializer serializer = new JsonSerializer();
serializer.Serialize(file, appSettingData);
}
}
catch
{
Logger.BackgroundStatus($"Unable to write app setting data file: {USER_PROFILE_DATA_FILENAME}", StatusMessageType.Error);
}
}
}
}

View file

@ -3,7 +3,7 @@ using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
namespace MSFSPopoutPanelManager.Shared
namespace MSFSPopoutPanelManager.Provider
{
public class ImageOperation
{

View file

@ -1,5 +1,6 @@
using MSFSPopoutPanelManager.Shared;
using System;
using System;
using System.Diagnostics;
using System.Drawing;
using System.Threading;
namespace MSFSPopoutPanelManager.Provider
@ -17,14 +18,6 @@ namespace MSFSPopoutPanelManager.Provider
const uint VK_SPACE = 0x20;
const uint KEY_0 = 0x30;
public static void SendMouseToLocation(IntPtr hwnd, int x, int y)
{
// Move the cursor to the flight simulator screen then move the cursor into position
//PInvoke.SetCursorPos(0, 0);
PInvoke.SetFocus(hwnd);
PInvoke.SetCursorPos(x, y);
}
public static void LeftClick(int x, int y)
{
PInvoke.SetCursorPos(x, y);
@ -47,13 +40,10 @@ namespace MSFSPopoutPanelManager.Provider
PInvoke.keybd_event(Convert.ToByte(VK_RMENU), 0, KEYEVENTF_KEYUP, 0);
}
public static void CenterView(IntPtr hwnd)
public static void CenterView(IntPtr hwnd, int x, int y)
{
PInvoke.SetForegroundWindow(hwnd);
Thread.Sleep(500);
PInvoke.SetFocus(hwnd);
Thread.Sleep(300);
LeftClick(x, y);
// First center view using Ctrl-Space
PInvoke.keybd_event(Convert.ToByte(VK_LCONTROL), 0, KEYEVENTF_KEYDOWN, 0);
@ -80,7 +70,6 @@ namespace MSFSPopoutPanelManager.Provider
PInvoke.keybd_event(Convert.ToByte(KEY_0), 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)
@ -106,5 +95,48 @@ namespace MSFSPopoutPanelManager.Provider
PInvoke.keybd_event(Convert.ToByte(KEY_0), 0, KEYEVENTF_KEYUP, 0);
PInvoke.keybd_event(Convert.ToByte(VK_LMENU), 0, KEYEVENTF_KEYUP, 0);
}
public static void LeftClickReadyToFly()
{
var simualatorProcess = DiagnosticManager.GetSimulatorProcess();
if (simualatorProcess != null)
{
var hwnd = simualatorProcess.Handle;
PInvoke.SetForegroundWindow(hwnd);
Thread.Sleep(500);
Rectangle rectangle;
PInvoke.GetWindowRect(hwnd, out rectangle);
Rectangle clientRectangle;
PInvoke.GetClientRect(hwnd, out clientRectangle);
// For windows mode
// The "Ready to Fly" button is at about 93% width, 91.3% height at the lower right corner of game window
var x = Convert.ToInt32(rectangle.X + (clientRectangle.Width + 8) * 0.93); // with 8 pixel adjustment
var y = Convert.ToInt32(rectangle.Y + (clientRectangle.Height + 39) * 0.915); // with 39 pixel adjustment
LeftClick(x, y); // set focus to game app
Thread.Sleep(250);
LeftClick(x, y);
Thread.Sleep(250);
Debug.WriteLine($"Windows Mode 'Ready to Fly' button coordinate: {x}, {y}");
// For full screen mode
x = Convert.ToInt32(rectangle.X + (clientRectangle.Width) * 0.93);
y = Convert.ToInt32(rectangle.Y + (clientRectangle.Height) * 0.915);
LeftClick(x, y); // set focus to game app
Thread.Sleep(250);
LeftClick(x, y);
Thread.Sleep(250);
Debug.WriteLine($"Full Screen Mode 'Ready to Fly' button coordinate: {x} , {y}");
}
}
}
}

View file

@ -0,0 +1,73 @@
using Gma.System.MouseKeyHook;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager.Provider
{
public class InputHookManager
{
private const string CTRL_KEY = "Control";
private const string SHIFT_KEY = "Shift";
private static IKeyboardMouseEvents _mouseHook;
public static bool SubscribeToPanelSelectionEvent { get; set; }
public static bool SubscribeToStartPopOutEvent { get; set; }
public static event EventHandler OnPanelSelectionCompleted;
public static event EventHandler<Point> OnPanelSelectionAdded;
public static event EventHandler OnPanelSelectionRemoved;
public static event EventHandler OnStartPopout;
public static void StartHook()
{
if (_mouseHook == null)
{
_mouseHook = Hook.GlobalEvents();
_mouseHook.OnCombination(new Dictionary<Combination, Action>
{
{Combination.FromString("Control+Alt+P"), () => { if(SubscribeToStartPopOutEvent) OnStartPopout?.Invoke(null, null); }}
});
_mouseHook.MouseDownExt += HandleMouseHookMouseDownExt;
}
}
public static void EndHook()
{
if (_mouseHook == null)
{
_mouseHook.MouseDownExt -= HandleMouseHookMouseDownExt;
_mouseHook.Dispose();
}
}
private static void HandleMouseHookMouseDownExt(object sender, MouseEventExtArgs e)
{
if (_mouseHook == null || !SubscribeToPanelSelectionEvent)
return;
if (e.Button == MouseButtons.Left)
{
var ctrlPressed = Control.ModifierKeys.ToString() == CTRL_KEY;
var shiftPressed = Control.ModifierKeys.ToString() == SHIFT_KEY;
if (ctrlPressed)
{
OnPanelSelectionCompleted?.Invoke(null, null);
}
else if (shiftPressed)
{
OnPanelSelectionRemoved?.Invoke(null, null);
}
else if (!shiftPressed)
{
OnPanelSelectionAdded?.Invoke(null, new Point(e.X, e.Y));
}
}
}
}
}

View file

@ -3,13 +3,15 @@ using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
namespace MSFSPopoutPanelManager.Shared
namespace MSFSPopoutPanelManager.Provider
{
public static class PInvokeConstant
{
public const int SW_SHOWNORMAL = 1;
public const int SW_SHOWMINIMIZED = 2;
public const int SW_SHOWMAXIMIZED = 3;
public const int SW_SHOW = 5;
public const int SW_SHOWDEFAULT = 10;
public const int SW_NORMAL = 1;
public const int SW_MINIMIZE = 6;
public const int SW_RESTORE = 9;
@ -33,12 +35,15 @@ namespace MSFSPopoutPanelManager.Shared
public const uint WM_CLOSE = 0x0010;
public const int WINEVENT_OUTOFCONTEXT = 0;
}
public class PInvoke
{
[DllImport("user32")]
public static extern int EnumWindows(CallBack x, int y);
[DllImport("user32")]
private static extern bool EnumChildWindows(IntPtr window, CallBack callback, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetClassName(IntPtr hWnd, StringBuilder strPtrClassName, Int32 nMaxCount);
@ -97,11 +102,14 @@ namespace MSFSPopoutPanelManager.Shared
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool ShowWindowAsync(HandleRef hWnd, int nCmdShow);
[DllImport("USER32.dll")]
public static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong);
[DllImport("user32.dll")]
public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int y, int cx, int cy, uint wFlags);
public static extern IntPtr SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint wFlags);
[DllImport("user32.dll")]
public static extern bool SetWindowText(System.IntPtr hwnd, System.String lpString);

View file

@ -0,0 +1,222 @@
using MSFSPopoutPanelManager.Model;
using System;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
namespace MSFSPopoutPanelManager.Provider
{
public class PanelConfigurationManager
{
private UserProfileManager _userProfileManager;
private IntPtr _winEventHook;
private static PInvoke.WinEventProc _winEvent; // keep this as static to prevent garbage collect or the app will crash
private Rectangle _lastWindowRectangle;
public UserProfile UserProfile { get; set; }
public bool AllowEdit { get; set; }
public PanelConfigurationManager(UserProfileManager userProfileManager)
{
_userProfileManager = userProfileManager;
_winEvent = new PInvoke.WinEventProc(EventCallback);
AllowEdit = true;
}
public void HookWinEvent()
{
// Setup panel config event hooks
_winEventHook = PInvoke.SetWinEventHook(PInvokeConstant.EVENT_SYSTEM_MOVESIZEEND, PInvokeConstant.EVENT_OBJECT_LOCATIONCHANGE, DiagnosticManager.GetApplicationProcess().Handle, _winEvent, 0, 0, PInvokeConstant.WINEVENT_OUTOFCONTEXT);
}
public void UnhookWinEvent()
{
// Unhook all Win API events
PInvoke.UnhookWinEvent(_winEventHook);
}
public void LockPanelsUpdated()
{
UserProfile.IsLocked = !UserProfile.IsLocked;
_userProfileManager.WriteUserProfiles();
}
public void PanelConfigPropertyUpdated(PanelConfigItem panelConfigItem)
{
if (!AllowEdit || UserProfile.IsLocked)
return;
var panelConfig = UserProfile.PanelConfigs.ToList().Find(p => p.PanelIndex == panelConfigItem.PanelIndex);
if (panelConfig != null)
{
switch (panelConfigItem.PanelConfigProperty)
{
case PanelConfigPropertyName.PanelName:
var name = panelConfig.PanelName;
if (name.IndexOf("(Custom)") == -1)
name = name + " (Custom)";
PInvoke.SetWindowText(panelConfig.PanelHandle, name);
break;
case PanelConfigPropertyName.Left:
case PanelConfigPropertyName.Top:
PInvoke.MoveWindow(panelConfig.PanelHandle, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height, true);
break;
case PanelConfigPropertyName.Width:
case PanelConfigPropertyName.Height:
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);
break;
case PanelConfigPropertyName.AlwaysOnTop:
WindowManager.ApplyAlwaysOnTop(panelConfig.PanelHandle, panelConfig.AlwaysOnTop, new Rectangle(panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height));
break;
case PanelConfigPropertyName.HideTitlebar:
WindowManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, panelConfig.HideTitlebar);
break;
}
_userProfileManager.WriteUserProfiles();
}
}
public void PanelConfigIncreaseDecrease(PanelConfigItem panelConfigItem, int changeAmount)
{
if (!AllowEdit || UserProfile.IsLocked || UserProfile.PanelConfigs == null || UserProfile.PanelConfigs.Count == 0)
return;
var index = UserProfile.PanelConfigs.ToList().FindIndex(p => p.PanelIndex == panelConfigItem.PanelIndex);
if (index > -1)
{
var panelConfig = UserProfile.PanelConfigs[index];
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;
break;
case PanelConfigPropertyName.Top:
PInvoke.MoveWindow(panelConfig.PanelHandle, panelConfig.Left, panelConfig.Top + changeAmount, panelConfig.Width, panelConfig.Height, false);
panelConfig.Top += changeAmount;
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;
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;
break;
default:
return;
}
_userProfileManager.WriteUserProfiles();
}
}
private void MSFSBugPanelShiftWorkaround(IntPtr handle, int originalLeft, int top, int width, int height)
{
// Fixed MSFS bug, create workaround where on 2nd or later instance of width adjustment, the panel shift to the left by itself
// Wait for system to catch up on panel coordinate that were just applied
System.Threading.Thread.Sleep(200);
Rectangle rectangle;
PInvoke.GetWindowRect(handle, out rectangle);
if (rectangle.Left != originalLeft)
PInvoke.MoveWindow(handle, originalLeft, top, width, height, false);
}
private void EventCallback(IntPtr hWinEventHook, uint iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime)
{
PanelConfig panelConfig;
// check by priority to minimize escaping constraint
if (hWnd == IntPtr.Zero
|| idObject != 0
|| hWinEventHook != _winEventHook
|| !AllowEdit
|| !(iEvent == PInvokeConstant.EVENT_OBJECT_LOCATIONCHANGE || iEvent == PInvokeConstant.EVENT_SYSTEM_MOVESIZEEND)
|| UserProfile.PanelConfigs == null || UserProfile.PanelConfigs.Count == 0)
{
return;
}
if(UserProfile.IsLocked)
{
panelConfig = UserProfile.PanelConfigs.FirstOrDefault(panel => panel.PanelHandle == hWnd);
if (panelConfig != null && panelConfig.PanelType == PanelType.CustomPopout)
{
// Move window back to original location if user profile is locked
if (iEvent == PInvokeConstant.EVENT_SYSTEM_MOVESIZEEND)
{
PInvoke.MoveWindow(panelConfig.PanelHandle, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height, false);
return;
}
if (iEvent == PInvokeConstant.EVENT_OBJECT_LOCATIONCHANGE)
{
// Detect if window is maximized, if so, save settings
WINDOWPLACEMENT wp = new WINDOWPLACEMENT();
wp.length = System.Runtime.InteropServices.Marshal.SizeOf(wp);
PInvoke.GetWindowPlacement(hWnd, ref wp);
if (wp.showCmd == PInvokeConstant.SW_SHOWMAXIMIZED || wp.showCmd == PInvokeConstant.SW_SHOWMINIMIZED || wp.showCmd == PInvokeConstant.SW_SHOWNORMAL)
{
PInvoke.ShowWindow(hWnd, PInvokeConstant.SW_RESTORE);
}
return;
}
}
return;
}
panelConfig = UserProfile.PanelConfigs.FirstOrDefault(panel => panel.PanelHandle == hWnd);
if (panelConfig != null)
{
switch (iEvent)
{
case PInvokeConstant.EVENT_OBJECT_LOCATIONCHANGE:
Rectangle winRectangle;
PInvoke.GetWindowRect(panelConfig.PanelHandle, out winRectangle);
if (_lastWindowRectangle == winRectangle) // ignore duplicate callback messages
return;
_lastWindowRectangle = winRectangle;
Rectangle clientRectangle;
PInvoke.GetClientRect(panelConfig.PanelHandle, out clientRectangle);
panelConfig.Left = winRectangle.Left;
panelConfig.Top = winRectangle.Top;
panelConfig.Width = clientRectangle.Width + 16;
panelConfig.Height = clientRectangle.Height + 39;
// Detect if window is maximized, if so, save settings
WINDOWPLACEMENT wp = new WINDOWPLACEMENT();
wp.length = System.Runtime.InteropServices.Marshal.SizeOf(wp);
PInvoke.GetWindowPlacement(hWnd, ref wp);
if (wp.showCmd == PInvokeConstant.SW_SHOWMAXIMIZED || wp.showCmd == PInvokeConstant.SW_SHOWMINIMIZED)
{
_userProfileManager.WriteUserProfiles();
}
break;
case PInvokeConstant.EVENT_SYSTEM_MOVESIZEEND:
_userProfileManager.WriteUserProfiles();
break;
}
}
}
}
}

View file

@ -1,33 +1,54 @@
using MSFSPopoutPanelManager.Shared;
using MSFSPopoutPanelManager.Model;
using MSFSPopoutPanelManager.Shared;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MSFSPopoutPanelManager.Provider
{
public class PopoutSeparationManager
public class PanelPopOutManager
{
private const int RETRY_COUNT = 5;
private IntPtr _simulatorHandle;
private UserProfileData _profile;
private List<PanelConfig> _panels;
public PopoutSeparationManager(IntPtr simulatorHandle, UserProfileData profile)
private UserProfileManager _userProfileManager;
private SimConnectManager _simConnectManager;
private IntPtr _simulatorHandle;
private List<PanelConfig> _panels;
private int _currentPanelIndex;
public event EventHandler OnPopOutStarted;
public event EventHandler<EventArgs<bool>> OnPopOutCompleted;
public UserProfile UserProfile { get; set; }
public AppSetting AppSetting { get; set; }
public PanelPopOutManager(UserProfileManager userProfileManager, SimConnectManager simConnectManager)
{
_simulatorHandle = simulatorHandle;
_profile = profile;
_panels = new List<PanelConfig>();
_userProfileManager = userProfileManager;
_simConnectManager = simConnectManager;
}
public bool StartPopout()
public void StartPopout()
{
var simulatorProcess = DiagnosticManager.GetSimulatorProcess();
if(simulatorProcess != null)
_simulatorHandle = simulatorProcess.Handle;
_panels = new List<PanelConfig>();
OnPopOutStarted?.Invoke(this, null);
// If enable, load the current viewport into custom view by Ctrl-Alt-0
if (FileManager.ReadAppSettingData().UseAutoPanning)
if (AppSetting.UseAutoPanning)
{
var simualatorProcess = WindowManager.GetSimulatorProcess();
var simualatorProcess = DiagnosticManager.GetSimulatorProcess();
if (simualatorProcess != null)
{
InputEmulationManager.LoadCustomViewZero(simualatorProcess.Handle);
@ -41,54 +62,62 @@ namespace MSFSPopoutPanelManager.Provider
});
popoutPanelTask.Wait();
var popoutReslts = popoutPanelTask.Result;
var popoutResults = popoutPanelTask.Result;
if (popoutReslts != null)
if (popoutResults != null)
{
if (_profile.PanelConfigs.Count > 0)
if (UserProfile.PanelConfigs.Count > 0)
{
LoadAndApplyPanelConfigs(popoutReslts);
Logger.Status("Panels have been popped out succesfully. Previously saved panel settings have been applied.", StatusMessageType.Info);
LoadAndApplyPanelConfigs(popoutResults);
Logger.LogStatus("Panels have been popped out succesfully and saved panel settings have been applied.", StatusMessageType.Info);
}
else
{
_profile.PanelConfigs = popoutReslts;
Logger.Status("Panels have been popped out succesfully.", StatusMessageType.Info);
UserProfile.PanelConfigs = new ObservableCollection<PanelConfig>(popoutResults);
Logger.LogStatus("Panels have been popped out succesfully.", StatusMessageType.Info);
}
// Recenter the view port by Ctrl-Space
var simualatorProcess = WindowManager.GetSimulatorProcess();
if (simualatorProcess != null)
// Recenter the view port by Ctrl-Space, needs to click on game window
var simualatorProcess = DiagnosticManager.GetSimulatorProcess();
if (simualatorProcess != null && UserProfile.PanelSourceCoordinates.Count > 0)
{
InputEmulationManager.CenterView(simualatorProcess.Handle);
InputEmulationManager.CenterView(simualatorProcess.Handle, UserProfile.PanelSourceCoordinates[0].X, UserProfile.PanelSourceCoordinates[0].Y);
}
return true;
}
return false;
_userProfileManager.WriteUserProfiles();
OnPopOutCompleted?.Invoke(this, new EventArgs<bool>(true));
}
else
{
OnPopOutCompleted?.Invoke(this, new EventArgs<bool>(false));
}
}
public List<PanelConfig> ExecutePopoutSeparation()
{
_currentPanelIndex = 0;
_panels.Clear();
// Must close out all existing custom pop out panels
PInvoke.EnumWindows(new PInvoke.CallBack(EnumCustomPopoutCallBack), 0);
if(_panels.Count > 0)
if (_panels.Count > 0)
{
Logger.BackgroundStatus("Please close all existing panel pop outs before continuing.", StatusMessageType.Error);
Logger.LogStatus("Please close all existing panel pop outs before continuing.", StatusMessageType.Error);
return null;
}
_panels.Clear();
PInvoke.SetForegroundWindow(_simulatorHandle);
if(_simulatorHandle != IntPtr.Zero)
PInvoke.SetForegroundWindow(_simulatorHandle);
try
{
for (var i = 0; i < _profile.PanelSourceCoordinates.Count; i++)
for (var i = 0; i < UserProfile.PanelSourceCoordinates.Count; i++)
{
PopoutPanel(_profile.PanelSourceCoordinates[i].X, _profile.PanelSourceCoordinates[i].Y);
PopoutPanel(UserProfile.PanelSourceCoordinates[i].X, UserProfile.PanelSourceCoordinates[i].Y);
if (i == 0)
{
@ -109,14 +138,14 @@ namespace MSFSPopoutPanelManager.Provider
}
if (GetPopoutPanelCountByType(PanelType.CustomPopout) != i + 1)
throw new PopoutManagerException("Unable to pop out the first panel. Please align first panel's number circle and check if the first panel has already been popped out. Also please check for window obstruction. Process stopped.");
throw new PopoutManagerException("Unable to pop out the first panel. Please check the first panel's number circle is positioned inside the panel, check for panel obstruction, and check if panel can be popped out. Pop out process stopped.");
}
if (i >= 1) // only separate with 2 or more panels
{
int retry = 0;
while (retry < RETRY_COUNT)
{
SeparatePanel(i, _panels[0].PanelHandle);
SeparatePanel(i, _panels[0].PanelHandle); // The joined panel is always the first panel that got popped out
PInvoke.EnumWindows(new PInvoke.CallBack(EnumCustomPopoutCallBack), i);
if (GetPopoutPanelCountByType(PanelType.CustomPopout) != i + 1)
@ -134,42 +163,46 @@ namespace MSFSPopoutPanelManager.Provider
}
if (GetPopoutPanelCountByType(PanelType.CustomPopout) != i + 1)
throw new PopoutManagerException($"Unable to pop out panel number {i + 1}. Please align the panel's number circle and check if the panel has already been popped out. Also please check for window obstruction.");
throw new PopoutManagerException($"Unable to pop out panel number {i + 1}. Please check panel's number circle is positioned inside the panel, check for panel obstruction, and check if panel can be popped out. Pop out process stopped.");
}
}
// Performance validation, make sure the number of pop out panels is equal to the number of selected panel
if (GetPopoutPanelCountByType(PanelType.CustomPopout) != _profile.PanelSourceCoordinates.Count)
throw new PopoutManagerException("Unable to pop out all panels. Please align all panel number circles with in-game panel locations. Also please check for window obstruction ");
if (GetPopoutPanelCountByType(PanelType.CustomPopout) != UserProfile.PanelSourceCoordinates.Count)
throw new PopoutManagerException("Unable to pop out all panels. Please align all panel number circles with in-game panel locations.");
// Add the built-in pop outs (ie. ATC, VFR Map) to the panel list
PInvoke.EnumWindows(new PInvoke.CallBack(EnumBuiltinPopoutCallBack), _profile.PanelSourceCoordinates.Count + 1);
if(AppSetting.IncludeBuiltInPanel)
PInvoke.EnumWindows(new PInvoke.CallBack(EnumBuiltinPopoutCallBack), 0);
// Add the MSFS Touch Panel (My other github project) windows to the panel list
PInvoke.EnumWindows(new PInvoke.CallBack(EnumMSFSTouchPanelPopoutCallBack), _profile.PanelSourceCoordinates.Count + 1);
PInvoke.EnumWindows(new PInvoke.CallBack(EnumMSFSTouchPanelPopoutCallBack), 0);
if (_panels.Count == 0)
throw new PopoutManagerException("No panels have been found. Please select or open at least one in-game panel or MSFS Touch Panel App's panel.");
throw new PopoutManagerException("No panels have been found. Please select at least one in-game panel.");
// Line up all the panels and fill in meta data
for (var i = _panels.Count - 1; i >= 0; i--)
{
var shift = _panels.Count - i - 1;
_panels[i].Top = shift * 30;
_panels[i].Left = shift * 30;
_panels[i].Width = 800;
_panels[i].Height = 600;
if (_panels[i].PanelType == PanelType.CustomPopout)
{
var shift = _panels.Count - i - 1;
_panels[i].Top = shift * 30;
_panels[i].Left = shift * 30;
_panels[i].Width = 800;
_panels[i].Height = 600;
PInvoke.MoveWindow(_panels[i].PanelHandle, _panels[i].Top, _panels[i].Left, _panels[i].Width, _panels[i].Height, true);
PInvoke.SetForegroundWindow(_panels[i].PanelHandle);
Thread.Sleep(200);
PInvoke.MoveWindow(_panels[i].PanelHandle, _panels[i].Top, _panels[i].Left, _panels[i].Width, _panels[i].Height, true);
PInvoke.SetForegroundWindow(_panels[i].PanelHandle);
Thread.Sleep(200);
}
}
return _panels;
}
catch(PopoutManagerException ex)
catch (PopoutManagerException ex)
{
Logger.BackgroundStatus(ex.Message, StatusMessageType.Error);
Logger.LogStatus(ex.Message, StatusMessageType.Error);
return null;
}
catch
@ -185,28 +218,33 @@ namespace MSFSPopoutPanelManager.Provider
{
if (resultPanel.PanelType == PanelType.CustomPopout)
{
index = _profile.PanelConfigs.FindIndex(x => x.PanelIndex == resultPanel.PanelIndex);
index = UserProfile.PanelConfigs.ToList().FindIndex(x => x.PanelIndex == resultPanel.PanelIndex);
if (index > -1)
_profile.PanelConfigs[index].PanelHandle = resultPanel.PanelHandle;
UserProfile.PanelConfigs[index].PanelHandle = resultPanel.PanelHandle;
}
else
{
index = _profile.PanelConfigs.FindIndex(x => x.PanelName == resultPanel.PanelName);
index = UserProfile.PanelConfigs.ToList().FindIndex(x => x.PanelName == resultPanel.PanelName);
if (index > -1)
_profile.PanelConfigs[index].PanelHandle = resultPanel.PanelHandle;
UserProfile.PanelConfigs[index].PanelHandle = resultPanel.PanelHandle;
else
_profile.PanelConfigs.Add(resultPanel);
UserProfile.PanelConfigs.Add(resultPanel);
}
});
//_profile.PanelConfigs.RemoveAll(x => x.PanelHandle == IntPtr.Zero && x.PanelType == PanelType.BuiltInPopout);
_profile.PanelConfigs.RemoveAll(x => x.PanelHandle == IntPtr.Zero);
//_profile.PanelSettings.ForEach(panel =>
Parallel.ForEach(_profile.PanelConfigs, panel =>
// Remove pop out that do not exist for this pop out iteration
foreach(var panelConfig in UserProfile.PanelConfigs.ToList())
{
if (panel != null && panel.Width != 0 && panel.Height != 0)
if(panelConfig.PanelHandle == IntPtr.Zero)
{
UserProfile.PanelConfigs.Remove(panelConfig);
}
}
Parallel.ForEach(UserProfile.PanelConfigs, panel =>
{
if (panel != null && panel.PanelHandle != IntPtr.Zero && panel.Width != 0 && panel.Height != 0)
{
// Apply panel name
if (panel.PanelType == PanelType.CustomPopout)
@ -220,7 +258,9 @@ namespace MSFSPopoutPanelManager.Provider
}
// Apply locations
PInvoke.MoveWindow(panel.PanelHandle, panel.Left, panel.Top, panel.Width, panel.Height, true);
PInvoke.ShowWindow(panel.PanelHandle, PInvokeConstant.SW_RESTORE);
Thread.Sleep(250);
PInvoke.MoveWindow(panel.PanelHandle, panel.Left, panel.Top, panel.Width, panel.Height, false);
Thread.Sleep(1000);
// Apply always on top
@ -264,11 +304,14 @@ namespace MSFSPopoutPanelManager.Provider
// MSFS draws popout panel differently at different time for same panel
PInvoke.MoveWindow(hwnd, -8, 0, 800, 600, true);
PInvoke.SetForegroundWindow(hwnd);
Thread.Sleep(250);
Thread.Sleep(500);
// Find the magnifying glass coordinate
var point = AnalyzeMergedWindows(hwnd);
if (point.Y <= 39) // false positive
return;
InputEmulationManager.LeftClick(point.X, point.Y);
}
@ -276,11 +319,12 @@ namespace MSFSPopoutPanelManager.Provider
{
var panelInfo = GetPanelWindowInfo(hwnd);
if(panelInfo != null && panelInfo.PanelType == PanelType.CustomPopout)
if (panelInfo != null && panelInfo.PanelType == PanelType.CustomPopout)
{
if (!_panels.Exists(x => x.PanelHandle == hwnd))
{
panelInfo.PanelIndex = index + 1; // Panel index starts at 1
Interlocked.Increment(ref _currentPanelIndex);
panelInfo.PanelIndex = _currentPanelIndex; // PanelIndex starts at 1
_panels.Add(panelInfo);
}
}
@ -296,7 +340,8 @@ namespace MSFSPopoutPanelManager.Provider
{
if (!_panels.Exists(x => x.PanelHandle == hwnd))
{
panelInfo.PanelIndex = index;
Interlocked.Increment(ref _currentPanelIndex);
panelInfo.PanelIndex = _currentPanelIndex;
_panels.Add(panelInfo);
}
}
@ -312,7 +357,8 @@ namespace MSFSPopoutPanelManager.Provider
{
if (!_panels.Exists(x => x.PanelHandle == hwnd))
{
panelInfo.PanelIndex = index;
Interlocked.Increment(ref _currentPanelIndex);
panelInfo.PanelIndex = _currentPanelIndex;
_panels.Add(panelInfo);
}
}
@ -369,7 +415,7 @@ namespace MSFSPopoutPanelManager.Provider
PInvoke.GetClientRect(hwnd, out rectangle);
var panelMenubarTop = GetPanelMenubarTop(sourceImage, rectangle);
if (panelMenubarTop > sourceImage.Height)
if (panelMenubarTop > sourceImage.Height)
return Point.Empty;
var panelMenubarBottom = GetPanelMenubarBottom(sourceImage, rectangle);
@ -463,7 +509,7 @@ namespace MSFSPopoutPanelManager.Provider
// found the top of menu bar
menubarBottom = y + top;
}
else if(menubarBottom > -1) /// it is no longer white in color, we hit menubar bottom
else if (menubarBottom > -1) /// it is no longer white in color, we hit menubar bottom
{
sourceImage.UnlockBits(stripData);
return menubarBottom;

View file

@ -1,108 +1,159 @@
using Gma.System.MouseKeyHook;
using MSFSPopoutPanelManager.Model;
using MSFSPopoutPanelManager.Shared;
using MSFSPopoutPanelManager.UI;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;
using System.Drawing;
using System.Linq;
namespace MSFSPopoutPanelManager.Provider
{
public class PanelSelectionManager
{
private IKeyboardMouseEvents _mouseHook;
private UserProfileManager _userProfileManager;
private int _panelIndex;
private Form _appForm;
private List<PanelSourceCoordinate> _panelCoordinates;
private IntPtr _winEventHook;
private static PInvoke.WinEventProc _winEvent; // keep this as static to prevent garbage collect or the app will crash
private Rectangle _lastWindowRectangle;
private bool _isEditingPanelCoordinates;
public event EventHandler OnSelectionCompleted;
public event EventHandler OnPanelSelectionCompleted;
public event EventHandler<EventArgs<PanelSourceCoordinate>> OnPanelLocationAdded;
public event EventHandler OnPanelLocationRemoved;
public event EventHandler OnAllPanelLocationsRemoved;
public List<PanelSourceCoordinate> PanelCoordinates { get; set; }
public UserProfile UserProfile { get; set; }
public PanelSelectionManager(Form form)
public AppSetting AppSetting { get; set; }
public PanelSelectionManager(UserProfileManager userProfileManager)
{
PanelCoordinates = new List<PanelSourceCoordinate>();
_appForm = form;
_userProfileManager = userProfileManager;
InputHookManager.OnPanelSelectionAdded += HandleOnPanelSelectionAdded;
InputHookManager.OnPanelSelectionRemoved += HandleOnPanelSelectionRemoved;
InputHookManager.OnPanelSelectionCompleted += HandleOnPanelSelectionCompleted;
}
public void Start()
{
if (_mouseHook == null)
{
_mouseHook = Hook.GlobalEvents();
_mouseHook.MouseDownExt += HandleMouseHookMouseDownExt;
}
_panelIndex = 1;
ShowPanelLocationOverlay(true);
_panelCoordinates = new List<PanelSourceCoordinate>();
ShowPanelLocationOverlay(_panelCoordinates, true);
InputHookManager.SubscribeToPanelSelectionEvent = true;
}
public void Reset()
{
_panelIndex = 1;
ShowPanelLocationOverlay(false);
}
public void ShowPanelLocationOverlay(bool show)
public void ShowPanelLocationOverlay(List<PanelSourceCoordinate> panelCoordinates, bool show)
{
// close all overlays
for (int i = Application.OpenForms.Count - 1; i >= 0; i--)
{
if (Application.OpenForms[i].GetType() == typeof(PopoutCoorOverlayForm))
Application.OpenForms[i].Close();
}
OnAllPanelLocationsRemoved?.Invoke(this, null);
if (show && PanelCoordinates.Count > 0)
if (show && panelCoordinates.Count > 0)
{
foreach (var coor in PanelCoordinates)
WindowManager.AddPanelLocationSelectionOverlay(coor.PanelIndex.ToString(), coor.X, coor.Y);
foreach (var coor in panelCoordinates)
{
var panelSourceCoordinate = new PanelSourceCoordinate() { PanelIndex = coor.PanelIndex, X = coor.X, Y = coor.Y };
OnPanelLocationAdded?.Invoke(this, new EventArgs<PanelSourceCoordinate>(panelSourceCoordinate));
}
}
}
private void HandleMouseHookMouseDownExt(object sender, MouseEventExtArgs e)
private void HandleOnPanelSelectionAdded(object sender, System.Drawing.Point e)
{
if (e.Button == MouseButtons.Left)
var newPanelCoordinates = new PanelSourceCoordinate() { PanelIndex = _panelIndex, X = e.X, Y = e.Y };
_panelCoordinates.Add(newPanelCoordinates);
_panelIndex++;
OnPanelLocationAdded?.Invoke(this, new EventArgs<PanelSourceCoordinate>(newPanelCoordinates));
}
private void HandleOnPanelSelectionRemoved(object sender, EventArgs e)
{
if(_panelCoordinates.Count > 0)
{
var ctrlPressed = Control.ModifierKeys.ToString() == "Control";
var shiftPressed = Control.ModifierKeys.ToString() == "Shift";
_panelCoordinates.RemoveAt(_panelCoordinates.Count - 1);
_panelIndex--;
if (ctrlPressed)
OnPanelLocationRemoved?.Invoke(this, null);
}
}
private void HandleOnPanelSelectionCompleted(object sender, EventArgs e)
{
// If enable, save the current viewport into custom view by Ctrl-Alt-0
if (AppSetting.UseAutoPanning)
{
var simualatorProcess = DiagnosticManager.GetSimulatorProcess();
if (simualatorProcess != null)
{
if (_mouseHook != null)
{
_mouseHook.MouseDownExt -= HandleMouseHookMouseDownExt;
_mouseHook.Dispose();
_mouseHook = null;
}
OnSelectionCompleted?.Invoke(this, null);
InputEmulationManager.SaveCustomViewZero(simualatorProcess.Handle);
}
else if (shiftPressed && Application.OpenForms.Count >= 1)
{
if (Application.OpenForms[Application.OpenForms.Count - 1].GetType() == typeof(PopoutCoorOverlayForm))
{
// Remove last drawn overlay
Application.OpenForms[Application.OpenForms.Count - 1].Close();
PanelCoordinates.RemoveAt(PanelCoordinates.Count - 1);
_panelIndex--;
}
}
else if (!shiftPressed)
{
var minX = _appForm.Location.X;
var minY = _appForm.Location.Y;
var maxX = _appForm.Location.X + _appForm.Width;
var maxY = _appForm.Location.Y + _appForm.Height;
}
if (e.X < minX || e.X > maxX || e.Y < minY || e.Y > maxY)
{
var newPanelCoordinates = new PanelSourceCoordinate() { PanelIndex = _panelIndex, X = e.X, Y = e.Y };
PanelCoordinates.Add(newPanelCoordinates);
// Assign and save panel coordinates to active profile
UserProfile.PanelSourceCoordinates.Clear();
_panelCoordinates.ForEach(c => UserProfile.PanelSourceCoordinates.Add(c));
UserProfile.PanelConfigs.Clear();
UserProfile.IsLocked = false;
WindowManager.AddPanelLocationSelectionOverlay(_panelIndex.ToString(), e.X, e.Y);
_panelIndex++;
}
_userProfileManager.WriteUserProfiles();
InputHookManager.SubscribeToPanelSelectionEvent = false;
OnPanelSelectionCompleted?.Invoke(this, null);
}
public void StartEditPanelLocations()
{
_winEvent = new PInvoke.WinEventProc(PanelLocationEditEventCallback);
_winEventHook = PInvoke.SetWinEventHook(PInvokeConstant.EVENT_SYSTEM_MOVESIZEEND, PInvokeConstant.EVENT_OBJECT_LOCATIONCHANGE, DiagnosticManager.GetApplicationProcess().Handle, _winEvent, 0, 0, PInvokeConstant.WINEVENT_OUTOFCONTEXT);
_isEditingPanelCoordinates = true;
}
public void EndEditPanelLocations()
{
PInvoke.UnhookWinEvent(_winEventHook);
_isEditingPanelCoordinates = false;
}
private void PanelLocationEditEventCallback(IntPtr hWinEventHook, uint iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime)
{
// check by priority to minimize escaping constraint
if (hWnd == IntPtr.Zero
|| idObject != 0
|| hWinEventHook != _winEventHook
|| !_isEditingPanelCoordinates
|| !(iEvent == PInvokeConstant.EVENT_OBJECT_LOCATIONCHANGE || iEvent == PInvokeConstant.EVENT_SYSTEM_MOVESIZEEND)
|| UserProfile.PanelSourceCoordinates == null || UserProfile.PanelSourceCoordinates.Count == 0)
{
return;
}
var panelSourceCoordinate = UserProfile.PanelSourceCoordinates.FirstOrDefault(panel => panel.PanelHandle == hWnd);
if (panelSourceCoordinate != null)
{
switch (iEvent)
{
case PInvokeConstant.EVENT_OBJECT_LOCATIONCHANGE:
Rectangle winRectangle;
PInvoke.GetWindowRect(panelSourceCoordinate.PanelHandle, out winRectangle);
if (_lastWindowRectangle == winRectangle) // ignore duplicate callback messages
return;
_lastWindowRectangle = winRectangle;
Rectangle clientRectangle;
PInvoke.GetClientRect(panelSourceCoordinate.PanelHandle, out clientRectangle);
panelSourceCoordinate.X = winRectangle.Left;
panelSourceCoordinate.Y = winRectangle.Top;
break;
case PInvokeConstant.EVENT_SYSTEM_MOVESIZEEND:
_userProfileManager.WriteUserProfiles();
break;
}
}
}
}
}
}

47
Provider/Provider.csproj Normal file
View file

@ -0,0 +1,47 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net5.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<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>
<Authors>Stanley Kwok</Authors>
<Copyright>Stanley Kwok 2021</Copyright>
<PackageProjectUrl>https://github.com/hawkeye-stan/msfs-popout-panel-manager</PackageProjectUrl>
<UseWindowsForms>true</UseWindowsForms>
<ApplicationIcon />
<StartupObject />
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MouseKeyHook" Version="5.6.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FsConnector\FsConnector.csproj" />
<ProjectReference Include="..\Model\Model.csproj" />
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,131 @@
using MSFSPopoutPanelManager.FsConnector;
using MSFSPopoutPanelManager.Shared;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using System.Timers;
namespace MSFSPopoutPanelManager.Provider
{
public class SimConnectManager
{
private const int MSFS_DATA_REFRESH_TIMEOUT = 1000;
private SimConnector _simConnector;
private dynamic _simData;
private System.Timers.Timer _requestDataTimer;
private SimConnectSystemEvent _lastSystemEvent;
private bool _isPowerOnForPopOut;
public event EventHandler OnConnected;
public event EventHandler OnDisconnected;
public event EventHandler<EventArgs<dynamic>> OnSimConnectDataRefreshed;
public event EventHandler OnFlightStarted;
public event EventHandler OnFlightStopped;
public bool IsSimConnectStarted { get; set; }
public SimConnectManager()
{
_simConnector = new SimConnector();
_simConnector.OnConnected += (sender, e) => { OnConnected?.Invoke(this, null); };
_simConnector.OnDisconnected += (sender, e) => { OnDisconnected?.Invoke(this, null); };
_simConnector.OnReceivedData += HandleDataReceived;
_simConnector.OnReceiveSystemEvent += HandleReceiveSystemEvent;
_simConnector.OnConnected += (sender, e) =>
{
_requestDataTimer = new System.Timers.Timer();
_requestDataTimer.Interval = MSFS_DATA_REFRESH_TIMEOUT;
_requestDataTimer.Enabled = true;
_requestDataTimer.Elapsed += HandleDataRequested;
_requestDataTimer.Elapsed += HandleMessageReceived;
};
_simConnector.Start();
}
public void Stop()
{
_simConnector.Stop();
}
public void Restart()
{
_simConnector.StopAndReconnect();
}
public void TurnOnPower(bool isRequiredForColdStart)
{
if (isRequiredForColdStart && _simData != null && !_simData.ElectricalMasterBattery)
{
_isPowerOnForPopOut = true;
_simConnector.TransmitActionEvent(ActionEvent.KEY_MASTER_BATTERY_SET, 1);
Thread.Sleep(100);
_simConnector.TransmitActionEvent(ActionEvent.KEY_ALTERNATOR_SET, 1);
Thread.Sleep(100);
_simConnector.TransmitActionEvent(ActionEvent.KEY_AVIONICS_MASTER_1_ON, 1);
Thread.Sleep(100);
_simConnector.TransmitActionEvent(ActionEvent.KEY_AVIONICS_MASTER_2_ON, 1);
Thread.Sleep(100);
_simConnector.TransmitActionEvent(ActionEvent.KEY_AVIONICS_MASTER_SET, 1);
}
}
public void TurnOffpower()
{
if(_isPowerOnForPopOut)
{
_simConnector.TransmitActionEvent(ActionEvent.KEY_AVIONICS_MASTER_1_OFF, 1);
Thread.Sleep(100);
_simConnector.TransmitActionEvent(ActionEvent.KEY_AVIONICS_MASTER_2_OFF, 1);
Thread.Sleep(100);
_simConnector.TransmitActionEvent(ActionEvent.KEY_AVIONICS_MASTER_SET, 0);
Thread.Sleep(100);
_simConnector.TransmitActionEvent(ActionEvent.KEY_ALTERNATOR_SET, 0);
Thread.Sleep(100);
_simConnector.TransmitActionEvent(ActionEvent.KEY_MASTER_BATTERY_SET, 0);
_isPowerOnForPopOut = false;
}
}
private void HandleDataRequested(object sender, ElapsedEventArgs e)
{
try
{
_simConnector.RequestData();
}
catch { }
}
private void HandleMessageReceived(object sender, ElapsedEventArgs e)
{
try
{
_simConnector.ReceiveMessage();
}
catch { }
}
public void HandleDataReceived(object sender, EventArgs<dynamic> e)
{
_simData = e.Value;
OnSimConnectDataRefreshed?.Invoke(this, new EventArgs<dynamic>(e.Value));
}
private void HandleReceiveSystemEvent(object sender, EventArgs<SimConnectSystemEvent> e)
{
// 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)
OnFlightStarted?.Invoke(this, null);
if (e.Value == SimConnectSystemEvent.SIMSTOP)
OnFlightStopped?.Invoke(this, null);
Debug.WriteLine($"SimConnectSystemEvent Received: {e.Value.ToString()}");
_lastSystemEvent = e.Value;
}
}
}

View file

@ -0,0 +1,168 @@
using MSFSPopoutPanelManager.Model;
using MSFSPopoutPanelManager.Shared;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
namespace MSFSPopoutPanelManager.Provider
{
public class UserProfileManager
{
private const string USER_PROFILE_DATA_FILENAME = "userprofiledata.json";
public ObservableCollection<UserProfile> UserProfiles { get; set; }
public int AddUserProfile(string newProfileName)
{
return AddProfile(new UserProfile(), newProfileName);
}
public int AddUserProfileByCopyingProfile(string newProfileName, int copyProfileId)
{
if (UserProfiles == null)
throw new Exception("User Profiles is null.");
var matchedProfile = UserProfiles.FirstOrDefault(p => p.ProfileId == copyProfileId);
var copiedProfile = matchedProfile.Copy<UserProfile>(); // Using Shared/ObjectExtensions.cs extension method
copiedProfile.IsDefaultProfile = false;
copiedProfile.BindingPlaneTitle = null;
return AddProfile(copiedProfile, newProfileName);
}
public bool DeleteUserProfile(int profileId)
{
if (UserProfiles == null)
throw new Exception("User Profiles is null.");
if (profileId == -1)
return false;
var profileToRemove = UserProfiles.First(x => x.ProfileId == profileId);
UserProfiles.Remove(profileToRemove);
WriteUserProfiles();
Logger.LogStatus($"Profile '{profileToRemove.ProfileName}' has been deleted successfully.", StatusMessageType.Info);
return true;
}
public bool SetDefaultUserProfile(int profileId)
{
if (UserProfiles == null)
throw new Exception("User Profiles is null.");
if (profileId == -1)
return false;
var profile = UserProfiles.First(x => x.ProfileId == profileId);
profile.IsDefaultProfile = true;
foreach (var p in UserProfiles)
{
if (p.ProfileId != profileId)
p.IsDefaultProfile = false;
}
WriteUserProfiles();
Logger.LogStatus($"Profile '{profile.ProfileName}' has been set as default.", StatusMessageType.Info);
return true;
}
public UserProfile GetDefaultProfile()
{
return UserProfiles.ToList().Find(x => x.IsDefaultProfile);
}
public void AddProfileBinding(string planeTitle, int activeProfileId)
{
var bindedProfile = UserProfiles.FirstOrDefault(p => p.BindingPlaneTitle == 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;
WriteUserProfiles();
Logger.LogStatus($"Binding for the profile has been added successfully.", StatusMessageType.Info);
}
public void DeleteProfileBinding(int activeProfileId)
{
UserProfiles.First(p => p.ProfileId == activeProfileId).BindingPlaneTitle = null;
WriteUserProfiles();
Logger.LogStatus($"Binding for the profile has been deleted successfully.", StatusMessageType.Info);
}
public void ReadUserProfiles()
{
try
{
using (StreamReader reader = new StreamReader(Path.Combine(FileIo.GetUserDataFilePath(), USER_PROFILE_DATA_FILENAME)))
{
UserProfiles = new ObservableCollection<UserProfile>(JsonConvert.DeserializeObject<List<UserProfile>>(reader.ReadToEnd()));
}
}
catch
{
UserProfiles = new ObservableCollection<UserProfile>(new List<UserProfile>());
}
}
public void WriteUserProfiles()
{
Debug.WriteLine("saving profile....");
if (UserProfiles == null)
throw new Exception("User Profiles is null.");
try
{
var userProfilePath = FileIo.GetUserDataFilePath();
if (!Directory.Exists(userProfilePath))
Directory.CreateDirectory(userProfilePath);
using (StreamWriter file = File.CreateText(Path.Combine(userProfilePath, USER_PROFILE_DATA_FILENAME)))
{
JsonSerializer serializer = new JsonSerializer();
serializer.Serialize(file, UserProfiles);
}
}
catch
{
Logger.LogStatus($"Unable to write user data file: {USER_PROFILE_DATA_FILENAME}", StatusMessageType.Error);
}
}
private int AddProfile(UserProfile userProfile, string newProfileName)
{
if (UserProfiles == null)
throw new Exception("User Profiles is null.");
var newPlaneProfile = userProfile;
var newProfileId = UserProfiles.Count > 0 ? UserProfiles.Max(x => x.ProfileId) + 1 : 1;
newPlaneProfile.ProfileName = newProfileName;
newPlaneProfile.ProfileId = newProfileId;
var tmpList = UserProfiles.ToList();
tmpList.Add(newPlaneProfile);
var index = tmpList.OrderBy(x => x.ProfileName).ToList().FindIndex(x => x.ProfileId == newProfileId);
UserProfiles.Insert(index, newPlaneProfile);
WriteUserProfiles();
Logger.LogStatus($"Profile '{newPlaneProfile.ProfileName}' has been added successfully.", StatusMessageType.Info);
return newProfileId;
}
}
}

View file

@ -1,23 +1,12 @@
using MSFSPopoutPanelManager.Shared;
using MSFSPopoutPanelManager.UI;
using System;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace MSFSPopoutPanelManager.Provider
{
public class WindowManager
{
public static void AddPanelLocationSelectionOverlay(string text, int x, int y)
{
PopoutCoorOverlayForm frm = new PopoutCoorOverlayForm();
frm.Location = new Point(x - frm.Width / 2, y - frm.Height / 2);
frm.StartPosition = FormStartPosition.Manual;
((Label)frm.Controls.Find("lblPanelIndex", true)[0]).Text = text;
frm.Show();
}
public static void ApplyHidePanelTitleBar(IntPtr handle, bool hideTitleBar)
{
var currentStyle = PInvoke.GetWindowLong(handle, PInvokeConstant.GWL_STYLE).ToInt64();
@ -31,9 +20,20 @@ namespace MSFSPopoutPanelManager.Provider
public static void ApplyAlwaysOnTop(IntPtr handle, bool alwaysOnTop, Rectangle panelRectangle)
{
if (alwaysOnTop)
PInvoke.SetWindowPos(handle, PInvokeConstant.HWND_TOPMOST, panelRectangle.Left, panelRectangle.Top, panelRectangle.Width, panelRectangle.Height, PInvokeConstant.SWP_ALWAYS_ON_TOP);
PInvoke.SetWindowPos(handle, new IntPtr(PInvokeConstant.HWND_TOPMOST), panelRectangle.Left, panelRectangle.Top, panelRectangle.Width, panelRectangle.Height, PInvokeConstant.SWP_ALWAYS_ON_TOP);
else
PInvoke.SetWindowPos(handle, PInvokeConstant.HWND_NOTOPMOST, panelRectangle.Left, panelRectangle.Top, panelRectangle.Width, panelRectangle.Height, 0);
PInvoke.SetWindowPos(handle, new IntPtr(PInvokeConstant.HWND_NOTOPMOST), panelRectangle.Left, panelRectangle.Top, panelRectangle.Width, panelRectangle.Height, 0);
}
public static void ApplyAlwaysOnTop(IntPtr handle, bool alwaysOnTop)
{
Rectangle rect;
PInvoke.GetWindowRect(handle, out rect);
Rectangle clientRectangle;
PInvoke.GetClientRect(handle, out clientRectangle);
ApplyAlwaysOnTop(handle, alwaysOnTop, new Rectangle(rect.X, rect.Y, clientRectangle.Width, clientRectangle.Height));
}
public static void CloseWindow(IntPtr handle)
@ -48,32 +48,15 @@ namespace MSFSPopoutPanelManager.Provider
PInvoke.MoveWindow(handle, x, y, rectangle.Width, rectangle.Height, false);
}
public static WindowProcess GetSimulatorProcess()
public static void MinimizeWindow(IntPtr handle)
{
return GetProcess("FlightSimulator");
PInvoke.ShowWindow(handle, PInvokeConstant.SW_MINIMIZE);
}
public static WindowProcess GetApplicationProcess()
public static void BringWindowToForeground(IntPtr handle)
{
return GetProcess("MSFSPopoutPanelManager");
}
private static WindowProcess GetProcess(string processName)
{
foreach (var process in Process.GetProcesses())
{
if (process.ProcessName == processName)
{
return new WindowProcess()
{
ProcessId = process.Id,
ProcessName = process.ProcessName,
Handle = process.MainWindowHandle
};
}
}
return null;
PInvoke.ShowWindowAsync(new HandleRef(null, handle), PInvokeConstant.SW_RESTORE);
PInvoke.SetForegroundWindow(handle);
}
public static void CloseAllCustomPopoutPanels()
@ -81,6 +64,34 @@ namespace MSFSPopoutPanelManager.Provider
PInvoke.EnumWindows(new PInvoke.CallBack(EnumAllCustomPopoutPanels), 1);
}
public static void MinimizeAllPopoutPanels(bool active)
{
if (active)
{
PInvoke.EnumWindows(new PInvoke.CallBack(EnumToMinimizePopoutPanels), 0);
}
else
{
PInvoke.EnumWindows(new PInvoke.CallBack(EnumToMinimizePopoutPanels), 1);
}
}
private static bool EnumToMinimizePopoutPanels(IntPtr hwnd, int index)
{
var className = PInvoke.GetClassName(hwnd);
var caption = PInvoke.GetWindowText(hwnd);
if (className == "AceApp" && caption.IndexOf("Microsoft Flight Simulator") == -1) // MSFS windows designation
{
if (index == 0)
PInvoke.ShowWindow(hwnd, PInvokeConstant.SW_MINIMIZE);
else
PInvoke.ShowWindow(hwnd, PInvokeConstant.SW_RESTORE);
}
return true;
}
private static bool EnumAllCustomPopoutPanels(IntPtr hwnd, int index)
{
var className = PInvoke.GetClassName(hwnd);
@ -90,7 +101,7 @@ namespace MSFSPopoutPanelManager.Provider
{
WindowManager.CloseWindow(hwnd);
}
else if (className == "AceApp") // for builtin pop out (ATC, VFR Map, ect)
else if (className == "AceApp" && caption.IndexOf("Microsoft Flight Simulator") == -1) // for builtin pop out (ATC, VFR Map, ect)
{
WindowManager.MoveWindow(hwnd, 0, 0);
}

View file

@ -1,6 +1,6 @@
using System;
namespace MSFSPopoutPanelManager
namespace MSFSPopoutPanelManager.Provider
{
public class WindowProcess
{

115
README.md
View file

@ -3,18 +3,55 @@ MSFS Pop Out Panel Manager is an application for MSFS 2020 which helps pop out,
[FlightSimulator.com forum thread regarding this project](https://forums.flightsimulator.com/t/msfs-pop-out-panel-manager-automatically-pop-out-and-save-panel-position/460613)
### IMPORTANT! Version 3.0 file format is not compatible with previous version user profile data format since additional information has been added and updated to support new features. Please continue to use version 2.2 of the application if it works for you and you do not need the new features. If you're technical, please see User Profile Data Files below to do a manual data transfer.
## Version 3.2 NEW FEATURES!
* 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.
* 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 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.
* New copy profile feature. You can reuse your defined panel settings for another plane or plane/livery combination. This is a feature to solve the problem when the final panel placements are the same but the in-game panel locations are different. This also allows using a defined profile for different liveries for the same plane for Auto Pop Out. (See Auto Pop Out Panel experiment feature).
* Added quick panel location selection adjustment feature. You can now adjust panel locations without redoing the entire profile. Just click "Show/Edit Panel Location Overlay" checkbox, you can now drag and move the panel selection number circle. When you're done with the placement, the new location will automatically save for the profile.
* Added Save Auto Panning Camera Angle function if you need to adjust the in-game camera angle during panel selection.
* New logo icon for the app.
* New dark theme for the entire UI.
* Technical Note - Application is ported and rewritten with .NET WPF framework instead of WinForms and SimConnect is added to the app for Auto Pop Out feature and for future functionality expansions.
## Version 3.0 NEW FEATURES!
** Beta feature - Auto Pop Out Panels**
* Provided 2X pop out and panel separation performance.
* Display resolution independent. Tested on 1080p/1440p/4k display.
* New Cold Start feature. Panels can be popped out and recalled later even when they're not turned on.
* New Auto Panning feature remembers the cockpit camera angle when you first define the pop out panels. You can now pan, zoom in, and zoom out to identify offscreen panels and the camera angle will be saved and reused. This feature requires the use of Ctrl-Alt-0 and Alt-0 keyboard binding to save custom camera view per plane configuration. If the keyboard binding is currently being used. The auto-panning feature will overwrite the saved camera view if enabled.
* New fine-grain control in positioning panels down to pixel level.
* New user-friendly features such as Always on Top, real time readout as you position panels, a more intuitive user interface and status messages.
* New auto save feature. All profile and panel changes get save automatically.
* Technical: Rewritten code base to improve code structure and performance.
When a profile is defined, final panel placements are set, and bound to a plane type + livery combination. The application will automatically pop out all panels if a matched profile is detected when a flight starts.
[Online Video - feature in action](https://vimeo.com/674073559) - In the video, after clicking flight restart, the app did all the clicking by itself.
**How it works and how to use:**
The app will try to find a matching profile with the title of the plane (per livery). It will then automatically detect when a flight is starting and then click the "Ready to Fly" button. It will then power on instrumentation for cold start (if necessary), and pop out all panels. This feature allows panels to be popped out without the need of user interaction. If profiles are set and bound, you can auto-start the app minimized in system and as you start your flight, panels will automatically pop out for you.
* First make sure in File->Preferences, "Auto Pop Out Panels" option is turned on.
* For existing profile to use Auto Pop Out feature, just click "Add Binding" and bind the profile to the active plane in the game.
* Since Auto Pop Out need to match plane title to work, a profile must be bound to a plane to use the Auto Pop Out feature. You can continue to manually click start the pop out for unbound profile. Or better yet, use the new keyboard shortcut (Ctrl-Alt-P) to manually start the pop out process.
* During my testing, instrumentations only need to be powered on for Auto Pop Out to work for G1000/G1000 NXi plane during cold start. (There seems to be a bug in the game that you can't do Alt-Right click to pop out cold start panel for this particular plane variant). For other plane instrumentations I've tested (G3000, CJ4, Aerosoft CRJ 550/700, FBW A32NX), panels can be popped out without powering on. So please make sure the checkbox "Power on required to pop out panels on cold start" is checked for G1000 related profiles.
* If you want to fly the same plane with different livery, the plane title will be different for each livery. You can see the current in-game active plane + livery title by hovering your mouse over the connection icon in the upper right corner of the app. In order to use Auto Pop Out feature in this scenario, first defined the initial profile for a plane + livery combination. After you're satisfy the profile is working, switch the plane livery and create a new profile by copying the initial profile and bound it to the plane again (with the new livery selected). You should notice the binding name will be different even though all the panel settings are the same. Repeat this for as many liveries for the plane as needed.
* If after binding the livery and Auto Pop Out did not work (which means the app cannot find a match), please hover your mouse over the connecting icon in the app to see the current plane title reported by the game is the same as what you've bound to the profile. Sometimes, SimConnect does not update the plane title until the flight actually starts. So if you've done the binding before the flight start, sometime it will not work. Just just "Replace binding" again and confirm the plane title.
* **TIPS:** One of the trick to force SimConnect to update the plane title after selecting a new livery is when you've selected a plane and livery in the World Map, click the "Go Back" button at the lower left of your screen.
* **TIPS:** For technical user, there are parts in this feature that uses a timer when waiting to execute certain steps in the Auto Pop Out process. An example is how long to wait for the "Ready to Fly" button to appear because auto-clicking it. Depending on the speed of your machine, you can adjust the wait timer to speed up or slow down the auto pop out process. Please see the section "User Profile Data Files" in the documentation for instruction to edit appSettings.json file.
<hr/>
## Application Features
* Display resolution independent. Supports 1080p/1440p/4k display.
* Support multiple user defined profiles to save panel locations to be recalled later.
* Intuitive user interface to defined location of panels to be popped out.
* Cold Start feature. Panels can be popped out and recalled later even when they're not powered on.
* Auto Panning feature remembers the cockpit camera angle when you first define the pop out panels. You can now pan, zoom in, and zoom out to identify offscreen panels and the camera angle will be saved and reused. This feature requires the use of Ctrl-Alt-0 and Alt-0 keyboard binding to save custom camera view per plane configuration. If the keyboard binding is currently being used. The auto-panning feature will overwrite the saved camera view if enabled.
* Fine-grain control in positioning panels down to pixel level.
* User-friendly features such as application Always on Top and Auto Start as MSFS starts.
* Auto save feature. All profile and panel changes get save automatically.
## History: Pop Out Panel Positioning Annoyance
@ -31,45 +68,47 @@ With v3.0, redesign from the ground up about how to pop out and separate the pan
## How to Use?
[Here](images/doc/userguide.mp4) or [Online](https://vimeo.com/668430955) is a video of how the app works.
1. Start the application **MSFSPopoutPanelManager.exe** and it will automatically connect when MSFS starts. You maybe prompt to download .NET framework 5.0. Please see the screenshot below to download and install x64 desktop version of the framework.
1. Start the application **MSFSPopoutPanelManager.exe** and it will automatically connect when MSFS/SimConnect starts. You maybe prompt to download .NET framework 5.0. Please see the screenshot below to download and install x64 desktop version of the framework.
<p align="center">
<img src="images/doc/frameworkdownload.png" width="1000" hspace="10"/>
</p>
2. First create a new plane profile (for example A32NX by FlybyWire)
2. First start the game and start a flight. Then, in the app, create a new profile (for example: Cessna 172 G1000)
<p align="center">
<img src="images/doc/v3.0/s5.png" width="600" hspace="10"/>
<img src="images/doc/v3.2/screenshot1.png" width="600" hspace="10"/>
</p>
3. Once the game has started and you're at the beginning of flight, please click "Start Panel Selection" to define where the pop out panels will be using LEFT CLICK. Use CTRL-LEFT CLICK when done to complete the selection.
3. If you want to associate the profile to the current plane to use the Auto Pop Out feature, click "Add Binding".
<p align="center">
<img src="images/doc/v3.2/screenshot2.png" width="600" hspace="10"/>
</p>
3. Now you're ready to select the panels you want to pop out. Please click "Start Panel Selection" to define where the pop out panels will be using LEFT CLICK. Use CTRL-LEFT CLICK when done to complete the selection. You can also move the number circles at this point to do final adjustment.
<p align="center">
<img src="images/doc/v3.0/s1.png" width="1000" hspace="10"/>
<img src="images/doc/v3.2/screenshot3.png" width="1000" hspace="10"/>
</p>
4. Now, click "Start Pop Out". At this point, please be patient. The application will start popping out and separating panels one by one and you will see a lot of movement on the screen. If something goes wrong, just follow the instruction in the status message and try again.
4. Now, click "Start Pop Out". At this point, please be patient. The application will start popping out and separating panels one by one and you will see a lot of movements on screen. If something goes wrong, just follow the instruction in the status message and try again.
5. Once the process is done, you will see a list of panels line up in the upper left corner of the screen. All the panels are given a default name. You can name them anything you want if desire.
<p align="center">
<img src="images/doc/v3.0/s2.png" width="1000" hspace="10"/>
<img src="images/doc/v3.2/screenshot4.png" width="1000" hspace="10"/>
</p>
6. Now, start the panel configuration by dragging the pop out panels into their final position. You can also type value directly into the data grid to move and resize a panel. The +/- pixel buttons by the lower left corner of the grid allow you to change panel position at the chosen increment/decrement by selecting the datagrid cell (X-Pos, Y-Pos, Width, Height). You can also select "Always on Top" and "Hide Titlebar" if desire. Once all the panels are at their final position, just click "Lock Panel" to prevent further panel changes.
6. Now, start the panel configuration by dragging the pop out panels into their final position (to your main monitor or other monitors). You can also type value directly into the data grid to move and resize a panel. The +/- pixel buttons by the lower left corner of the grid allow you to change panel position at the chosen increment/decrement by selecting the datagrid cell (X-Pos, Y-Pos, Width, Height). You can also select "Always on Top" and "Hide Titlebar" if desire. Once all the panels are at their final position, just click "Lock Panel" to prevent further panel changes.
<p align="center">
<img src="images/doc/v3.0/s3.png" width="600" hspace="10"/>
<img src="images/doc/v3.2/screenshot5.png" width="600" hspace="10"/>
</p>
<p align="center">
<img src="images/doc/v3.0/s4.png" width="1000" hspace="10"/>
</p>
7. To test if everything is working. Once the profile is saved, please click "Restart" in the File menu. This will close all pop outs, except the built-in ones from the game main menu bar, and you're back to the start of the application. Now click "Start Pop Out" and see the magic happens!
7. To test if everything is working. Once the profile is saved, please click "Restart" in the File menu. This will close all pop out, except the built-in ones from the game main menu bar, and you're back to the start of the application. Now click "Start Pop Out" and see the magic happens!
8. With auto panning feature enabled, you do not have to line up the circles that identified the panels in order for the panels to be popped out. But if you would like to do it manually without auto-panning, on next start of the flight, just line up the panels before clicking "Start Pop Out".
8. With auto panning feature enabled, you do not have to line up the circles that identified the panels in order for the panels to be popped out. But if you would like to do it manually without auto-panning, on next start of the flight, just line up the panels before clicking "Start Pop Out" if needed.
<p align="center">
<img src="images/doc/screenshot3.png" width="1000" hspace="10"/>
@ -87,25 +126,31 @@ The user plane profile data and application settings data are stored as JSON fil
* userdata/userprofiledata.json
* userdata/appsettingdata.json
Note for technical user. If you would like to transfer existing profile data into v3.0 file format, you can first create a new profile in v3.0 of the app with the same panels and save it. Then you can open previous version of the configuration in config/userdata.json. You can match the old JSON attribute of "PanelDestinationList" of (Top, Left, Width, Height) for each panel and transfer it over to the new file JSON attribute of "PanelConfigs". Please edit at your own risk.
**Note for technical user**. If you would like to shorten or extend the wait time for each step during Auto Pop Out, Panel, you can edit the following JSON element in appsettingdata.json file. The wait time is in seconds. If you don't see this JSON element, just update any File->Preferences setting and it will trigger this JSON element to be saved into the file.
"AutoPopOutPanelsWaitDelay": {
"ReadyToFlyButton": 3,
"InitialCockpitView": 3,
"InstrumentationPowerOn": 2
}
## Current Known Issue
* Sometimes when using the auto-panning feature, the keyboard combination of Ctrl-Alt-0 and Alt-0 do not work to save and load panel panning coordinates. First try to restart the flightsim and it usually fixes the problem. Otherwise, the only way to fix this is to redo the profile if you want the auto-panning feature since the camera angle is only being saved during the initial creation of the profile. The is another MSFS bug.
* If running the game in windows mode on your non-primary monitor in a multi-monitor setup with different display resolution, panel identification and separation may not work correctly.
* Current application package size is bigger than previous version of the application because it is not a single EXE file package. With added feature of exception logging and stack trace to support user feedback and troubleshooting, a Single EXE package in .NET 5.0 as well as .NET 6.0 has a bug that stack trace information is not complete. Hopefully, Microsoft will be fixing this problem.
## Common Problem Resolution
* Unable to pop out panels when creating a profile for the first time with error such as "Unable to pop out panel #X". If the panel is not being obstructed, by changing the sequence of the pop out when defining the profile may help solve the issue. Currently there are some panels in certain plane configuration that does not follow predefined MSFS pop out rule.
* Unable to pop out panels when creating a profile for the first time with error such as "Unable to pop out panel #X". If the panel is not being obstructed by another window, by changing the sequence of the pop out when defining the profile may help solve the issue. Currently there are some panels in certain plane configuration that does not follow predefined MSFS pop out rule.
* Unable to pop out panels on subsequent flight. Please follow status message instruction. Also, if using auto-panning, Ctrl-Alt-0 may not have been saved correctly during profile creation. You will be able to fix this by manually line up the panel circles identifier and do a force save view by pressing Ctrl-Alt-0.
* Unable to pop out panels on subsequent flight. Please follow status message instruction. Also, if using auto-panning, Ctrl-Alt-0 may not have been saved correctly during profile creation. You can trigger a force camera angle save by clicking the "Save Auto Panning Camera" button for the profile.
* Unable to pop out ALL panels. This may indicate a potential miscount of selected panels (circles) and the number of actual panels that got popped out. You may have duplicate panels in your selection or panels that cannot be popped out.
* If you encounter application crashes or unknown error, please help attach the file **error.log** in the application folder and open a ticket issue in the github repo for this project. This is going to help me troubleshoot the issue and provide hotfixes.
* 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.
## Author
@ -115,13 +160,15 @@ Stanley Kwok
I welcome feedback to help improve the accuracy and usefulness of this application. You are welcome to take a copy of this code to further enhance it and use within your own project. But please abide by licensing terms and keep it open source:)
## Credits
[Tesseract](https://github.com/charlesw/tesseract/) by Charles Weld - .NET wrapper for Tesseract OCR package. For version 1.x of application.
[AForge.NET](http://www.aforgenet.com/framework/) Image recognition library. For version 2.x of the application.
[DarkUI](http://www.darkui.com/) by Robin Perria
[MouseKeyHook](https://github.com/gmamaladze/globalmousekeyhook) by George Mamaladze
[Fody](https://github.com/Fody/Fody) .NET assemblies weaver by Fody
[MahApps.Metro Dark Theme](https://github.com/MahApps/MahApps.Metro) by Jan Karger, Dennis Daume, Brendan Forster, Paul Jenkins, Jake Ginnivan, Alex Mitchell
[Hardcodet NotifyIcon](https://github.com/hardcodet/wpf-notifyicon) by Philipp Sumi, Robin Krom, Jan Karger
[WPF CalcBinding](https://github.com/Alex141/CalcBinding) by Alexander Zinchenko

View file

@ -1,24 +0,0 @@
using Newtonsoft.Json;
namespace MSFSPopoutPanelManager.Shared
{
public class AppSettingData
{
public AppSettingData()
{
// Set defaults
MinimizeToTray = false;
AlwaysOnTop = true;
UseAutoPanning = true;
}
public bool MinimizeToTray { get; set; }
public bool AlwaysOnTop { get; set; }
public bool UseAutoPanning { get; set; }
[JsonIgnore]
public bool AutoStart { get; set; }
}
}

View file

@ -0,0 +1,39 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace MSFSPopoutPanelManager.Shared
{
[ValueConversion(typeof(bool), typeof(Visibility))]
public sealed class BoolToVisibilityConverter : IValueConverter
{
public Visibility TrueValue { get; set; }
public Visibility FalseValue { get; set; }
public BoolToVisibilityConverter()
{
// set defaults
TrueValue = Visibility.Visible;
FalseValue = Visibility.Collapsed;
}
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (!(value is bool))
return null;
return (bool)value ? TrueValue : FalseValue;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (Equals(value, TrueValue))
return true;
if (Equals(value, FalseValue))
return false;
return null;
}
}
}

View file

@ -1,53 +0,0 @@
using System.Linq;
using System.ComponentModel;
namespace MSFSPopoutPanelManager.Shared
{
public class DataStore : INotifyPropertyChanged
{
private int _activeProfileId;
public event PropertyChangedEventHandler PropertyChanged;
public DataStore()
{
_activeProfileId = -1;
ActiveUserProfile = null;
ActiveProfilePanelCoordinates = new BindingList<PanelSourceCoordinate>();
PanelConfigs = new BindingList<PanelConfig>();
}
public BindingList<UserProfileData> UserProfiles { get; set; }
public BindingList<PanelSourceCoordinate> ActiveProfilePanelCoordinates { get; set; }
public BindingList<PanelConfig> PanelConfigs { get; set; }
public UserProfileData ActiveUserProfile { get; set; }
public int ActiveUserProfileId
{
get
{
return _activeProfileId;
}
set
{
_activeProfileId = value;
if(value == -1)
{
ActiveUserProfile = null;
ActiveProfilePanelCoordinates.Clear();
}
else
{
ActiveUserProfile = UserProfiles.ToList().Find(x => x.ProfileId == value);
ActiveProfilePanelCoordinates.Clear();
ActiveUserProfile.PanelSourceCoordinates.ForEach(c => ActiveProfilePanelCoordinates.Add(c));
}
}
}
}
}

13
Shared/FileIO.cs Normal file
View file

@ -0,0 +1,13 @@
using System.IO;
namespace MSFSPopoutPanelManager.Shared
{
public class FileIo
{
public static string GetUserDataFilePath()
{
var startupPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
return Path.Combine(startupPath, "userdata");
}
}
}

View file

@ -5,9 +5,8 @@ namespace MSFSPopoutPanelManager.Shared
public class Logger
{
public static event EventHandler<EventArgs<StatusMessage>> OnStatusLogged;
public static event EventHandler<EventArgs<StatusMessage>> OnBackgroundStatusLogged;
public static void Status(string message, StatusMessageType MessageType)
public static void LogStatus(string message, StatusMessageType MessageType)
{
var statusMessage = new StatusMessage() { Message = message, MessageType = MessageType };
OnStatusLogged?.Invoke(null, new EventArgs<StatusMessage>(statusMessage));
@ -15,18 +14,7 @@ namespace MSFSPopoutPanelManager.Shared
public static void ClearStatus()
{
Status(String.Empty, StatusMessageType.Info);
}
public static void BackgroundStatus(string message, StatusMessageType MessageType)
{
var statusMessage = new StatusMessage() { Message = message, MessageType = MessageType };
OnBackgroundStatusLogged?.Invoke(null, new EventArgs<StatusMessage>(statusMessage));
}
public static void ClearBackgroundStatus()
{
BackgroundStatus(String.Empty, StatusMessageType.Info);
LogStatus(String.Empty, StatusMessageType.Info);
}
}
@ -43,7 +31,7 @@ namespace MSFSPopoutPanelManager.Shared
Error
}
public class PopoutManagerException : Exception
public class PopoutManagerException : Exception
{
public PopoutManagerException(string message) : base(message) { }
}

131
Shared/ObjectExtension.cs Normal file
View file

@ -0,0 +1,131 @@
using MSFSPopoutPanelManager.Shared.ArrayExtensions;
using System;
using System.Collections.Generic;
using System.Reflection;
namespace MSFSPopoutPanelManager.Shared
{
public static class ObjectExtensions
{
private static readonly MethodInfo CloneMethod = typeof(Object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance);
public static bool IsPrimitive(this Type type)
{
if (type == typeof(String)) return true;
return (type.IsValueType & type.IsPrimitive);
}
public static Object Copy(this Object originalObject)
{
return InternalCopy(originalObject, new Dictionary<Object, Object>(new ReferenceEqualityComparer()));
}
private static Object InternalCopy(Object originalObject, IDictionary<Object, Object> visited)
{
if (originalObject == null) return null;
var typeToReflect = originalObject.GetType();
if (IsPrimitive(typeToReflect)) return originalObject;
if (visited.ContainsKey(originalObject)) return visited[originalObject];
if (typeof(Delegate).IsAssignableFrom(typeToReflect)) return null;
var cloneObject = CloneMethod.Invoke(originalObject, null);
if (typeToReflect.IsArray)
{
var arrayType = typeToReflect.GetElementType();
if (IsPrimitive(arrayType) == false)
{
Array clonedArray = (Array)cloneObject;
clonedArray.ForEach((array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), visited), indices));
}
}
visited.Add(originalObject, cloneObject);
CopyFields(originalObject, visited, cloneObject, typeToReflect);
RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect);
return cloneObject;
}
private static void RecursiveCopyBaseTypePrivateFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect)
{
if (typeToReflect.BaseType != null)
{
RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect.BaseType);
CopyFields(originalObject, visited, cloneObject, typeToReflect.BaseType, BindingFlags.Instance | BindingFlags.NonPublic, info => info.IsPrivate);
}
}
private static void CopyFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy, Func<FieldInfo, bool> filter = null)
{
foreach (FieldInfo fieldInfo in typeToReflect.GetFields(bindingFlags))
{
if (filter != null && filter(fieldInfo) == false) continue;
if (IsPrimitive(fieldInfo.FieldType)) continue;
var originalFieldValue = fieldInfo.GetValue(originalObject);
var clonedFieldValue = InternalCopy(originalFieldValue, visited);
fieldInfo.SetValue(cloneObject, clonedFieldValue);
}
}
public static T Copy<T>(this T original)
{
return (T)Copy((Object)original);
}
}
public class ReferenceEqualityComparer : EqualityComparer<Object>
{
public override bool Equals(object x, object y)
{
return ReferenceEquals(x, y);
}
public override int GetHashCode(object obj)
{
if (obj == null) return 0;
return obj.GetHashCode();
}
}
namespace ArrayExtensions
{
public static class ArrayExtensions
{
public static void ForEach(this Array array, Action<Array, int[]> action)
{
if (array.LongLength == 0) return;
ArrayTraverse walker = new ArrayTraverse(array);
do action(array, walker.Position);
while (walker.Step());
}
}
internal class ArrayTraverse
{
public int[] Position;
private int[] maxLengths;
public ArrayTraverse(Array array)
{
maxLengths = new int[array.Rank];
for (int i = 0; i < array.Rank; ++i)
{
maxLengths[i] = array.GetLength(i) - 1;
}
Position = new int[array.Rank];
}
public bool Step()
{
for (int i = 0; i < Position.Length; ++i)
{
if (Position[i] < maxLengths[i])
{
Position[i]++;
for (int j = 0; j < i; j++)
{
Position[j] = 0;
}
return true;
}
}
return false;
}
}
}
}

21
Shared/Shared.csproj Normal file
View file

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0-windows</TargetFramework>
<RootNamespace>MSFSPopoutPanelManager.Shared</RootNamespace>
<PackageId>MSFS 2020 Popout Panel Manager Shared</PackageId>
<Product>MSFS 2020 Popout Panel Manager Shared</Product>
<Authors>Stanley Kwok</Authors>
<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>
<Platforms>AnyCPU;x64</Platforms>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Toolkit" Version="7.1.2" />
</ItemGroup>
</Project>

View file

@ -1,68 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace MSFSPopoutPanelManager.Shared
{
public class UserProfileData
{
public UserProfileData()
{
PanelSourceCoordinates = new List<PanelSourceCoordinate>();
PanelConfigs = new List<PanelConfig>();
IsLocked = false;
}
public int ProfileId { get; set; }
public string ProfileName { get; set; }
public bool IsDefaultProfile { get; set; }
public bool IsLocked { get; set; }
public List<PanelSourceCoordinate> PanelSourceCoordinates;
public List<PanelConfig> PanelConfigs { get; set; }
public void Reset()
{
PanelSourceCoordinates.Clear();
PanelConfigs.Clear();
IsLocked = false;
}
}
public class PanelSourceCoordinate
{
public int PanelIndex { get; set; }
public int X { get; set; }
public int Y { get; set; }
}
public class PanelConfig
{
public int PanelIndex { get; set; }
public string PanelName { get; set; }
public PanelType PanelType { get; set; }
public int Top { get; set; }
public int Left { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public bool AlwaysOnTop { get; set; }
public bool HideTitlebar { get; set; }
[JsonIgnore]
public IntPtr PanelHandle { get; set; }
}
}

View file

@ -1,120 +0,0 @@

namespace MSFSPopoutPanelManager.UI
{
partial class AddProfileForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.darkLabel2 = new DarkUI.Controls.DarkLabel();
this.textBoxProfileName = new DarkUI.Controls.DarkTextBox();
this.buttonOK = new System.Windows.Forms.Button();
this.buttonCancel = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// darkLabel2
//
this.darkLabel2.AutoSize = true;
this.darkLabel2.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.darkLabel2.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.darkLabel2.Location = new System.Drawing.Point(33, 37);
this.darkLabel2.Name = "darkLabel2";
this.darkLabel2.Size = new System.Drawing.Size(96, 20);
this.darkLabel2.TabIndex = 2;
this.darkLabel2.Text = "Profile Name";
//
// textBoxProfileName
//
this.textBoxProfileName.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(69)))), ((int)(((byte)(73)))), ((int)(((byte)(74)))));
this.textBoxProfileName.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.textBoxProfileName.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.textBoxProfileName.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.textBoxProfileName.Location = new System.Drawing.Point(177, 35);
this.textBoxProfileName.MaxLength = 50;
this.textBoxProfileName.Name = "textBoxProfileName";
this.textBoxProfileName.Size = new System.Drawing.Size(356, 27);
this.textBoxProfileName.TabIndex = 3;
this.textBoxProfileName.TextChanged += new System.EventHandler(this.textBoxProfileName_TextChanged);
this.textBoxProfileName.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.textBoxProfileName_KeyPress);
//
// buttonOK
//
this.buttonOK.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonOK.Enabled = false;
this.buttonOK.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonOK.ForeColor = System.Drawing.Color.White;
this.buttonOK.Location = new System.Drawing.Point(268, 119);
this.buttonOK.Name = "buttonOK";
this.buttonOK.Size = new System.Drawing.Size(118, 35);
this.buttonOK.TabIndex = 22;
this.buttonOK.Text = "OK";
this.buttonOK.UseVisualStyleBackColor = false;
this.buttonOK.Click += new System.EventHandler(this.buttonOK_Click);
//
// buttonCancel
//
this.buttonCancel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonCancel.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonCancel.ForeColor = System.Drawing.Color.White;
this.buttonCancel.Location = new System.Drawing.Point(410, 119);
this.buttonCancel.Name = "buttonCancel";
this.buttonCancel.Size = new System.Drawing.Size(118, 35);
this.buttonCancel.TabIndex = 23;
this.buttonCancel.Text = "Cancel";
this.buttonCancel.UseVisualStyleBackColor = false;
this.buttonCancel.Click += new System.EventHandler(this.buttonCancel_Click);
//
// AddProfileForm
//
this.AcceptButton = this.buttonOK;
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.buttonCancel;
this.ClientSize = new System.Drawing.Size(562, 173);
this.ControlBox = false;
this.Controls.Add(this.buttonCancel);
this.Controls.Add(this.buttonOK);
this.Controls.Add(this.textBoxProfileName);
this.Controls.Add(this.darkLabel2);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "AddProfileForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Add Profile";
this.TopMost = true;
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private DarkUI.Controls.DarkLabel darkLabel2;
private DarkUI.Controls.DarkTextBox textBoxProfileName;
private System.Windows.Forms.Button buttonOK;
private System.Windows.Forms.Button buttonCancel;
}
}

View file

@ -1,42 +0,0 @@
using DarkUI.Forms;
using MSFSPopoutPanelManager.UIController;
using System;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager.UI
{
public partial class AddProfileForm : DarkForm
{
public AddProfileForm()
{
InitializeComponent();
}
public string ProfileName { get { return textBoxProfileName.Text.Trim(); } }
private void textBoxProfileName_KeyPress(object sender, KeyPressEventArgs e)
{
e.Handled = !(Char.IsLetterOrDigit(e.KeyChar) ||
Char.IsPunctuation(e.KeyChar) ||
e.KeyChar == (char)Keys.Space ||
e.KeyChar == (char)Keys.Back);
}
private void textBoxProfileName_TextChanged(object sender, EventArgs e)
{
buttonOK.Enabled = textBoxProfileName.Text.Trim().Length > 0;
}
private void buttonCancel_Click(object sender, EventArgs e)
{
DialogResult = DialogResult.Cancel;
Close();
}
private void buttonOK_Click(object sender, EventArgs e)
{
DialogResult = DialogResult.OK;
Close();
}
}
}

View file

@ -1,60 +0,0 @@
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View file

@ -1,119 +0,0 @@

namespace MSFSPopoutPanelManager.UI
{
partial class ConfirmDialogForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.buttonYes = new System.Windows.Forms.Button();
this.buttonNo = new System.Windows.Forms.Button();
this.labelMessage = new DarkUI.Controls.DarkLabel();
this.buttonOK = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// buttonYes
//
this.buttonYes.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonYes.DialogResult = System.Windows.Forms.DialogResult.Yes;
this.buttonYes.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonYes.ForeColor = System.Drawing.Color.White;
this.buttonYes.Location = new System.Drawing.Point(268, 93);
this.buttonYes.Name = "buttonYes";
this.buttonYes.Size = new System.Drawing.Size(118, 35);
this.buttonYes.TabIndex = 22;
this.buttonYes.Text = "Yes";
this.buttonYes.UseVisualStyleBackColor = false;
this.buttonYes.Click += new System.EventHandler(this.buttonYes_Click);
//
// buttonNo
//
this.buttonNo.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonNo.DialogResult = System.Windows.Forms.DialogResult.No;
this.buttonNo.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonNo.ForeColor = System.Drawing.Color.White;
this.buttonNo.Location = new System.Drawing.Point(410, 93);
this.buttonNo.Name = "buttonNo";
this.buttonNo.Size = new System.Drawing.Size(118, 35);
this.buttonNo.TabIndex = 23;
this.buttonNo.Text = "No";
this.buttonNo.UseVisualStyleBackColor = false;
this.buttonNo.Click += new System.EventHandler(this.buttonNo_Click);
//
// labelMessage
//
this.labelMessage.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.labelMessage.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.labelMessage.Location = new System.Drawing.Point(34, 25);
this.labelMessage.Name = "labelMessage";
this.labelMessage.Size = new System.Drawing.Size(494, 65);
this.labelMessage.TabIndex = 2;
this.labelMessage.Text = "Message";
//
// buttonOK
//
this.buttonOK.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonOK.DialogResult = System.Windows.Forms.DialogResult.OK;
this.buttonOK.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonOK.ForeColor = System.Drawing.Color.White;
this.buttonOK.Location = new System.Drawing.Point(410, 93);
this.buttonOK.Name = "buttonOK";
this.buttonOK.Size = new System.Drawing.Size(118, 35);
this.buttonOK.TabIndex = 20;
this.buttonOK.Text = "OK";
this.buttonOK.UseVisualStyleBackColor = false;
this.buttonOK.Visible = false;
//
// ConfirmDialogForm
//
this.AcceptButton = this.buttonYes;
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.buttonNo;
this.ClientSize = new System.Drawing.Size(562, 140);
this.ControlBox = false;
this.Controls.Add(this.buttonOK);
this.Controls.Add(this.buttonNo);
this.Controls.Add(this.buttonYes);
this.Controls.Add(this.labelMessage);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "ConfirmDialogForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Title";
this.TopMost = true;
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button buttonYes;
private System.Windows.Forms.Button buttonNo;
private DarkUI.Controls.DarkLabel labelMessage;
private System.Windows.Forms.Button buttonOK;
}
}

View file

@ -1,30 +0,0 @@
using DarkUI.Forms;
using System;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager.UI
{
public partial class ConfirmDialogForm : DarkForm
{
public ConfirmDialogForm(string title, string Message, bool cancellable = true)
{
InitializeComponent();
Text = title;
labelMessage.Text = Message;
buttonYes.Visible = cancellable;
buttonNo.Visible = cancellable;
buttonOK.Visible = !cancellable;
}
private void buttonYes_Click(object sender, EventArgs e)
{
Close();
}
private void buttonNo_Click(object sender, EventArgs e)
{
Close();
}
}
}

View file

@ -1,60 +0,0 @@
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View file

@ -1,78 +0,0 @@

namespace MSFSPopoutPanelManager.UI
{
partial class PopoutCoorOverlayForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(PopoutCoorOverlayForm));
this.lblPanelIndex = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// lblPanelIndex
//
this.lblPanelIndex.BackColor = System.Drawing.Color.Transparent;
this.lblPanelIndex.CausesValidation = false;
this.lblPanelIndex.Font = new System.Drawing.Font("Segoe UI", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.lblPanelIndex.ForeColor = System.Drawing.Color.WhiteSmoke;
this.lblPanelIndex.Image = ((System.Drawing.Image)(resources.GetObject("lblPanelIndex.Image")));
this.lblPanelIndex.Location = new System.Drawing.Point(0, 0);
this.lblPanelIndex.Margin = new System.Windows.Forms.Padding(0);
this.lblPanelIndex.Name = "lblPanelIndex";
this.lblPanelIndex.Size = new System.Drawing.Size(55, 46);
this.lblPanelIndex.TabIndex = 0;
this.lblPanelIndex.Text = "1";
this.lblPanelIndex.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// PopoutCoorOverlayForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CausesValidation = false;
this.ClientSize = new System.Drawing.Size(55, 46);
this.ControlBox = false;
this.Controls.Add(this.lblPanelIndex);
this.DoubleBuffered = true;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "PopoutCoorOverlayForm";
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
this.Text = "PopoutCoorOverlay";
this.TopMost = true;
this.TransparencyKey = System.Drawing.SystemColors.Control;
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Label lblPanelIndex;
}
}

View file

@ -1,12 +0,0 @@
using System.Windows.Forms;
namespace MSFSPopoutPanelManager.UI
{
public partial class PopoutCoorOverlayForm : Form
{
public PopoutCoorOverlayForm()
{
InitializeComponent();
}
}
}

View file

@ -1,80 +0,0 @@
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="lblPanelIndex.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAAD4AAAAsCAYAAAA93wvUAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL
EAAACxABrSO9dQAAAyRJREFUaEPl2s2uDEEYxvFDsLD0sRNrzo5EENyGzy0W3ABXwA0I1iQsbIRICDfB
mgVnhWPnOzKefzJvclJ5+nRXdfX00G/ySyZvvVNVx4zuqqlemc1mk2STU2CTU2CTFWyVo3JNHslrWZcf
c7wmRxs11PIe19cgbLKHfXJD3ktu8B7eSx+u76psssBuuSU/JeKt3JYLclio2THHa3K0UfNOIuiDvqhx
Y1Vhk5lOyych/shDOS6utskWOSkPhD4I+jwjrr43m+xom/BpRbySVXG1OeiDviIYg7FcbTGb7GCnPBHi
u1wVV9fHFaFvgrEY09UVsckW/Os/FuKzcEV2dTXQN2MQjFntk7fJFneE+CgHxNXUxBiMRTC2q8lmk5vg
YkN8kyE/6RRjfRXinLiaLDbZgNtLXL0vi6sZEmMSLH72iqvpzCYbxBX8hbj2RYirfe+vvE0a++WX/JaD
89wYGJs5MBfm5Go6sUmDpSRxT1z7It0Xgjm59k5sMsHm4YMQJ8TVLNIpIZhT8cbGJhNcUQnW0ywtXc0i
MYdY2xffWWwycV2IavfQCmItwZbWtbeyyQR7ZoKdlGsfA3MhmJtrb2WTiTdCHBLXPgbmQjA3197KJhOx
Vh50f5yJuRDMzbW3sslE/LjADwiufQzMhWBurr2VTSYm+4cv41d9lxCDftUne3Gb7O0sFjDszlz7GGKn
OOgC5pgQk1uyLtsmhTkQg29SsEzbUn57JwbfloJjHe6Zy/BDBAcOzKXXUZNNNuBYh3gurn0RXgrR+0Jr
kw1YwMRi5pK4miExJsEcei+mbHITZ4Xgp96xfl4+L64mi022GPNA4a64mmw22YINwsYjpCPi6mqg7/jv
xZjVNko22QEHeM+E4GDvori6PuiTExviqYx+aBi2S3ztCa64NY6JuWXF1ZtgDMZytcVsMhMXvI0PBrDA
KHkwgBVZ+mBAlXMyxyYL7BHurfGjBRGPgnAVZhvJLYhPDrwmRxs16aMg5OjTjVWFTfbAauqmrElu8B7e
+089/JNi88C9ly0te2Ye7foinHmB1/G4FzXUFm84Stjk/2+28hdDyseyXx6hNQAAAABJRU5ErkJggg==
</value>
</data>
</root>

View file

@ -1,354 +0,0 @@

namespace MSFSPopoutPanelManager.UI
{
partial class StartupForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(StartupForm));
this.panelSteps = new System.Windows.Forms.Panel();
this.labelMsfsConnection = new System.Windows.Forms.Label();
this.panelStatus = new System.Windows.Forms.Panel();
this.darkLabel3 = new DarkUI.Controls.DarkLabel();
this.txtBoxStatus = new DarkUI.Controls.DarkTextBox();
this.notifyIcon1 = new System.Windows.Forms.NotifyIcon(this.components);
this.lblVersion = new DarkUI.Controls.DarkLabel();
this.darkMenuStrip1 = new DarkUI.Controls.DarkMenuStrip();
this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.menuItem_restart = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
this.preferencesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.menuItem_alwaysOnTop = new System.Windows.Forms.ToolStripMenuItem();
this.menuItem_autoPanning = new System.Windows.Forms.ToolStripMenuItem();
this.menuItem_autoStart = new System.Windows.Forms.ToolStripMenuItem();
this.menuItem_minimizeToSystemTray = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
this.menuItem_exit = new System.Windows.Forms.ToolStripMenuItem();
this.viewToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.menuItem_minimizeAllPanels = new System.Windows.Forms.ToolStripMenuItem();
this.menuItem_help = new System.Windows.Forms.ToolStripMenuItem();
this.menuItem_userGuide = new System.Windows.Forms.ToolStripMenuItem();
this.menuItem_downloadLatestRelease = new System.Windows.Forms.ToolStripMenuItem();
this.panelStatus.SuspendLayout();
this.darkMenuStrip1.SuspendLayout();
this.SuspendLayout();
//
// panelSteps
//
this.panelSteps.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(61)))), ((int)(((byte)(101)))), ((int)(((byte)(171)))));
this.panelSteps.Location = new System.Drawing.Point(0, 30);
this.panelSteps.Name = "panelSteps";
this.panelSteps.Size = new System.Drawing.Size(915, 405);
this.panelSteps.TabIndex = 0;
//
// labelMsfsConnection
//
this.labelMsfsConnection.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.labelMsfsConnection.AutoSize = true;
this.labelMsfsConnection.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.labelMsfsConnection.ForeColor = System.Drawing.Color.Red;
this.labelMsfsConnection.Location = new System.Drawing.Point(763, 511);
this.labelMsfsConnection.Name = "labelMsfsConnection";
this.labelMsfsConnection.RightToLeft = System.Windows.Forms.RightToLeft.Yes;
this.labelMsfsConnection.Size = new System.Drawing.Size(33, 20);
this.labelMsfsConnection.TabIndex = 10;
this.labelMsfsConnection.Text = " ";
this.labelMsfsConnection.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// panelStatus
//
this.panelStatus.Controls.Add(this.darkLabel3);
this.panelStatus.Controls.Add(this.txtBoxStatus);
this.panelStatus.Location = new System.Drawing.Point(0, 435);
this.panelStatus.Name = "panelStatus";
this.panelStatus.Size = new System.Drawing.Size(915, 74);
this.panelStatus.TabIndex = 20;
//
// darkLabel3
//
this.darkLabel3.AutoSize = true;
this.darkLabel3.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.darkLabel3.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.darkLabel3.Location = new System.Drawing.Point(12, 16);
this.darkLabel3.Name = "darkLabel3";
this.darkLabel3.Size = new System.Drawing.Size(49, 20);
this.darkLabel3.TabIndex = 24;
this.darkLabel3.Text = "Status";
//
// txtBoxStatus
//
this.txtBoxStatus.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(69)))), ((int)(((byte)(73)))), ((int)(((byte)(74)))));
this.txtBoxStatus.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.txtBoxStatus.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.txtBoxStatus.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.txtBoxStatus.Location = new System.Drawing.Point(67, 16);
this.txtBoxStatus.Multiline = true;
this.txtBoxStatus.Name = "txtBoxStatus";
this.txtBoxStatus.ReadOnly = true;
this.txtBoxStatus.Size = new System.Drawing.Size(835, 46);
this.txtBoxStatus.TabIndex = 23;
//
// notifyIcon1
//
this.notifyIcon1.Icon = ((System.Drawing.Icon)(resources.GetObject("notifyIcon1.Icon")));
this.notifyIcon1.Text = "MSFS 2020 Pop Out Panel Manager";
this.notifyIcon1.Visible = true;
this.notifyIcon1.DoubleClick += new System.EventHandler(this.notifyIcon1_DoubleClick);
//
// lblVersion
//
this.lblVersion.AutoSize = true;
this.lblVersion.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.lblVersion.Location = new System.Drawing.Point(423, 520);
this.lblVersion.Name = "lblVersion";
this.lblVersion.Size = new System.Drawing.Size(48, 15);
this.lblVersion.TabIndex = 24;
this.lblVersion.Text = "Version ";
//
// darkMenuStrip1
//
this.darkMenuStrip1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(63)))), ((int)(((byte)(65)))));
this.darkMenuStrip1.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.darkMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.fileToolStripMenuItem,
this.viewToolStripMenuItem,
this.menuItem_help});
this.darkMenuStrip1.Location = new System.Drawing.Point(0, 0);
this.darkMenuStrip1.Name = "darkMenuStrip1";
this.darkMenuStrip1.Padding = new System.Windows.Forms.Padding(3, 2, 0, 2);
this.darkMenuStrip1.Size = new System.Drawing.Size(914, 28);
this.darkMenuStrip1.TabIndex = 29;
this.darkMenuStrip1.Text = "darkMenuStrip1";
//
// fileToolStripMenuItem
//
this.fileToolStripMenuItem.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(63)))), ((int)(((byte)(65)))));
this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.menuItem_restart,
this.toolStripSeparator1,
this.preferencesToolStripMenuItem,
this.toolStripSeparator2,
this.menuItem_exit});
this.fileToolStripMenuItem.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.fileToolStripMenuItem.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.fileToolStripMenuItem.Name = "fileToolStripMenuItem";
this.fileToolStripMenuItem.Size = new System.Drawing.Size(44, 24);
this.fileToolStripMenuItem.Text = "File";
//
// menuItem_restart
//
this.menuItem_restart.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(63)))), ((int)(((byte)(65)))));
this.menuItem_restart.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.menuItem_restart.Name = "menuItem_restart";
this.menuItem_restart.Size = new System.Drawing.Size(154, 24);
this.menuItem_restart.Text = "Restart";
//
// toolStripSeparator1
//
this.toolStripSeparator1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(63)))), ((int)(((byte)(65)))));
this.toolStripSeparator1.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.toolStripSeparator1.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
this.toolStripSeparator1.Name = "toolStripSeparator1";
this.toolStripSeparator1.Size = new System.Drawing.Size(151, 6);
//
// preferencesToolStripMenuItem
//
this.preferencesToolStripMenuItem.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(63)))), ((int)(((byte)(65)))));
this.preferencesToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.menuItem_alwaysOnTop,
this.menuItem_autoPanning,
this.menuItem_autoStart,
this.menuItem_minimizeToSystemTray});
this.preferencesToolStripMenuItem.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.preferencesToolStripMenuItem.Name = "preferencesToolStripMenuItem";
this.preferencesToolStripMenuItem.Size = new System.Drawing.Size(154, 24);
this.preferencesToolStripMenuItem.Text = "Preferences";
//
// menuItem_alwaysOnTop
//
this.menuItem_alwaysOnTop.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(63)))), ((int)(((byte)(65)))));
this.menuItem_alwaysOnTop.CheckOnClick = true;
this.menuItem_alwaysOnTop.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.menuItem_alwaysOnTop.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None;
this.menuItem_alwaysOnTop.Name = "menuItem_alwaysOnTop";
this.menuItem_alwaysOnTop.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.T)));
this.menuItem_alwaysOnTop.Size = new System.Drawing.Size(256, 24);
this.menuItem_alwaysOnTop.Text = "Always on Top ";
this.menuItem_alwaysOnTop.TextImageRelation = System.Windows.Forms.TextImageRelation.TextBeforeImage;
//
// menuItem_autoPanning
//
this.menuItem_autoPanning.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(63)))), ((int)(((byte)(65)))));
this.menuItem_autoPanning.CheckOnClick = true;
this.menuItem_autoPanning.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.menuItem_autoPanning.Name = "menuItem_autoPanning";
this.menuItem_autoPanning.Size = new System.Drawing.Size(256, 24);
this.menuItem_autoPanning.Text = "Auto Panning";
//
// menuItem_autoStart
//
this.menuItem_autoStart.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(63)))), ((int)(((byte)(65)))));
this.menuItem_autoStart.CheckOnClick = true;
this.menuItem_autoStart.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.menuItem_autoStart.Name = "menuItem_autoStart";
this.menuItem_autoStart.Size = new System.Drawing.Size(256, 24);
this.menuItem_autoStart.Text = "Auto Start";
//
// menuItem_minimizeToSystemTray
//
this.menuItem_minimizeToSystemTray.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(63)))), ((int)(((byte)(65)))));
this.menuItem_minimizeToSystemTray.CheckOnClick = true;
this.menuItem_minimizeToSystemTray.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.menuItem_minimizeToSystemTray.Name = "menuItem_minimizeToSystemTray";
this.menuItem_minimizeToSystemTray.Size = new System.Drawing.Size(256, 24);
this.menuItem_minimizeToSystemTray.Text = "Minimize to Tray";
//
// toolStripSeparator2
//
this.toolStripSeparator2.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(63)))), ((int)(((byte)(65)))));
this.toolStripSeparator2.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.toolStripSeparator2.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
this.toolStripSeparator2.Name = "toolStripSeparator2";
this.toolStripSeparator2.Size = new System.Drawing.Size(151, 6);
//
// menuItem_exit
//
this.menuItem_exit.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(63)))), ((int)(((byte)(65)))));
this.menuItem_exit.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.menuItem_exit.Name = "menuItem_exit";
this.menuItem_exit.Size = new System.Drawing.Size(154, 24);
this.menuItem_exit.Text = "Exit";
//
// viewToolStripMenuItem
//
this.viewToolStripMenuItem.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(63)))), ((int)(((byte)(65)))));
this.viewToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.menuItem_minimizeAllPanels});
this.viewToolStripMenuItem.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.viewToolStripMenuItem.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.viewToolStripMenuItem.Name = "viewToolStripMenuItem";
this.viewToolStripMenuItem.Size = new System.Drawing.Size(122, 24);
this.viewToolStripMenuItem.Text = "View";
//
// menuItem_minimizeAllPanels
//
this.menuItem_minimizeAllPanels.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(63)))), ((int)(((byte)(65)))));
this.menuItem_minimizeAllPanels.CheckOnClick = true;
this.menuItem_minimizeAllPanels.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.menuItem_minimizeAllPanels.Name = "menuItem_minimizeAllPanels";
this.menuItem_minimizeAllPanels.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.M)));
this.menuItem_minimizeAllPanels.Size = new System.Drawing.Size(281, 24);
this.menuItem_minimizeAllPanels.Text = "Minimize All Panels ";
//
// menuItem_help
//
this.menuItem_help.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(63)))), ((int)(((byte)(65)))));
this.menuItem_help.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.menuItem_userGuide,
this.menuItem_downloadLatestRelease});
this.menuItem_help.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.menuItem_help.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.menuItem_help.Name = "menuItem_help";
this.menuItem_help.Size = new System.Drawing.Size(53, 24);
this.menuItem_help.Text = "Help";
//
// menuItem_userGuide
//
this.menuItem_userGuide.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(63)))), ((int)(((byte)(65)))));
this.menuItem_userGuide.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.menuItem_userGuide.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.menuItem_userGuide.Name = "menuItem_userGuide";
this.menuItem_userGuide.Size = new System.Drawing.Size(245, 24);
this.menuItem_userGuide.Text = "User Guide";
this.menuItem_userGuide.TextDirection = System.Windows.Forms.ToolStripTextDirection.Horizontal;
//
// menuItem_downloadLatestRelease
//
this.menuItem_downloadLatestRelease.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(63)))), ((int)(((byte)(65)))));
this.menuItem_downloadLatestRelease.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.menuItem_downloadLatestRelease.Name = "menuItem_downloadLatestRelease";
this.menuItem_downloadLatestRelease.Size = new System.Drawing.Size(245, 24);
this.menuItem_downloadLatestRelease.Text = "Download Latest Release";
//
// StartupForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(914, 540);
this.Controls.Add(this.lblVersion);
this.Controls.Add(this.labelMsfsConnection);
this.Controls.Add(this.panelStatus);
this.Controls.Add(this.panelSteps);
this.Controls.Add(this.darkMenuStrip1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.MainMenuStrip = this.darkMenuStrip1;
this.MaximizeBox = false;
this.Name = "StartupForm";
this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
this.Text = "MSFS Pop Out Panel Manager";
this.Load += new System.EventHandler(this.StartupForm_Load);
this.Resize += new System.EventHandler(this.StartupForm_Resize);
this.panelStatus.ResumeLayout(false);
this.panelStatus.PerformLayout();
this.darkMenuStrip1.ResumeLayout(false);
this.darkMenuStrip1.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Panel panelSteps;
private System.Windows.Forms.Label labelMsfsConnection;
private System.Windows.Forms.Panel panelStatus;
private System.Windows.Forms.NotifyIcon notifyIcon1;
private DarkUI.Controls.DarkTextBox txtBoxStatus;
private DarkUI.Controls.DarkLabel lblVersion;
private DarkUI.Controls.DarkLabel darkLabel3;
private DarkUI.Controls.DarkMenuStrip darkMenuStrip1;
private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem menuItem_exit;
private System.Windows.Forms.ToolStripMenuItem menuItem_autoStart;
private System.Windows.Forms.ToolStripMenuItem menuItem_alwaysOnTop;
private System.Windows.Forms.ToolStripMenuItem menuItem_autoPanning;
private System.Windows.Forms.ToolStripMenuItem menuItem_minimizeToSystemTray;
private System.Windows.Forms.ToolStripMenuItem menuItem_help;
private System.Windows.Forms.ToolStripMenuItem menuItem_userGuide;
private System.Windows.Forms.ToolStripMenuItem menuItem_downloadLatestRelease;
private System.Windows.Forms.ToolStripMenuItem menuItem_restart;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator1;
private System.Windows.Forms.ToolStripMenuItem preferencesToolStripMenuItem;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator2;
private System.Windows.Forms.ToolStripMenuItem viewToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem menuItem_minimizeAllPanels;
}
}

View file

@ -1,213 +0,0 @@
using DarkUI.Forms;
using MSFSPopoutPanelManager.Shared;
using MSFSPopoutPanelManager.UIController;
using System;
using System.Diagnostics;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager.UI
{
public partial class StartupForm : DarkForm, IApplicationView
{
private Color ERROR_MESSAGE_COLOR = Color.FromArgb(1, 255, 71, 71);
private Color SUCCESS_MESSAGE_COLOR = Color.LightGreen;
private Color INFO_MESSAGE_COLOR = Color.White;
private SynchronizationContext _syncRoot;
private UserControlPanelSelection _ucPanelSelection;
private UserControlPanelConfiguration _ucPanelConfiguration;
private ApplicationController _controller;
public StartupForm()
{
InitializeComponent();
_ucPanelSelection = new UserControlPanelSelection();
_ucPanelConfiguration = new UserControlPanelConfiguration();
panelSteps.Controls.Add(_ucPanelSelection);
panelSteps.Controls.Add(_ucPanelConfiguration);
_syncRoot = SynchronizationContext.Current;
// Set version number
lblVersion.Text += $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.Major}.{System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.Minor}.{System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.Build}";
Logger.OnStatusLogged += Logger_OnStatusLogged;
Logger.OnBackgroundStatusLogged += Logger_OnBackgroundStatusLogged;
_controller = new ApplicationController(this);
_controller.OnSimConnectionChanged += HandleSimConnectionChanged;
_controller.OnPanelSelectionActivated += HandleShowPanelSelection;
_controller.OnPanelConfigurationActivated += HandleShowPanelConfiguration;
_controller.Initialize();
_ucPanelSelection.Initialize(_controller.PanelSelectionController);
_ucPanelConfiguration.Initialize(_controller.PanelConfigurationController);
menuItem_restart.Click += HandleMenuClicked;
menuItem_exit.Click += HandleMenuClicked;
menuItem_alwaysOnTop.Click += HandleMenuClicked;
menuItem_autoPanning.Click += HandleMenuClicked;
menuItem_autoStart.Click += HandleMenuClicked;
menuItem_minimizeToSystemTray.Click += HandleMenuClicked;
menuItem_minimizeAllPanels.Click += HandleMenuClicked;
menuItem_userGuide.Click += HandleMenuClicked;
menuItem_downloadLatestRelease.Click += HandleMenuClicked;
}
#region Implement view interface
public Form Form { get => this; }
public IPanelSelectionView PanelSelection { get => _ucPanelSelection; }
public IPanelConfigurationView PanelConfiguration { get => _ucPanelConfiguration; }
public bool MinimizeToTray { get => menuItem_minimizeToSystemTray.Checked; set => menuItem_minimizeToSystemTray.Checked = value; }
public bool AlwaysOnTop { get => menuItem_alwaysOnTop.Checked; set => menuItem_alwaysOnTop.Checked = value; }
public bool AutoStart { get => menuItem_autoStart.Checked; set => menuItem_autoStart.Checked = value; }
public bool AutoPanning { get => menuItem_autoPanning.Checked; set => menuItem_autoPanning.Checked = value; }
#endregion
private void StartupForm_Load(object sender, EventArgs e)
{
notifyIcon1.BalloonTipText = "Application Minimized";
notifyIcon1.BalloonTipTitle = "MSFS 2020 Pop Out Panel Manager";
}
private void StartupForm_Resize(object sender, EventArgs e)
{
if (WindowState == FormWindowState.Minimized)
{
if (menuItem_minimizeToSystemTray.Checked)
{
ShowInTaskbar = false;
notifyIcon1.Visible = true;
notifyIcon1.ShowBalloonTip(1000);
}
}
}
private void notifyIcon1_DoubleClick(object sender, EventArgs e)
{
ShowInTaskbar = true;
notifyIcon1.Visible = false;
WindowState = FormWindowState.Normal;
}
private void HandleSimConnectionChanged(object sender, EventArgs<bool> e)
{
_syncRoot.Post((arg) =>
{
var connected = Convert.ToBoolean(arg);
if (connected)
{
labelMsfsConnection.ForeColor = SUCCESS_MESSAGE_COLOR;
labelMsfsConnection.Text = "MSFS Connected";
}
else
{
labelMsfsConnection.ForeColor = ERROR_MESSAGE_COLOR;
labelMsfsConnection.Text = "MSFS Disconnected";
}
}, e.Value);
}
private void Logger_OnStatusLogged(object sender, EventArgs<StatusMessage> e)
{
if (e != null)
{
txtBoxStatus.ForeColor = e.Value.MessageType == StatusMessageType.Info ? INFO_MESSAGE_COLOR : ERROR_MESSAGE_COLOR;
txtBoxStatus.Text = e.Value.Message;
this.ActiveControl = this.panelStatus;
}
if (e.Value.MessageType == StatusMessageType.Error)
PInvoke.SetForegroundWindow(Handle);
}
private void Logger_OnBackgroundStatusLogged(object sender, EventArgs<StatusMessage> e)
{
_syncRoot.Post((arg) =>
{
var statusMessage = arg as StatusMessage;
if (statusMessage != null)
{
txtBoxStatus.ForeColor = statusMessage.MessageType == StatusMessageType.Info ? INFO_MESSAGE_COLOR : ERROR_MESSAGE_COLOR;
txtBoxStatus.Text = statusMessage.Message;
this.ActiveControl = this.panelStatus;
}
if (e.Value.MessageType == StatusMessageType.Error)
PInvoke.SetForegroundWindow(Handle);
}, e.Value);
}
private void HandleMenuClicked(object sender, EventArgs e)
{
var itemName = ((ToolStripMenuItem)sender).Name;
switch (itemName)
{
case nameof(menuItem_restart):
_controller.Restart();
break;
case nameof(menuItem_exit):
Application.Exit();
break;
case nameof(menuItem_alwaysOnTop):
_controller.SetAlwaysOnTop(menuItem_alwaysOnTop.Checked);
break;
case nameof(menuItem_autoPanning):
_controller.SetAutoPanning(menuItem_autoPanning.Checked);
break;
case nameof(menuItem_autoStart):
_controller.SetAutoStart(menuItem_autoStart.Checked);
break;
case nameof(menuItem_minimizeToSystemTray):
_controller.SetMinimizeToTray(menuItem_minimizeToSystemTray.Checked);
break;
case nameof(menuItem_minimizeAllPanels):
_controller.MinimizeAllPanels(menuItem_minimizeAllPanels.Checked);
break;
case nameof(menuItem_userGuide):
Process.Start(new ProcessStartInfo("https://github.com/hawkeye-stan/msfs-popout-panel-manager#msfs-pop-out-panel-manager") { UseShellExecute = true });
return;
case nameof(menuItem_downloadLatestRelease):
Process.Start(new ProcessStartInfo("https://github.com/hawkeye-stan/msfs-popout-panel-manager/releases") { UseShellExecute = true });
return;
}
}
private void HandleShowPanelSelection(object sender, EventArgs e)
{
if (_ucPanelSelection != null && _ucPanelConfiguration != null)
{
_ucPanelSelection.Visible = true;
_ucPanelConfiguration.Visible = false;
menuItem_restart.Enabled = false;
menuItem_minimizeAllPanels.Checked = false;
menuItem_minimizeAllPanels.Enabled = false;
}
}
private void HandleShowPanelConfiguration(object sender, EventArgs e)
{
if (_ucPanelSelection != null && _ucPanelConfiguration != null)
{
_ucPanelSelection.Visible = false;
_ucPanelConfiguration.Visible = true;
menuItem_restart.Enabled = true;
menuItem_minimizeAllPanels.Enabled = true;
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,334 +0,0 @@

namespace MSFSPopoutPanelManager.UI
{
partial class UserControlPanelConfiguration
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle11 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle12 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle18 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle19 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle20 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle13 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle14 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle15 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle16 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle17 = new System.Windows.Forms.DataGridViewCellStyle();
this.panel1 = new System.Windows.Forms.Panel();
this.dataGridViewPanels = new System.Windows.Forms.DataGridView();
this.PanelName = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Left = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Top = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Width = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Height = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.AlwaysOnTop = new System.Windows.Forms.DataGridViewCheckBoxColumn();
this.HideTitlebar = new System.Windows.Forms.DataGridViewCheckBoxColumn();
this.label2 = new System.Windows.Forms.Label();
this.buttonPixelLargeMinus = new System.Windows.Forms.Button();
this.buttonPixelMinusSmall = new System.Windows.Forms.Button();
this.buttonPixelPlusSmall = new System.Windows.Forms.Button();
this.buttonPixelPlusLarge = new System.Windows.Forms.Button();
this.buttonLockPanel = new System.Windows.Forms.Button();
this.toolTipLargeMinus = new System.Windows.Forms.ToolTip(this.components);
this.toolTipSmallMinus = new System.Windows.Forms.ToolTip(this.components);
this.toolTipSmallPlus = new System.Windows.Forms.ToolTip(this.components);
this.toolTipLargePlus = new System.Windows.Forms.ToolTip(this.components);
this.panel1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.dataGridViewPanels)).BeginInit();
this.SuspendLayout();
//
// panel1
//
this.panel1.Controls.Add(this.dataGridViewPanels);
this.panel1.Controls.Add(this.label2);
this.panel1.ForeColor = System.Drawing.Color.White;
this.panel1.Location = new System.Drawing.Point(0, 0);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(915, 348);
this.panel1.TabIndex = 1;
//
// dataGridViewPanels
//
this.dataGridViewPanels.AllowUserToAddRows = false;
this.dataGridViewPanels.AllowUserToDeleteRows = false;
this.dataGridViewPanels.AllowUserToResizeColumns = false;
this.dataGridViewPanels.AllowUserToResizeRows = false;
dataGridViewCellStyle11.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle11.ForeColor = System.Drawing.Color.Black;
dataGridViewCellStyle11.Padding = new System.Windows.Forms.Padding(3);
this.dataGridViewPanels.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle11;
this.dataGridViewPanels.CausesValidation = false;
dataGridViewCellStyle12.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
dataGridViewCellStyle12.BackColor = System.Drawing.SystemColors.Control;
dataGridViewCellStyle12.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle12.ForeColor = System.Drawing.SystemColors.WindowText;
dataGridViewCellStyle12.Padding = new System.Windows.Forms.Padding(10, 3, 3, 3);
dataGridViewCellStyle12.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle12.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle12.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.dataGridViewPanels.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle12;
this.dataGridViewPanels.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridViewPanels.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.PanelName,
this.Left,
this.Top,
this.Width,
this.Height,
this.AlwaysOnTop,
this.HideTitlebar});
dataGridViewCellStyle18.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
dataGridViewCellStyle18.BackColor = System.Drawing.SystemColors.Window;
dataGridViewCellStyle18.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle18.ForeColor = System.Drawing.Color.White;
dataGridViewCellStyle18.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle18.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle18.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
this.dataGridViewPanels.DefaultCellStyle = dataGridViewCellStyle18;
this.dataGridViewPanels.Location = new System.Drawing.Point(20, 35);
this.dataGridViewPanels.MultiSelect = false;
this.dataGridViewPanels.Name = "dataGridViewPanels";
dataGridViewCellStyle19.BackColor = System.Drawing.SystemColors.Control;
dataGridViewCellStyle19.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle19.ForeColor = System.Drawing.SystemColors.WindowText;
dataGridViewCellStyle19.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle19.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle19.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.dataGridViewPanels.RowHeadersDefaultCellStyle = dataGridViewCellStyle19;
this.dataGridViewPanels.RowHeadersVisible = false;
dataGridViewCellStyle20.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle20.ForeColor = System.Drawing.SystemColors.WindowText;
dataGridViewCellStyle20.Padding = new System.Windows.Forms.Padding(3);
this.dataGridViewPanels.RowsDefaultCellStyle = dataGridViewCellStyle20;
this.dataGridViewPanels.RowTemplate.Height = 25;
this.dataGridViewPanels.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.dataGridViewPanels.ShowCellErrors = false;
this.dataGridViewPanels.ShowCellToolTips = false;
this.dataGridViewPanels.ShowEditingIcon = false;
this.dataGridViewPanels.ShowRowErrors = false;
this.dataGridViewPanels.Size = new System.Drawing.Size(874, 310);
this.dataGridViewPanels.TabIndex = 8;
//
// PanelName
//
this.PanelName.DataPropertyName = "PanelName";
dataGridViewCellStyle13.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
this.PanelName.DefaultCellStyle = dataGridViewCellStyle13;
this.PanelName.FillWeight = 80F;
this.PanelName.HeaderText = "Panel Name";
this.PanelName.Name = "PanelName";
this.PanelName.Width = 300;
//
// Left
//
this.Left.DataPropertyName = "Left";
dataGridViewCellStyle14.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
this.Left.DefaultCellStyle = dataGridViewCellStyle14;
this.Left.FillWeight = 80F;
this.Left.HeaderText = "X Pos";
this.Left.MaxInputLength = 6;
this.Left.Name = "Left";
this.Left.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.Left.Width = 95;
//
// Top
//
this.Top.DataPropertyName = "Top";
dataGridViewCellStyle15.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
this.Top.DefaultCellStyle = dataGridViewCellStyle15;
this.Top.FillWeight = 80F;
this.Top.HeaderText = "Y Pos";
this.Top.MaxInputLength = 6;
this.Top.Name = "Top";
this.Top.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.Top.Width = 95;
//
// Width
//
this.Width.DataPropertyName = "Width";
dataGridViewCellStyle16.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
this.Width.DefaultCellStyle = dataGridViewCellStyle16;
this.Width.FillWeight = 80F;
this.Width.HeaderText = "Width";
this.Width.MaxInputLength = 6;
this.Width.Name = "Width";
this.Width.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.Width.Width = 95;
//
// Height
//
this.Height.DataPropertyName = "Height";
dataGridViewCellStyle17.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
this.Height.DefaultCellStyle = dataGridViewCellStyle17;
this.Height.FillWeight = 80F;
this.Height.HeaderText = "Height";
this.Height.MaxInputLength = 6;
this.Height.Name = "Height";
this.Height.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.Height.Width = 95;
//
// AlwaysOnTop
//
this.AlwaysOnTop.DataPropertyName = "AlwaysOnTop";
this.AlwaysOnTop.FalseValue = "false";
this.AlwaysOnTop.FillWeight = 80F;
this.AlwaysOnTop.HeaderText = "Always on Top";
this.AlwaysOnTop.Name = "AlwaysOnTop";
this.AlwaysOnTop.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.AlwaysOnTop.TrueValue = "true";
this.AlwaysOnTop.Width = 95;
//
// HideTitlebar
//
this.HideTitlebar.DataPropertyName = "HideTitlebar";
this.HideTitlebar.FalseValue = "false";
this.HideTitlebar.FillWeight = 80F;
this.HideTitlebar.HeaderText = "Hide Titlebar";
this.HideTitlebar.Name = "HideTitlebar";
this.HideTitlebar.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.HideTitlebar.TrueValue = "true";
this.HideTitlebar.Width = 95;
//
// label2
//
this.label2.AutoSize = true;
this.label2.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.label2.ForeColor = System.Drawing.Color.White;
this.label2.Location = new System.Drawing.Point(19, 9);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(192, 20);
this.label2.TabIndex = 7;
this.label2.Text = "Panel locations and settings";
//
// buttonPixelLargeMinus
//
this.buttonPixelLargeMinus.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonPixelLargeMinus.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonPixelLargeMinus.ForeColor = System.Drawing.Color.White;
this.buttonPixelLargeMinus.Location = new System.Drawing.Point(20, 354);
this.buttonPixelLargeMinus.Name = "buttonPixelLargeMinus";
this.buttonPixelLargeMinus.Size = new System.Drawing.Size(69, 35);
this.buttonPixelLargeMinus.TabIndex = 24;
this.buttonPixelLargeMinus.Text = "-10 px";
this.toolTipLargeMinus.SetToolTip(this.buttonPixelLargeMinus, "Ctrl -");
this.buttonPixelLargeMinus.UseVisualStyleBackColor = false;
//
// buttonPixelMinusSmall
//
this.buttonPixelMinusSmall.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonPixelMinusSmall.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonPixelMinusSmall.ForeColor = System.Drawing.Color.White;
this.buttonPixelMinusSmall.Location = new System.Drawing.Point(95, 354);
this.buttonPixelMinusSmall.Name = "buttonPixelMinusSmall";
this.buttonPixelMinusSmall.Size = new System.Drawing.Size(69, 35);
this.buttonPixelMinusSmall.TabIndex = 25;
this.buttonPixelMinusSmall.Text = "-1 px";
this.toolTipSmallMinus.SetToolTip(this.buttonPixelMinusSmall, "Ctrl [");
this.buttonPixelMinusSmall.UseVisualStyleBackColor = false;
//
// buttonPixelPlusSmall
//
this.buttonPixelPlusSmall.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonPixelPlusSmall.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonPixelPlusSmall.ForeColor = System.Drawing.Color.White;
this.buttonPixelPlusSmall.Location = new System.Drawing.Point(170, 354);
this.buttonPixelPlusSmall.Name = "buttonPixelPlusSmall";
this.buttonPixelPlusSmall.Size = new System.Drawing.Size(69, 35);
this.buttonPixelPlusSmall.TabIndex = 26;
this.buttonPixelPlusSmall.Text = "+1 px";
this.toolTipSmallPlus.SetToolTip(this.buttonPixelPlusSmall, "Ctrl ]");
this.buttonPixelPlusSmall.UseVisualStyleBackColor = false;
//
// buttonPixelPlusLarge
//
this.buttonPixelPlusLarge.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonPixelPlusLarge.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonPixelPlusLarge.ForeColor = System.Drawing.Color.White;
this.buttonPixelPlusLarge.Location = new System.Drawing.Point(245, 354);
this.buttonPixelPlusLarge.Name = "buttonPixelPlusLarge";
this.buttonPixelPlusLarge.Size = new System.Drawing.Size(69, 35);
this.buttonPixelPlusLarge.TabIndex = 27;
this.buttonPixelPlusLarge.Text = "+10 px";
this.toolTipLargePlus.SetToolTip(this.buttonPixelPlusLarge, "Ctrl +");
this.buttonPixelPlusLarge.UseVisualStyleBackColor = false;
//
// buttonLockPanel
//
this.buttonLockPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonLockPanel.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonLockPanel.ForeColor = System.Drawing.Color.White;
this.buttonLockPanel.Location = new System.Drawing.Point(772, 354);
this.buttonLockPanel.Name = "buttonLockPanel";
this.buttonLockPanel.Size = new System.Drawing.Size(122, 35);
this.buttonLockPanel.TabIndex = 28;
this.buttonLockPanel.Text = "Lock Panels";
this.buttonLockPanel.UseVisualStyleBackColor = false;
//
// UserControlPanelConfiguration
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.Transparent;
this.Controls.Add(this.buttonLockPanel);
this.Controls.Add(this.buttonPixelPlusLarge);
this.Controls.Add(this.buttonPixelPlusSmall);
this.Controls.Add(this.buttonPixelMinusSmall);
this.Controls.Add(this.buttonPixelLargeMinus);
this.Controls.Add(this.panel1);
this.Name = "UserControlPanelConfiguration";
this.Size = new System.Drawing.Size(915, 405);
this.panel1.ResumeLayout(false);
this.panel1.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.dataGridViewPanels)).EndInit();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.DataGridView dataGridViewPanels;
private System.Windows.Forms.Button buttonPixelLargeMinus;
private System.Windows.Forms.Button buttonPixelMinusSmall;
private System.Windows.Forms.Button buttonPixelPlusSmall;
private System.Windows.Forms.Button buttonPixelPlusLarge;
private System.Windows.Forms.DataGridViewTextBoxColumn PanelName;
private System.Windows.Forms.DataGridViewTextBoxColumn Left;
private System.Windows.Forms.DataGridViewTextBoxColumn Top;
private System.Windows.Forms.DataGridViewTextBoxColumn Width;
private System.Windows.Forms.DataGridViewTextBoxColumn Height;
private System.Windows.Forms.DataGridViewCheckBoxColumn AlwaysOnTop;
private System.Windows.Forms.DataGridViewCheckBoxColumn HideTitlebar;
private System.Windows.Forms.Button buttonLockPanel;
private System.Windows.Forms.ToolTip toolTipLargeMinus;
private System.Windows.Forms.ToolTip toolTipSmallMinus;
private System.Windows.Forms.ToolTip toolTipSmallPlus;
private System.Windows.Forms.ToolTip toolTipLargePlus;
}
}

View file

@ -1,187 +0,0 @@
using MSFSPopoutPanelManager.Shared;
using MSFSPopoutPanelManager.UIController;
using System;
using System.Drawing;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager.UI
{
public partial class UserControlPanelConfiguration : UserControl, IPanelConfigurationView
{
private PanelConfigurationController _controller;
public bool IsPanelLocked { set => SetProfileLockButtonText(_controller.DataStore.ActiveUserProfile.IsLocked); }
public bool IsPanelChangeDisabled { set => this.Enabled = !value; }
public UserControlPanelConfiguration()
{
InitializeComponent();
}
public void Initialize(PanelConfigurationController controller)
{
_controller = controller;
_controller.RefreshDataUI += (source, e) => dataGridViewPanels.Refresh();
_controller.HightlightSelectedPanel += HandleHighlightSelectedPanel;
dataGridViewPanels.AutoGenerateColumns = false;
dataGridViewPanels.AutoSize = false;
dataGridViewPanels.DataSource = _controller.DataStore.PanelConfigs;
dataGridViewPanels.CellValidating += HandleCellValidating;
dataGridViewPanels.CellEndEdit += HandleCellValueChanged;
dataGridViewPanels.CellContentClick += HandleCellContentChanged; // for checkbox columns
dataGridViewPanels.CellFormatting += HandleCellFormatting;
buttonPixelPlusLarge.Click += (source, e) => HandleCellValueIncrDecr(10);
buttonPixelPlusSmall.Click += (source, e) => HandleCellValueIncrDecr(1);
buttonPixelLargeMinus.Click += (source, e) => HandleCellValueIncrDecr(-10);
buttonPixelMinusSmall.Click += (source, e) => HandleCellValueIncrDecr(-1);
buttonLockPanel.Click += HandleLockPanelChanged;
}
private void HandleCellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if (e.ColumnIndex >= 0 && e.RowIndex >= 0)
{
var column = (PanelConfigDataColumn)Enum.Parse(typeof(PanelConfigDataColumn), dataGridViewPanels.Columns[e.ColumnIndex].Name);
var dgv = sender as DataGridView;
var data = dgv.Rows[e.RowIndex].DataBoundItem as PanelConfig;
if (column == PanelConfigDataColumn.PanelName || column == PanelConfigDataColumn.HideTitlebar)
{
if(data.PanelType == PanelType.BuiltInPopout)
dgv[e.ColumnIndex, e.RowIndex].ReadOnly = true;
}
}
}
private void HandleCellValueIncrDecr(int changedAmount)
{
var activeCell = dataGridViewPanels.CurrentCell;
if (activeCell != null)
{
var rowIndex = dataGridViewPanels.CurrentCell.RowIndex;
var column = (PanelConfigDataColumn)Enum.Parse(typeof(PanelConfigDataColumn), dataGridViewPanels.Columns[dataGridViewPanels.CurrentCell.ColumnIndex].Name);
_controller.CellValueIncrDecr(rowIndex, column, changedAmount);
}
}
private void HandleCellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex >= 0 && e.ColumnIndex <= 4 && e.RowIndex >= 0)
{
dataGridViewPanels.EndEdit();
var column = (PanelConfigDataColumn)Enum.Parse(typeof(PanelConfigDataColumn), dataGridViewPanels.Columns[e.ColumnIndex].Name);
_controller.CellValueChanged(e.RowIndex, column, dataGridViewPanels[e.ColumnIndex, e.RowIndex].EditedFormattedValue);
}
}
private void HandleCellContentChanged(object sender, DataGridViewCellEventArgs e)
{
if ((e.ColumnIndex == 5 || e.ColumnIndex == 6) && e.RowIndex >= 0)
{
var column = (PanelConfigDataColumn)Enum.Parse(typeof(PanelConfigDataColumn), dataGridViewPanels.Columns[e.ColumnIndex].Name);
_controller.CellValueChanged(e.RowIndex, column, dataGridViewPanels[e.ColumnIndex, e.RowIndex].EditedFormattedValue);
}
}
private void HandleCellValidating(object sender, DataGridViewCellValidatingEventArgs e)
{
if (e.ColumnIndex >= 0 && e.RowIndex >= 0)
{
var column = (PanelConfigDataColumn)Enum.Parse(typeof(PanelConfigDataColumn), dataGridViewPanels.Columns[e.ColumnIndex].Name);
// Allow cell edit for only specific data type in each column
switch (column)
{
case PanelConfigDataColumn.PanelName:
if (!(e.FormattedValue is String))
e.Cancel = true;
break;
case PanelConfigDataColumn.Left:
case PanelConfigDataColumn.Top:
case PanelConfigDataColumn.Width:
case PanelConfigDataColumn.Height:
// must be numbers
int i;
bool result = int.TryParse(Convert.ToString(e.FormattedValue), out i);
if (!result)
e.Cancel = true;
break;
default:
return;
}
}
}
private void HandleHighlightSelectedPanel(object sender, EventArgs<int> e)
{
dataGridViewPanels.ClearSelection();
dataGridViewPanels.Rows[e.Value].Selected = true;
}
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (!_controller.DataStore.ActiveUserProfile.IsLocked)
{
if (keyData == (Keys.Oemplus | Keys.Control) || keyData == (Keys.Add | Keys.Control))
{
HandleCellValueIncrDecr(10);
}
else if (keyData == (Keys.OemMinus | Keys.Control) || keyData == (Keys.Subtract | Keys.Control))
{
HandleCellValueIncrDecr(-10);
}
else if (keyData == (Keys.OemCloseBrackets | Keys.Control))
{
HandleCellValueIncrDecr(1);
}
else if (keyData == (Keys.OemOpenBrackets | Keys.Control))
{
HandleCellValueIncrDecr(-1);
}
}
return base.ProcessCmdKey(ref msg, keyData);
}
private void HandleLockPanelChanged(object sender, EventArgs e)
{
if (!_controller.DataStore.ActiveUserProfile.IsLocked)
{
_controller.LockPanelChanged(true);
}
else
{
var title = "Confirm Unlock Panels";
var message = "Are you sure you want to unlock all panels to make changes?";
using (var form = new ConfirmDialogForm(title, message) { StartPosition = FormStartPosition.CenterParent })
{
if (form.ShowDialog() == DialogResult.Yes)
{
_controller.LockPanelChanged(false);
}
}
}
}
private void SetProfileLockButtonText(bool isLocked)
{
buttonLockPanel.Text = isLocked ? "Unlock Panels" : "Lock Panels";
buttonLockPanel.BackColor = isLocked ? Color.Red : Color.FromArgb(17, 158, 218);
dataGridViewPanels.ReadOnly = isLocked;
buttonPixelLargeMinus.Enabled = !isLocked;
buttonPixelMinusSmall.Enabled = !isLocked;
buttonPixelPlusLarge.Enabled = !isLocked;
buttonPixelPlusSmall.Enabled = !isLocked;
}
}
}

View file

@ -1,105 +0,0 @@
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="PanelName.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="Left.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="Top.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="Width.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="Height.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="AlwaysOnTop.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="HideTitlebar.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="toolTipLargeMinus.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="toolTipSmallMinus.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>187, 17</value>
</metadata>
<metadata name="toolTipSmallPlus.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>292, 17</value>
</metadata>
<metadata name="toolTipLargePlus.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>397, 17</value>
</metadata>
<metadata name="toolTipLargeMinus.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="toolTipSmallMinus.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>187, 17</value>
</metadata>
<metadata name="toolTipSmallPlus.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>292, 17</value>
</metadata>
<metadata name="toolTipLargePlus.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>397, 17</value>
</metadata>
</root>

View file

@ -1,410 +0,0 @@

namespace MSFSPopoutPanelManager.UI
{
partial class UserControlPanelSelection
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle9 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle10 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle14 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle15 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle16 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle11 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle12 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle13 = new System.Windows.Forms.DataGridViewCellStyle();
this.dataGridViewPanelCoor = new System.Windows.Forms.DataGridView();
this.PanelIndex = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.X = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Y = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.panel1 = new System.Windows.Forms.Panel();
this.buttonDeleteProfile = new System.Windows.Forms.Button();
this.buttonAddProfile = new System.Windows.Forms.Button();
this.buttonSetDefault = new System.Windows.Forms.Button();
this.label2 = new System.Windows.Forms.Label();
this.comboBoxProfile = new System.Windows.Forms.ComboBox();
this.panel2 = new System.Windows.Forms.Panel();
this.label1 = new System.Windows.Forms.Label();
this.label5 = new System.Windows.Forms.Label();
this.label4 = new System.Windows.Forms.Label();
this.buttonStartPanelSelection = new System.Windows.Forms.Button();
this.label3 = new System.Windows.Forms.Label();
this.label6 = new System.Windows.Forms.Label();
this.panel4 = new System.Windows.Forms.Panel();
this.checkBoxShowPanelLocation = new System.Windows.Forms.CheckBox();
this.buttonStartPopOut = new System.Windows.Forms.Button();
this.panel3 = new System.Windows.Forms.Panel();
this.label7 = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.dataGridViewPanelCoor)).BeginInit();
this.panel1.SuspendLayout();
this.panel2.SuspendLayout();
this.panel4.SuspendLayout();
this.panel3.SuspendLayout();
this.SuspendLayout();
//
// dataGridViewPanelCoor
//
this.dataGridViewPanelCoor.AllowUserToAddRows = false;
this.dataGridViewPanelCoor.AllowUserToDeleteRows = false;
this.dataGridViewPanelCoor.AllowUserToResizeColumns = false;
this.dataGridViewPanelCoor.AllowUserToResizeRows = false;
dataGridViewCellStyle9.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
dataGridViewCellStyle9.ForeColor = System.Drawing.Color.Black;
this.dataGridViewPanelCoor.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle9;
this.dataGridViewPanelCoor.CausesValidation = false;
dataGridViewCellStyle10.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
dataGridViewCellStyle10.BackColor = System.Drawing.SystemColors.Control;
dataGridViewCellStyle10.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle10.ForeColor = System.Drawing.SystemColors.WindowText;
dataGridViewCellStyle10.Padding = new System.Windows.Forms.Padding(18, 3, 3, 3);
dataGridViewCellStyle10.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle10.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle10.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
this.dataGridViewPanelCoor.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle10;
this.dataGridViewPanelCoor.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridViewPanelCoor.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.PanelIndex,
this.X,
this.Y});
dataGridViewCellStyle14.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
dataGridViewCellStyle14.BackColor = System.Drawing.SystemColors.Window;
dataGridViewCellStyle14.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle14.ForeColor = System.Drawing.SystemColors.ControlText;
dataGridViewCellStyle14.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle14.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle14.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
this.dataGridViewPanelCoor.DefaultCellStyle = dataGridViewCellStyle14;
this.dataGridViewPanelCoor.Location = new System.Drawing.Point(18, 42);
this.dataGridViewPanelCoor.MultiSelect = false;
this.dataGridViewPanelCoor.Name = "dataGridViewPanelCoor";
this.dataGridViewPanelCoor.ReadOnly = true;
dataGridViewCellStyle15.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
dataGridViewCellStyle15.BackColor = System.Drawing.SystemColors.Control;
dataGridViewCellStyle15.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle15.ForeColor = System.Drawing.SystemColors.WindowText;
dataGridViewCellStyle15.Padding = new System.Windows.Forms.Padding(3);
dataGridViewCellStyle15.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle15.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle15.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.dataGridViewPanelCoor.RowHeadersDefaultCellStyle = dataGridViewCellStyle15;
this.dataGridViewPanelCoor.RowHeadersVisible = false;
dataGridViewCellStyle16.Padding = new System.Windows.Forms.Padding(3);
this.dataGridViewPanelCoor.RowsDefaultCellStyle = dataGridViewCellStyle16;
this.dataGridViewPanelCoor.RowTemplate.Height = 25;
this.dataGridViewPanelCoor.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.dataGridViewPanelCoor.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.CellSelect;
this.dataGridViewPanelCoor.ShowCellErrors = false;
this.dataGridViewPanelCoor.ShowCellToolTips = false;
this.dataGridViewPanelCoor.ShowEditingIcon = false;
this.dataGridViewPanelCoor.ShowRowErrors = false;
this.dataGridViewPanelCoor.Size = new System.Drawing.Size(303, 286);
this.dataGridViewPanelCoor.TabIndex = 18;
//
// PanelIndex
//
this.PanelIndex.DataPropertyName = "PanelIndex";
dataGridViewCellStyle11.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
dataGridViewCellStyle11.ForeColor = System.Drawing.Color.Black;
this.PanelIndex.DefaultCellStyle = dataGridViewCellStyle11;
this.PanelIndex.HeaderText = "Panel";
this.PanelIndex.Name = "PanelIndex";
this.PanelIndex.ReadOnly = true;
//
// X
//
this.X.DataPropertyName = "X";
dataGridViewCellStyle12.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
dataGridViewCellStyle12.ForeColor = System.Drawing.Color.Black;
this.X.DefaultCellStyle = dataGridViewCellStyle12;
this.X.HeaderText = "X-Pos";
this.X.Name = "X";
this.X.ReadOnly = true;
//
// Y
//
this.Y.DataPropertyName = "Y";
dataGridViewCellStyle13.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
dataGridViewCellStyle13.ForeColor = System.Drawing.Color.Black;
this.Y.DefaultCellStyle = dataGridViewCellStyle13;
this.Y.HeaderText = "Y-Pos";
this.Y.Name = "Y";
this.Y.ReadOnly = true;
//
// panel1
//
this.panel1.Controls.Add(this.buttonDeleteProfile);
this.panel1.Controls.Add(this.buttonAddProfile);
this.panel1.Controls.Add(this.buttonSetDefault);
this.panel1.Controls.Add(this.label2);
this.panel1.Controls.Add(this.comboBoxProfile);
this.panel1.ForeColor = System.Drawing.Color.White;
this.panel1.Location = new System.Drawing.Point(0, 0);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(579, 118);
this.panel1.TabIndex = 0;
//
// buttonDeleteProfile
//
this.buttonDeleteProfile.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonDeleteProfile.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonDeleteProfile.ForeColor = System.Drawing.Color.White;
this.buttonDeleteProfile.Location = new System.Drawing.Point(167, 75);
this.buttonDeleteProfile.Name = "buttonDeleteProfile";
this.buttonDeleteProfile.Size = new System.Drawing.Size(118, 35);
this.buttonDeleteProfile.TabIndex = 21;
this.buttonDeleteProfile.Text = "Delete Profile";
this.buttonDeleteProfile.UseVisualStyleBackColor = false;
//
// buttonAddProfile
//
this.buttonAddProfile.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonAddProfile.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonAddProfile.ForeColor = System.Drawing.Color.White;
this.buttonAddProfile.Location = new System.Drawing.Point(35, 75);
this.buttonAddProfile.Name = "buttonAddProfile";
this.buttonAddProfile.Size = new System.Drawing.Size(115, 35);
this.buttonAddProfile.TabIndex = 20;
this.buttonAddProfile.Text = "Add Profile";
this.buttonAddProfile.UseVisualStyleBackColor = false;
//
// buttonSetDefault
//
this.buttonSetDefault.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonSetDefault.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonSetDefault.ForeColor = System.Drawing.Color.White;
this.buttonSetDefault.Location = new System.Drawing.Point(300, 75);
this.buttonSetDefault.Name = "buttonSetDefault";
this.buttonSetDefault.Size = new System.Drawing.Size(107, 35);
this.buttonSetDefault.TabIndex = 19;
this.buttonSetDefault.Text = "Set Default";
this.buttonSetDefault.UseVisualStyleBackColor = false;
//
// label2
//
this.label2.AutoSize = true;
this.label2.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.label2.ForeColor = System.Drawing.Color.White;
this.label2.Location = new System.Drawing.Point(20, 10);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(315, 20);
this.label2.TabIndex = 7;
this.label2.Text = "1. Please select a profile you would like to use.";
//
// comboBoxProfile
//
this.comboBoxProfile.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxProfile.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.comboBoxProfile.ForeColor = System.Drawing.Color.Black;
this.comboBoxProfile.FormattingEnabled = true;
this.comboBoxProfile.Location = new System.Drawing.Point(35, 41);
this.comboBoxProfile.Name = "comboBoxProfile";
this.comboBoxProfile.Size = new System.Drawing.Size(445, 28);
this.comboBoxProfile.TabIndex = 5;
//
// panel2
//
this.panel2.Controls.Add(this.label1);
this.panel2.Controls.Add(this.label5);
this.panel2.Controls.Add(this.label4);
this.panel2.Controls.Add(this.buttonStartPanelSelection);
this.panel2.Controls.Add(this.label3);
this.panel2.Location = new System.Drawing.Point(0, 117);
this.panel2.Name = "panel2";
this.panel2.Size = new System.Drawing.Size(579, 172);
this.panel2.TabIndex = 8;
//
// label1
//
this.label1.AutoSize = true;
this.label1.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.label1.ForeColor = System.Drawing.Color.White;
this.label1.Location = new System.Drawing.Point(35, 37);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(213, 20);
this.label1.TabIndex = 12;
this.label1.Text = "LEFT CLICK to add a new panel";
//
// label5
//
this.label5.AutoSize = true;
this.label5.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.label5.ForeColor = System.Drawing.Color.White;
this.label5.Location = new System.Drawing.Point(35, 97);
this.label5.Name = "label5";
this.label5.Size = new System.Drawing.Size(523, 20);
this.label5.TabIndex = 11;
this.label5.Text = "CTRL + LEFT CLICK when all panels have been selected or to cancel selections.";
//
// label4
//
this.label4.AutoSize = true;
this.label4.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.label4.ForeColor = System.Drawing.Color.White;
this.label4.Location = new System.Drawing.Point(35, 66);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(418, 20);
this.label4.TabIndex = 10;
this.label4.Text = "SHIFT + LEFT CLICK to remove the most recently added panel.";
//
// buttonStartPanelSelection
//
this.buttonStartPanelSelection.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonStartPanelSelection.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonStartPanelSelection.ForeColor = System.Drawing.Color.White;
this.buttonStartPanelSelection.Location = new System.Drawing.Point(32, 127);
this.buttonStartPanelSelection.Name = "buttonStartPanelSelection";
this.buttonStartPanelSelection.Size = new System.Drawing.Size(170, 35);
this.buttonStartPanelSelection.TabIndex = 9;
this.buttonStartPanelSelection.Text = "Start Panel Selection";
this.buttonStartPanelSelection.UseVisualStyleBackColor = false;
//
// label3
//
this.label3.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.label3.ForeColor = System.Drawing.Color.White;
this.label3.Location = new System.Drawing.Point(20, 4);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(551, 27);
this.label3.TabIndex = 7;
this.label3.Text = "2. Identify pop out panel locations in the game by clicking on them.";
//
// label6
//
this.label6.AutoSize = true;
this.label6.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.label6.ForeColor = System.Drawing.Color.White;
this.label6.Location = new System.Drawing.Point(113, 10);
this.label6.Name = "label6";
this.label6.Size = new System.Drawing.Size(111, 20);
this.label6.TabIndex = 11;
this.label6.Text = "Panel Locations";
//
// panel4
//
this.panel4.Controls.Add(this.dataGridViewPanelCoor);
this.panel4.Controls.Add(this.checkBoxShowPanelLocation);
this.panel4.Controls.Add(this.label6);
this.panel4.Location = new System.Drawing.Point(577, 0);
this.panel4.Name = "panel4";
this.panel4.Size = new System.Drawing.Size(338, 403);
this.panel4.TabIndex = 13;
//
// checkBoxShowPanelLocation
//
this.checkBoxShowPanelLocation.AutoSize = true;
this.checkBoxShowPanelLocation.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.checkBoxShowPanelLocation.ForeColor = System.Drawing.Color.White;
this.checkBoxShowPanelLocation.Location = new System.Drawing.Point(67, 342);
this.checkBoxShowPanelLocation.Name = "checkBoxShowPanelLocation";
this.checkBoxShowPanelLocation.Size = new System.Drawing.Size(213, 24);
this.checkBoxShowPanelLocation.TabIndex = 17;
this.checkBoxShowPanelLocation.Text = "Show Panel Location Ovelay";
this.checkBoxShowPanelLocation.UseVisualStyleBackColor = true;
//
// buttonStartPopOut
//
this.buttonStartPopOut.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonStartPopOut.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonStartPopOut.ForeColor = System.Drawing.Color.White;
this.buttonStartPopOut.Location = new System.Drawing.Point(32, 41);
this.buttonStartPopOut.Name = "buttonStartPopOut";
this.buttonStartPopOut.Size = new System.Drawing.Size(118, 35);
this.buttonStartPopOut.TabIndex = 18;
this.buttonStartPopOut.Text = "Start Pop Out";
this.buttonStartPopOut.UseVisualStyleBackColor = false;
//
// panel3
//
this.panel3.Controls.Add(this.buttonStartPopOut);
this.panel3.Controls.Add(this.label7);
this.panel3.ForeColor = System.Drawing.Color.White;
this.panel3.Location = new System.Drawing.Point(0, 290);
this.panel3.Name = "panel3";
this.panel3.Size = new System.Drawing.Size(579, 113);
this.panel3.TabIndex = 14;
//
// label7
//
this.label7.AutoSize = true;
this.label7.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.label7.ForeColor = System.Drawing.Color.White;
this.label7.Location = new System.Drawing.Point(20, 10);
this.label7.Name = "label7";
this.label7.Size = new System.Drawing.Size(323, 20);
this.label7.TabIndex = 7;
this.label7.Text = "3. Start the pop out process for selected panels.";
//
// UserControlPanelSelection
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.Transparent;
this.Controls.Add(this.panel3);
this.Controls.Add(this.panel4);
this.Controls.Add(this.panel2);
this.Controls.Add(this.panel1);
this.Name = "UserControlPanelSelection";
this.Size = new System.Drawing.Size(915, 405);
((System.ComponentModel.ISupportInitialize)(this.dataGridViewPanelCoor)).EndInit();
this.panel1.ResumeLayout(false);
this.panel1.PerformLayout();
this.panel2.ResumeLayout(false);
this.panel2.PerformLayout();
this.panel4.ResumeLayout(false);
this.panel4.PerformLayout();
this.panel3.ResumeLayout(false);
this.panel3.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.ComboBox comboBoxProfile;
private System.Windows.Forms.Panel panel2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Button buttonStartPanelSelection;
private System.Windows.Forms.Label label5;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label6;
private System.Windows.Forms.Panel panel3;
private System.Windows.Forms.Panel panel4;
private System.Windows.Forms.CheckBox checkBoxShowPanelLocation;
private System.Windows.Forms.Button buttonStartPopOut;
private System.Windows.Forms.Label label7;
private System.Windows.Forms.Button buttonSetDefault;
private System.Windows.Forms.Button buttonAddProfile;
private System.Windows.Forms.Button buttonDeleteProfile;
private System.Windows.Forms.DataGridView dataGridViewPanelCoor;
private System.Windows.Forms.DataGridViewTextBoxColumn PanelIndex;
private System.Windows.Forms.DataGridViewTextBoxColumn X;
private System.Windows.Forms.DataGridViewTextBoxColumn Y;
}
}

View file

@ -1,166 +0,0 @@
using MSFSPopoutPanelManager.Shared;
using MSFSPopoutPanelManager.UIController;
using System;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager.UI
{
public partial class UserControlPanelSelection : UserControl, IPanelSelectionView
{
private PanelSelectionController _controller;
#region Implement view interface
public Form Form { get => ParentForm; }
public bool ShowPanelLocationOverlay { get => checkBoxShowPanelLocation.Checked; set => checkBoxShowPanelLocation.Checked = value; }
#endregion
public UserControlPanelSelection()
{
InitializeComponent();
}
public void Initialize(PanelSelectionController controller)
{
_controller = controller;
_controller.OnUIStateChanged += HandleOnUIStateChanged;
_controller.Initialize();
// Set bindings
comboBoxProfile.DisplayMember = "ProfileName";
comboBoxProfile.ValueMember = "ProfileId";
comboBoxProfile.DataSource = _controller.DataStore.UserProfiles;
comboBoxProfile.DataBindings.Add("SelectedValue", _controller.DataStore, "ActiveUserProfileId");
comboBoxProfile.SelectedValue = -1; // forced a default
comboBoxProfile.SelectedIndexChanged += HandleProfileChanged;
buttonAddProfile.Click += HandleAddProfile;
buttonDeleteProfile.Click += HandleDeleteProfile;
buttonSetDefault.Click += (source, e) => _controller.SetDefaultProfile();
buttonStartPanelSelection.Click += HandleStartPanelSelection;
buttonStartPopOut.Click += (source, e) => _controller.StartPopOut();
dataGridViewPanelCoor.AutoGenerateColumns = false;
dataGridViewPanelCoor.AutoSize = false;
dataGridViewPanelCoor.DataSource = _controller.DataStore.ActiveProfilePanelCoordinates;
checkBoxShowPanelLocation.CheckedChanged += (source, e) => _controller.SetPanelLocationOverlayChanged();
}
private void HandleProfileChanged(object sender, EventArgs e)
{
if(Convert.ToInt32(comboBoxProfile.SelectedValue) > 0)
_controller.ProfileChanged(Convert.ToInt32(comboBoxProfile.SelectedValue));
}
private void HandleAddProfile(object sender, EventArgs e)
{
using (var form = new AddProfileForm { StartPosition = FormStartPosition.CenterParent })
{
var dialogResult = form.ShowDialog();
if (dialogResult == DialogResult.OK)
{
_controller.AddProfile(form.ProfileName);
}
}
}
private void HandleDeleteProfile(object sender, EventArgs e)
{
var title = "Confirm Delete";
var message = "Are you sure you want to delete the selected profile?";
using (var form = new ConfirmDialogForm(title, message) { StartPosition = FormStartPosition.CenterParent })
{
if (form.ShowDialog() == DialogResult.Yes)
_controller.DeleteProfile();
}
}
private void HandleStartPanelSelection(object sender, EventArgs e)
{
if (!_controller.HasExistingPanelCoordinates)
{
_controller.StartPanelSelection();
}
else
{
var title = "Confirm Overwrite";
var message = "WARNING! Are you sure you want to overwrite existing saved panel locations and ALL settings for this profile?";
using (var form = new ConfirmDialogForm(title, message) { StartPosition = FormStartPosition.CenterParent })
{
if (form.ShowDialog() == DialogResult.Yes)
_controller.StartPanelSelection();
}
}
}
private void HandleOnUIStateChanged(object sender, EventArgs<PanelSelectionUIState> e)
{
switch (e.Value)
{
case PanelSelectionUIState.NoProfileSelected:
comboBoxProfile.Enabled = true;
buttonAddProfile.Enabled = true;
buttonDeleteProfile.Enabled = false;
buttonSetDefault.Enabled = false;
buttonStartPanelSelection.Enabled = false;
checkBoxShowPanelLocation.Enabled = false;
buttonStartPopOut.Enabled = false;
break;
case PanelSelectionUIState.ProfileSelected:
comboBoxProfile.Enabled = true;
buttonAddProfile.Enabled = true;
buttonDeleteProfile.Enabled = true;
buttonSetDefault.Enabled = true;
buttonStartPanelSelection.Enabled = true;
checkBoxShowPanelLocation.Enabled = true;
buttonStartPopOut.Enabled = false;
break;
case PanelSelectionUIState.PanelSelectionStarted:
comboBoxProfile.Enabled = true;
buttonAddProfile.Enabled = false;
buttonDeleteProfile.Enabled = false;
buttonSetDefault.Enabled = false;
buttonStartPanelSelection.Enabled = false;
checkBoxShowPanelLocation.Enabled = false;
buttonStartPopOut.Enabled = false;
break;
case PanelSelectionUIState.PanelSelectionCompletedValid:
comboBoxProfile.Enabled = true;
buttonAddProfile.Enabled = true;
buttonDeleteProfile.Enabled = true;
buttonSetDefault.Enabled = true;
buttonStartPanelSelection.Enabled = true;
checkBoxShowPanelLocation.Enabled = true;
buttonStartPopOut.Enabled = true;
buttonStartPopOut.Focus();
break;
case PanelSelectionUIState.PanelSelectionCompletedInvalid:
comboBoxProfile.Enabled = true;
buttonAddProfile.Enabled = true;
buttonDeleteProfile.Enabled = true;
buttonSetDefault.Enabled = true;
buttonStartPanelSelection.Enabled = true;
checkBoxShowPanelLocation.Enabled = true;
buttonStartPopOut.Enabled = false;
break;
case PanelSelectionUIState.PopoutStarted:
comboBoxProfile.Enabled = false;
buttonAddProfile.Enabled = false;
buttonDeleteProfile.Enabled = false;
buttonSetDefault.Enabled = false;
buttonStartPanelSelection.Enabled = false;
checkBoxShowPanelLocation.Enabled = false;
buttonStartPopOut.Enabled = false;
break;
}
}
}
}

View file

@ -1,69 +0,0 @@
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="PanelIndex.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="X.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="Y.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
</root>

View file

@ -1,140 +0,0 @@

using MSFSPopoutPanelManager.Provider;
using MSFSPopoutPanelManager.Shared;
using System;
namespace MSFSPopoutPanelManager.UIController
{
public class ApplicationController
{
private const int MSFS_CONNECTION_CHECK_INTERVAL = 3000;
private System.Timers.Timer _timer;
private IApplicationView _view;
private AppSettingData _appSettings;
protected DataStore UserProfileDataStore { get; set; }
public ApplicationController(IApplicationView view)
{
_view = view;
UserProfileDataStore = new DataStore();
}
public PanelSelectionController PanelSelectionController { get; set; }
public PanelConfigurationController PanelConfigurationController { get; set; }
public event EventHandler<EventArgs<bool>> OnSimConnectionChanged;
public event EventHandler OnPanelSelectionActivated;
public event EventHandler OnPanelConfigurationActivated;
public void Initialize()
{
PanelSelectionController = new PanelSelectionController(_view.PanelSelection, UserProfileDataStore);
PanelConfigurationController = new PanelConfigurationController(_view.PanelConfiguration, UserProfileDataStore);
PanelSelectionController.OnPopOutCompleted += (source, e) =>
{
PanelConfigurationController.Initialize();
OnPanelConfigurationActivated?.Invoke(this, null);
};
OnPanelSelectionActivated?.Invoke(this, null);
CheckSimulatorStarted();
_appSettings = FileManager.ReadAppSettingData();
_view.AlwaysOnTop = _appSettings.AlwaysOnTop;
_view.AutoPanning = _appSettings.UseAutoPanning;
_view.AutoStart = Autostart.CheckIsAutoStart();
_view.MinimizeToTray = _appSettings.MinimizeToTray;
if (_view.AlwaysOnTop)
_view.Form.TopMost = true;
}
private void CheckSimulatorStarted()
{
OnSimConnectionChanged?.Invoke(this, new EventArgs<bool>(false));
// Autoconnect to flight simulator
_timer = new System.Timers.Timer();
_timer.Interval = MSFS_CONNECTION_CHECK_INTERVAL;
_timer.Enabled = true;
_timer.Elapsed += (source, e) =>
{
var simulatorProcess = WindowManager.GetSimulatorProcess();
OnSimConnectionChanged?.Invoke(this, new EventArgs<bool>(simulatorProcess != null));
};
}
public void Restart()
{
PanelConfigurationController.UnhookWinEvent();
// Try to close all Cutome Panel window
UserProfileDataStore.ActiveUserProfile.PanelConfigs.FindAll(p => p.PanelType == PanelType.CustomPopout).ForEach(panel => WindowManager.CloseWindow(panel.PanelHandle));
OnPanelSelectionActivated?.Invoke(this, null);
Logger.ClearStatus();
}
public void SetMinimizeToTray(bool value) {
_appSettings.MinimizeToTray = value;
FileManager.WriteAppSettingData(_appSettings);
}
public void SetAlwaysOnTop(bool value)
{
_appSettings.AlwaysOnTop = value;
FileManager.WriteAppSettingData(_appSettings);
WindowManager.ApplyAlwaysOnTop(_view.Form.Handle, value, _view.Form.Bounds);
}
public void SetAutoStart(bool value)
{
if (value)
Autostart.Activate();
else
Autostart.Deactivate();
}
public void SetAutoPanning(bool value)
{
_appSettings.UseAutoPanning = value;
FileManager.WriteAppSettingData(_appSettings);
}
public void MinimizeAllPanels(bool isMinimized)
{
if (isMinimized)
{
PanelConfigurationController.DisablePanelChanges(true);
PInvoke.EnumWindows(new PInvoke.CallBack(EnumAllPanels), 0);
}
else
{
PanelConfigurationController.DisablePanelChanges(false);
PInvoke.EnumWindows(new PInvoke.CallBack(EnumAllPanels), 1);
}
}
public bool EnumAllPanels(IntPtr hwnd, int index)
{
var className = PInvoke.GetClassName(hwnd);
var caption = PInvoke.GetWindowText(hwnd);
if (className == "AceApp" && caption.IndexOf("Microsoft Flight Simulator") == -1) // MSFS windows designation
{
if(index == 0)
PInvoke.ShowWindow(hwnd, PInvokeConstant.SW_MINIMIZE);
else
PInvoke.ShowWindow(hwnd, PInvokeConstant.SW_RESTORE);
}
return true;
}
}
}

View file

@ -1,21 +0,0 @@
using System.Windows.Forms;
namespace MSFSPopoutPanelManager.UIController
{
public interface IApplicationView
{
public Form Form { get; }
public IPanelSelectionView PanelSelection { get; }
public IPanelConfigurationView PanelConfiguration { get; }
public bool MinimizeToTray { get; set; }
public bool AlwaysOnTop { get; set; }
public bool AutoStart { get; set; }
public bool AutoPanning { get; set; }
}
}

View file

@ -1,9 +0,0 @@
namespace MSFSPopoutPanelManager.UIController
{
public interface IPanelConfigurationView
{
public bool IsPanelLocked { set; }
public bool IsPanelChangeDisabled { set; }
}
}

View file

@ -1,11 +0,0 @@
using System.Windows.Forms;
namespace MSFSPopoutPanelManager.UIController
{
public interface IPanelSelectionView
{
public Form Form { get; }
public bool ShowPanelLocationOverlay { get; set; }
}
}

View file

@ -1,252 +0,0 @@
using MSFSPopoutPanelManager.Provider;
using MSFSPopoutPanelManager.Shared;
using System;
using System.Drawing;
using System.Linq;
namespace MSFSPopoutPanelManager.UIController
{
public class PanelConfigurationController
{
private IPanelConfigurationView _view;
private bool _isDisablePanelChanges;
private static PInvoke.WinEventProc _winEvent; // keep this as static to prevent garbage collect or the app will crash
private IntPtr _winEventHook;
public PanelConfigurationController(IPanelConfigurationView view, DataStore dataStore)
{
_view = view;
DataStore = dataStore;
_winEvent = new PInvoke.WinEventProc(EventCallback);
}
public DataStore DataStore { get; set; }
public void Initialize()
{
// Populate panel data
DataStore.PanelConfigs.Clear();
DataStore.ActiveUserProfile.PanelConfigs.ForEach(p => DataStore.PanelConfigs.Add(p));
// Setup panel config event hooks
_winEventHook = PInvoke.SetWinEventHook(PInvokeConstant.EVENT_SYSTEM_MOVESIZEEND, PInvokeConstant.EVENT_OBJECT_LOCATIONCHANGE, WindowManager.GetApplicationProcess().Handle, _winEvent, 0, 0, PInvokeConstant.WINEVENT_OUTOFCONTEXT);
_view.IsPanelLocked = DataStore.ActiveUserProfile.IsLocked;
_isDisablePanelChanges = false;
_view.IsPanelChangeDisabled = false;
}
public event EventHandler RefreshDataUI;
public event EventHandler<EventArgs<int>> HightlightSelectedPanel;
public void UnhookWinEvent()
{
// Unhook all Win API events
PInvoke.UnhookWinEvent(_winEventHook);
}
public void LockPanelChanged(bool isLocked)
{
DataStore.ActiveUserProfile.IsLocked = isLocked;
SaveSettings();
_view.IsPanelLocked = isLocked;
}
public void DisablePanelChanges(bool isDisabled)
{
_isDisablePanelChanges = isDisabled;
_view.IsPanelChangeDisabled = isDisabled;
}
public void CellValueChanged(int rowIndex, PanelConfigDataColumn column, object newCellValue)
{
if (_isDisablePanelChanges || DataStore.ActiveUserProfile.IsLocked || DataStore.PanelConfigs == null || DataStore.PanelConfigs.Count == 0)
return;
int orignalLeft = DataStore.PanelConfigs[rowIndex].Left;
if (rowIndex != -1)
{
switch (column)
{
case PanelConfigDataColumn.PanelName:
var name = DataStore.PanelConfigs[rowIndex].PanelName;
if (name.IndexOf("(Custom)") == -1)
name = name + " (Custom)";
PInvoke.SetWindowText(DataStore.PanelConfigs[rowIndex].PanelHandle, name);
break;
case PanelConfigDataColumn.Left:
PInvoke.MoveWindow(DataStore.PanelConfigs[rowIndex].PanelHandle, DataStore.PanelConfigs[rowIndex].Left, DataStore.PanelConfigs[rowIndex].Top, DataStore.PanelConfigs[rowIndex].Width, DataStore.PanelConfigs[rowIndex].Height, true);
break;
case PanelConfigDataColumn.Top:
PInvoke.MoveWindow(DataStore.PanelConfigs[rowIndex].PanelHandle, DataStore.PanelConfigs[rowIndex].Left, DataStore.PanelConfigs[rowIndex].Top, DataStore.PanelConfigs[rowIndex].Width, DataStore.PanelConfigs[rowIndex].Height, true);
break;
case PanelConfigDataColumn.Width:
PInvoke.MoveWindow(DataStore.PanelConfigs[rowIndex].PanelHandle, DataStore.PanelConfigs[rowIndex].Left, DataStore.PanelConfigs[rowIndex].Top, DataStore.PanelConfigs[rowIndex].Width, DataStore.PanelConfigs[rowIndex].Height, true);
MSFSBugPanelShiftWorkaround(DataStore.PanelConfigs[rowIndex].PanelHandle, orignalLeft, DataStore.PanelConfigs[rowIndex].Top, DataStore.PanelConfigs[rowIndex].Width, DataStore.PanelConfigs[rowIndex].Height);
break;
case PanelConfigDataColumn.Height:
PInvoke.MoveWindow(DataStore.PanelConfigs[rowIndex].PanelHandle, DataStore.PanelConfigs[rowIndex].Left, DataStore.PanelConfigs[rowIndex].Top, DataStore.PanelConfigs[rowIndex].Width, DataStore.PanelConfigs[rowIndex].Height, true);
MSFSBugPanelShiftWorkaround(DataStore.PanelConfigs[rowIndex].PanelHandle, orignalLeft, DataStore.PanelConfigs[rowIndex].Top, DataStore.PanelConfigs[rowIndex].Width, DataStore.PanelConfigs[rowIndex].Height);
break;
case PanelConfigDataColumn.AlwaysOnTop:
DataStore.PanelConfigs[rowIndex].AlwaysOnTop = Convert.ToBoolean(newCellValue);
WindowManager.ApplyAlwaysOnTop(DataStore.PanelConfigs[rowIndex].PanelHandle, DataStore.PanelConfigs[rowIndex].AlwaysOnTop, new Rectangle(DataStore.PanelConfigs[rowIndex].Left, DataStore.PanelConfigs[rowIndex].Top, DataStore.PanelConfigs[rowIndex].Width, DataStore.PanelConfigs[rowIndex].Height));
break;
case PanelConfigDataColumn.HideTitlebar:
DataStore.PanelConfigs[rowIndex].HideTitlebar = Convert.ToBoolean(newCellValue);
WindowManager.ApplyHidePanelTitleBar(DataStore.PanelConfigs[rowIndex].PanelHandle, DataStore.PanelConfigs[rowIndex].HideTitlebar);
break;
default:
return;
}
SaveSettings();
}
}
public void CellValueIncrDecr(int rowIndex, PanelConfigDataColumn column, int changeAmount)
{
if (_isDisablePanelChanges || DataStore.ActiveUserProfile.IsLocked || DataStore.PanelConfigs == null || DataStore.PanelConfigs.Count == 0)
return;
int orignalLeft = DataStore.PanelConfigs[rowIndex].Left;
if (rowIndex != -1)
{
switch (column)
{
case PanelConfigDataColumn.Left:
PInvoke.MoveWindow(DataStore.PanelConfigs[rowIndex].PanelHandle, DataStore.PanelConfigs[rowIndex].Left + changeAmount, DataStore.PanelConfigs[rowIndex].Top, DataStore.PanelConfigs[rowIndex].Width, DataStore.PanelConfigs[rowIndex].Height, false);
DataStore.PanelConfigs[rowIndex].Left = DataStore.PanelConfigs[rowIndex].Left + changeAmount;
break;
case PanelConfigDataColumn.Top:
PInvoke.MoveWindow(DataStore.PanelConfigs[rowIndex].PanelHandle, DataStore.PanelConfigs[rowIndex].Left, DataStore.PanelConfigs[rowIndex].Top + changeAmount, DataStore.PanelConfigs[rowIndex].Width, DataStore.PanelConfigs[rowIndex].Height, false);
DataStore.PanelConfigs[rowIndex].Top = DataStore.PanelConfigs[rowIndex].Top + changeAmount;
break;
case PanelConfigDataColumn.Width:
PInvoke.MoveWindow(DataStore.PanelConfigs[rowIndex].PanelHandle, DataStore.PanelConfigs[rowIndex].Left, DataStore.PanelConfigs[rowIndex].Top, DataStore.PanelConfigs[rowIndex].Width + changeAmount, DataStore.PanelConfigs[rowIndex].Height, false);
MSFSBugPanelShiftWorkaround(DataStore.PanelConfigs[rowIndex].PanelHandle, orignalLeft, DataStore.PanelConfigs[rowIndex].Top, DataStore.PanelConfigs[rowIndex].Width + changeAmount, DataStore.PanelConfigs[rowIndex].Height);
DataStore.PanelConfigs[rowIndex].Width = DataStore.PanelConfigs[rowIndex].Width + changeAmount;
break;
case PanelConfigDataColumn.Height:
PInvoke.MoveWindow(DataStore.PanelConfigs[rowIndex].PanelHandle, DataStore.PanelConfigs[rowIndex].Left, DataStore.PanelConfigs[rowIndex].Top, DataStore.PanelConfigs[rowIndex].Width, DataStore.PanelConfigs[rowIndex].Height + changeAmount, false);
MSFSBugPanelShiftWorkaround(DataStore.PanelConfigs[rowIndex].PanelHandle, orignalLeft, DataStore.PanelConfigs[rowIndex].Top, DataStore.PanelConfigs[rowIndex].Width, DataStore.PanelConfigs[rowIndex].Height + changeAmount);
DataStore.PanelConfigs[rowIndex].Height = DataStore.PanelConfigs[rowIndex].Height + changeAmount;
break;
default:
return;
}
SaveSettings();
}
}
private void MSFSBugPanelShiftWorkaround(IntPtr handle, int originalLeft, int top, int width, int height)
{
// Fixed MSFS bug, create workaround where on 2nd or later instance of width adjustment, the panel shift to the left by itself
// Wait for system to catch up on panel coordinate that were just applied
System.Threading.Thread.Sleep(200);
Rectangle rectangle;
PInvoke.GetWindowRect(handle, out rectangle);
if (rectangle.Left != originalLeft)
PInvoke.MoveWindow(handle, originalLeft, top, width, height, false);
}
private Rectangle _lastWindowRectangle;
private int count = 1;
private void EventCallback(IntPtr hWinEventHook, uint iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime)
{
// check by priority to minimize escaping constraint
if (hWnd == IntPtr.Zero
|| idObject != 0
|| hWinEventHook != _winEventHook
|| !(iEvent == PInvokeConstant.EVENT_OBJECT_LOCATIONCHANGE || iEvent == PInvokeConstant.EVENT_SYSTEM_MOVESIZEEND)
|| _isDisablePanelChanges
|| DataStore.PanelConfigs == null || DataStore.PanelConfigs.Count == 0)
{
return;
}
var panelConfig = DataStore.PanelConfigs.FirstOrDefault(panel => panel.PanelHandle == hWnd);
if (panelConfig != null)
{
switch (iEvent)
{
case PInvokeConstant.EVENT_OBJECT_LOCATIONCHANGE:
Rectangle winRectangle;
PInvoke.GetWindowRect(panelConfig.PanelHandle, out winRectangle);
if (_lastWindowRectangle == winRectangle) // ignore duplicate callback messages
return;
_lastWindowRectangle = winRectangle;
Rectangle clientRectangle;
PInvoke.GetClientRect(panelConfig.PanelHandle, out clientRectangle);
if (!DataStore.ActiveUserProfile.IsLocked)
{
panelConfig.Left = winRectangle.Left;
panelConfig.Top = winRectangle.Top;
panelConfig.Width = clientRectangle.Width + 16;
panelConfig.Height = clientRectangle.Height + 39;
var rowIndex = DataStore.PanelConfigs.IndexOf(panelConfig);
HightlightSelectedPanel?.Invoke(this, new EventArgs<int>(rowIndex));
}
// Detect if window is maximized, if so, save settings
WINDOWPLACEMENT wp = new WINDOWPLACEMENT();
wp.length = System.Runtime.InteropServices.Marshal.SizeOf(wp);
PInvoke.GetWindowPlacement(hWnd, ref wp);
if (wp.showCmd == PInvokeConstant.SW_SHOWMAXIMIZED || wp.showCmd == PInvokeConstant.SW_SHOWMINIMIZED)
{
if (DataStore.ActiveUserProfile.IsLocked && panelConfig.PanelType == PanelType.CustomPopout)
PInvoke.ShowWindow(hWnd, PInvokeConstant.SW_RESTORE);
else
SaveSettings();
}
break;
case PInvokeConstant.EVENT_SYSTEM_MOVESIZEEND:
if(DataStore.ActiveUserProfile.IsLocked && panelConfig.PanelType == PanelType.CustomPopout)
PInvoke.MoveWindow(panelConfig.PanelHandle, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height, false);
else
SaveSettings();
break;
}
RefreshDataUI?.Invoke(this, null);
}
}
private void SaveSettings()
{
var profile = DataStore.ActiveUserProfile;
profile.PanelConfigs = DataStore.PanelConfigs.ToList();
var allProfiles = FileManager.ReadUserProfileData();
var index = allProfiles.FindIndex(x => x.ProfileId == profile.ProfileId);
allProfiles[index] = profile;
FileManager.WriteUserProfileData(allProfiles);
}
}
public enum PanelConfigDataColumn
{
PanelName,
Left,
Top,
Width,
Height,
AlwaysOnTop,
HideTitlebar
}
}

View file

@ -1,251 +0,0 @@
using MSFSPopoutPanelManager.Provider;
using MSFSPopoutPanelManager.Shared;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager.UIController
{
public class PanelSelectionController
{
private PanelSelectionManager _panelSelectionManager;
private IPanelSelectionView _view;
public PanelSelectionController(IPanelSelectionView view, DataStore dataStore)
{
_view = view;
DataStore = dataStore;
_panelSelectionManager = new PanelSelectionManager(_view.Form);
_panelSelectionManager.OnSelectionCompleted += HandleOnPanelSelectionCompleted;
}
public DataStore DataStore { get; set; }
public event EventHandler<EventArgs<PanelSelectionUIState>> OnUIStateChanged;
public event EventHandler OnPopOutCompleted;
public void Initialize()
{
_view.ShowPanelLocationOverlay = false;
DataStore.UserProfiles = new BindingList<UserProfileData>(FileManager.ReadUserProfileData().OrderBy(x => x.ProfileName).ToList());
DataStore.ActiveProfilePanelCoordinates = new BindingList<PanelSourceCoordinate>(DataStore.ActiveUserProfile == null ? new List<PanelSourceCoordinate>() : DataStore.ActiveUserProfile.PanelSourceCoordinates);
var defaultProfile = FileManager.ReadUserProfileData().Find(x => x.IsDefaultProfile);
if(defaultProfile == null)
{
DataStore.ActiveUserProfileId= -1;
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.NoProfileSelected));
}
else
{
DataStore.ActiveUserProfileId = defaultProfile.ProfileId;
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.ProfileSelected));
}
}
public void AddProfile(string profileName)
{
var newProfileId = DataStore.UserProfiles.Count > 0 ? DataStore.UserProfiles.Max(x => x.ProfileId) + 1 : 1;
var newPlaneProfile = new UserProfileData() { ProfileId = newProfileId, ProfileName = profileName };
var tmpList = DataStore.UserProfiles.ToList();
tmpList.Add(newPlaneProfile);
var index = tmpList.OrderBy(x => x.ProfileName).ToList().FindIndex(x => x.ProfileId == newProfileId);
DataStore.UserProfiles.Insert(index, newPlaneProfile);
FileManager.WriteUserProfileData(DataStore.UserProfiles.ToList()); // save the backing list data
DataStore.ActiveUserProfileId = newProfileId;
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.ProfileSelected));
Logger.Status($"Profile '{newPlaneProfile.ProfileName}' has been added successfully.", StatusMessageType.Info);
}
public void DeleteProfile()
{
if (DataStore.ActiveUserProfileId != -1)
{
var profileToRemove = DataStore.UserProfiles.First(x => x.ProfileId == DataStore.ActiveUserProfileId);
DataStore.UserProfiles.Remove(profileToRemove);
FileManager.WriteUserProfileData(DataStore.UserProfiles.ToList());
_panelSelectionManager.Reset();
DataStore.ActiveUserProfileId= -1;
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.NoProfileSelected));
Logger.Status($"Profile '{profileToRemove.ProfileName}' has been deleted successfully.", StatusMessageType.Info);
}
}
public void SetDefaultProfile()
{
if (DataStore.ActiveUserProfileId== -1)
return;
var profile = DataStore.UserProfiles.First(x => x.ProfileId == DataStore.ActiveUserProfileId);
profile.IsDefaultProfile = true;
foreach (var p in DataStore.UserProfiles)
{
if (p.ProfileId != DataStore.ActiveUserProfileId)
p.IsDefaultProfile = false;
}
FileManager.WriteUserProfileData(DataStore.UserProfiles.ToList());
Logger.Status($"Profile '{profile.ProfileName}' has been set as default.", StatusMessageType.Info);
}
public void ProfileChanged(int profileId)
{
Logger.ClearStatus();
if (profileId == -1)
{
DataStore.ActiveUserProfileId= -1;
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.NoProfileSelected));
}
else
{
DataStore.ActiveUserProfileId = profileId;
_panelSelectionManager.Reset();
_view.ShowPanelLocationOverlay = false;
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.PanelSelectionCompletedValid));
}
}
public bool HasExistingPanelCoordinates
{
get
{
return DataStore.ActiveUserProfile != null && DataStore.ActiveUserProfile.PanelSourceCoordinates.Count > 0;
}
}
public void StartPanelSelection()
{
DataStore.ActiveUserProfile.Reset();
_panelSelectionManager.PanelCoordinates = DataStore.ActiveUserProfile.PanelSourceCoordinates;
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.PanelSelectionStarted));
// Temporary minimize the app during panel selection
if (_view.Form != null)
_view.Form.WindowState = FormWindowState.Minimized;
// Set MSFS to foreground for panel selection
var simulatorProcess = WindowManager.GetSimulatorProcess();
if (simulatorProcess != null)
PInvoke.SetForegroundWindow(simulatorProcess.Handle);
_panelSelectionManager.Start();
Logger.Status("Panels selection has started.", StatusMessageType.Info);
}
private void HandleOnPanelSelectionCompleted(object sender, EventArgs e)
{
// If enable, save the current viewport into custom view by Ctrl-Alt-0
if (FileManager.ReadAppSettingData().UseAutoPanning)
{
var simualatorProcess = WindowManager.GetSimulatorProcess();
if (simualatorProcess != null)
{
InputEmulationManager.SaveCustomViewZero(simualatorProcess.Handle);
}
}
if (_view.Form != null)
_view.Form.WindowState = FormWindowState.Normal;
PInvoke.SetForegroundWindow(_view.Form.Handle);
DataStore.ActiveProfilePanelCoordinates.Clear();
DataStore.ActiveUserProfile.PanelSourceCoordinates.ForEach(c => DataStore.ActiveProfilePanelCoordinates.Add(c));
FileManager.WriteUserProfileData(DataStore.UserProfiles.ToList());
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.PanelSelectionCompletedValid));
_view.ShowPanelLocationOverlay = true;
if (DataStore.ActiveUserProfile.PanelSourceCoordinates.Count > 0)
Logger.Status("Panels selection is completed. Please click 'Start Pop Out' to start popping out these panels.", StatusMessageType.Info);
else
Logger.Status("Panels selection is completed. No panel has been selected.", StatusMessageType.Info);
}
public void SetPanelLocationOverlayChanged()
{
_panelSelectionManager.PanelCoordinates = DataStore.ActiveProfilePanelCoordinates.ToList();
_panelSelectionManager.ShowPanelLocationOverlay(_view.ShowPanelLocationOverlay);
}
public void StartPopOut()
{
if (WindowManager.GetApplicationProcess() == null)
return;
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.PopoutStarted));
Logger.Status("Panel pop out and separation in progress. Please wait......", StatusMessageType.Info);
WindowManager.CloseAllCustomPopoutPanels();
Thread.Sleep(1000); // allow time for the mouse to be stopped moving by the user
_panelSelectionManager.ShowPanelLocationOverlay(false);
_view.ShowPanelLocationOverlay = false;
var simulatorProcess = WindowManager.GetSimulatorProcess();
if(simulatorProcess == null)
{
Logger.Status("MSFS has not been started. Please try again at a later time.", StatusMessageType.Error);
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.PanelSelectionCompletedValid));
return;
}
PopoutSeparationManager popoutSeparationManager = new PopoutSeparationManager(simulatorProcess.Handle, DataStore.ActiveUserProfile);
// Temporary make app go to background before pop out process
WindowManager.ApplyAlwaysOnTop(_view.Form.Handle, false, _view.Form.Bounds);
var result = popoutSeparationManager.StartPopout();
WindowManager.ApplyAlwaysOnTop(_view.Form.Handle, true, _view.Form.Bounds);
if (result)
{
OnPopOutCompleted?.Invoke(this, null);
// Save data
FileManager.WriteUserProfileData(DataStore.UserProfiles.ToList());
PInvoke.SetForegroundWindow(_view.Form.Handle);
}
else
{
_panelSelectionManager.ShowPanelLocationOverlay(true);
_view.ShowPanelLocationOverlay = true;
}
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.PanelSelectionCompletedValid));
}
}
public enum PanelSelectionUIState
{
NoProfileSelected,
ProfileSelected,
PanelSelectionStarted,
PanelSelectionCompletedValid,
PanelSelectionCompletedInvalid,
PopoutStarted,
}
}

View file

@ -1,15 +1,28 @@
# Version History
<hr/>
## Version 3.1.0
* Updated UI to use menu bar to control most application settings.
* Added long awaited auto save feature. Application no longer requires user to manually save profile after each panel change. All panel adjustments are automatically save.
* Added panel lock feature to complement autosave feature. When panels are lock and are being moved or resize, their new location information will not get save. Also, for instrumentation pop out panels, when panels are locked, any accidental movement of the panels will return them to previously saved locations. For built-in panels such as ATC, VFR Map, etc, they can still be moved but their saved location will not get change.
* Added keyboard shortcuts for commonly used function. The buttons for -10px, -1px, +1px, and +10px now has keyboard shortcut of 'Ctrl -', 'Ctrl [', 'Ctrl ]', and 'Ctrl +' respectively.
* Added miminize all panels feature. This allows user to temporary see what is behind all open pop out panels. (This is an user requested feature with Github ticket #6).
* Various smaller bug fixes and usability enhancements.
## Version 3.2.0 (Beta)
* 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.
* 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 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.
* 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.
* New logo icon for the app.
* New dark theme for the entire UI.
* Technical Note - Application is ported and rewritten with .NET WPF framework instead of WinForms.
##
## Version 3.1.0.2 (Hotfix)
* Change application DPI mode to use DPIPerMonitor and added DPI aware setting to application to fix user configuration of using a 4K high DPI monitor (with windows scaling of greater than 100%) in conjunction of one or more lower DPI monitors such as 1440P or 1080P. With this configuration, the application rendering, panel selection and pop out panel adjustments and do not work correctly.
## Version 3.1.0
* Updated and streamlined UI to have a menu bar to control most application settings.
* Added long awaited auto save feature. Application no longer requires user to manually save profile after each panel change. All panel adjustments are saved automatically.
* Added panel lock feature to complement autosave feature. When panels are locked and are being moved or resized, their new location information will not get save. Also, for instrumentation pop out panels, when panels are locked, any accidental movement of the panels will return them to previously saved locations. For built-in panels such as ATC, VFR Map, etc, they can still be moved but their saved location will not get change.
* Added keyboard shortcuts for commonly use function. The buttons for -10px, -1px, +1px, and +10px now has keyboard shortcut of Ctrl -, Ctrl [, Ctrl ], and Ctrl + respectively.
* Added minimize all panels feature. This allows user to temporary see what is behind all open pop out panels. (This is a user requested feature with Github ticket [#6](https://github.com/hawkeye-stan/msfs-popout-panel-manager/issues/6)).
* Various small bug fixes and usability enhancements.
## Version 3.0.1
* Added workaround for MSFS pop out panel adjustment bug when using the position data grid to adjust width and height will work as expected.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

View file

@ -0,0 +1,46 @@
<mah:MetroWindow x:Class="MSFSPopoutPanelManager.WpfApp.AddProfileDialog"
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:local="clr-namespace:MSFSPopoutPanelManager.WpfApp"
mc:Ignorable="d"
Title="Add Profile"
Height="220"
Width="600"
ResizeMode="NoResize"
ContentRendered="Window_ContentRendered"
KeyDown="Window_KeyDown"
Background="Transparent">
<Window.Resources>
<CollectionViewSource x:Key="UserProfilesViewSource" Source="{Binding Path=UserProfiles, Mode=OneWay}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="ProfileName" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid>
<DockPanel>
<WrapPanel DockPanel.Dock="Top" HorizontalAlignment="Center" Margin="0,30,0,0">
<Label Content="Copy Profile" HorizontalAlignment="Right" Width="120"/>
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="420"
ItemsSource="{Binding Source={StaticResource UserProfilesViewSource}}"
SelectedValue="{Binding Path=SelectedCopyProfileId, Mode=TwoWay}"
DisplayMemberPath="ProfileName"
SelectedValuePath="ProfileId"/>
</WrapPanel>
<WrapPanel DockPanel.Dock="Top" HorizontalAlignment="Center" Margin="0,20,0,0">
<Label Content="Profile Name" HorizontalAlignment="Right" Width="120"/>
<TextBox Name="txtProfileName" HorizontalAlignment="Left" TextWrapping="Wrap" Width="420" TextChanged="txtProfileName_TextChanged"/>
</WrapPanel>
<WrapPanel DockPanel.Dock="Bottom" HorizontalAlignment="Right" Margin="0,20,30,0">
<Button IsDefault="True" Name="btnDialogOk" Click="btnDialogOk_Click" Width="80" Margin="0,0,20,0">_Ok</Button>
<Button IsCancel="True" Width="80">_Cancel</Button>
</WrapPanel>
</DockPanel>
</Grid>
</mah:MetroWindow>

View file

@ -0,0 +1,69 @@
using MahApps.Metro.Controls;
using MSFSPopoutPanelManager.Model;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Input;
namespace MSFSPopoutPanelManager.WpfApp
{
/// <summary>
/// Interaction logic for AddProfileDialog.xaml
/// </summary>
public partial class AddProfileDialog : MetroWindow
{
public List<UserProfile> UserProfiles { get; set; }
public int SelectedCopyProfileId { get; set; }
public AddProfileDialog(ObservableCollection<UserProfile> userProfiles)
{
InitializeComponent();
UserProfiles = userProfiles.ToList();
UserProfiles.Insert(0, new UserProfile() { ProfileId = -1 });
this.DataContext = this;
SelectedCopyProfileId = -1;
btnDialogOk.IsEnabled = false;
}
private void btnDialogOk_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = true;
}
public string ProfileName
{
get { return txtProfileName.Text; }
}
private void Window_ContentRendered(object sender, EventArgs e)
{
txtProfileName.Focus();
}
private void Window_KeyDown(object sender, KeyEventArgs e)
{
bool isNumber = e.Key >= Key.D0 && e.Key <= Key.D9 || e.Key >= Key.NumPad0 && e.Key <= Key.NumPad9;
bool isLetter = e.Key >= Key.A && e.Key <= Key.Z || (e.Key >= Key.A && e.Key <= Key.Z && e.KeyboardDevice.Modifiers == ModifierKeys.Shift);
bool isCtrlA = e.Key == Key.A && e.KeyboardDevice.Modifiers == ModifierKeys.Control;
bool isCtrlV = e.Key == Key.V && e.KeyboardDevice.Modifiers == ModifierKeys.Control;
bool isBack = e.Key == Key.Back;
bool isLeftOrRight = e.Key == Key.Left || e.Key == Key.Right;
bool isEnterOrCancel = e.Key == Key.Enter || e.Key == Key.Escape;
if (isNumber || isLetter || isCtrlA || isCtrlV || isBack || isLeftOrRight || isEnterOrCancel)
e.Handled = false;
else
e.Handled = true;
}
private void txtProfileName_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
btnDialogOk.IsEnabled = txtProfileName.Text.Length > 0;
}
}
}

63
WpfApp/App.xaml Normal file
View file

@ -0,0 +1,63 @@
<Application x:Class="MSFSPopoutPanelManager.WpfApp.App"
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:local="clr-namespace:MSFSPopoutPanelManager.WpfApp"
StartupUri="ApplicationWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- MahApps.Metro resource dictionaries. Make sure that all file names are Case Sensitive! -->
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<!-- Theme setting -->
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Dark.Steel.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style BasedOn="{StaticResource MahApps.Styles.MenuItem}" TargetType="MenuItem">
<Setter Property="FontSize" Value="14" />
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="controls:ControlsHelper.ContentCharacterCasing" Value="Normal"/>
<Setter Property="Margin" Value="5, 0"/>
</Style>
<Style BasedOn="{StaticResource MahApps.Styles.Button}" TargetType="Button">
<Setter Property="FontSize" Value="14" />
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="controls:ControlsHelper.ContentCharacterCasing" Value="Normal"/>
<Setter Property="Height" Value="36"/>
</Style>
<Style BasedOn="{StaticResource MahApps.Styles.Label}" TargetType="Label">
<Setter Property="FontSize" Value="16" />
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="FontWeight" Value="Normal"/>
</Style>
<Style BasedOn="{StaticResource MahApps.Styles.TextBox}" TargetType="TextBox">
<Setter Property="FontSize" Value="16" />
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="FontWeight" Value="Normal"/>
</Style>
<Style BasedOn="{StaticResource MahApps.Styles.TextBlock}" TargetType="TextBlock">
<Setter Property="FontSize" Value="16" />
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="FontWeight" Value="Normal"/>
</Style>
<Style BasedOn="{StaticResource MahApps.Styles.CheckBox}" TargetType="CheckBox">
<Setter Property="FontSize" Value="16" />
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="FontWeight" Value="Normal"/>
</Style>
<Style BasedOn="{StaticResource MahApps.Styles.ComboBox}" TargetType="ComboBox">
<Setter Property="Padding" Value="10, 5, 10, 5" />
<Setter Property="FontSize" Value="16" />
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="FontWeight" Value="Normal"/>
</Style>
<Style BasedOn="{StaticResource MahApps.Styles.DataGrid}" TargetType="DataGrid">
<Setter Property="FontSize" Value="16" />
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="FontWeight" Value="Normal"/>
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>

143
WpfApp/App.xaml.cs Normal file
View file

@ -0,0 +1,143 @@
using log4net;
using log4net.Config;
using MSFSPopoutPanelManager.Provider;
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace MSFSPopoutPanelManager.WpfApp
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private static Mutex _mutex = null;
private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
protected override void OnStartup(StartupEventArgs e)
{
// Setup log4Net
var logRepository = LogManager.GetRepository(Assembly.GetEntryAssembly());
XmlConfigurator.Configure(logRepository, new FileInfo("log4net.config"));
// Override default WPF System DPI Awareness to Per Monitor Awareness
// For this to work make sure [assembly:dpiawareness]
DpiAwareness.Enable(Log);
const string appName = "MSFS PopOut Panel Manager";
bool createdNew;
_mutex = new Mutex(true, appName, out createdNew);
if (!createdNew)
{
//app is already running! Exiting the application
Application.Current.Shutdown();
}
else
{
// Setup all unhandle exception handlers
Dispatcher.UnhandledException += HandleDispatcherException;
TaskScheduler.UnobservedTaskException += HandleTaskSchedulerUnobservedTaskException;
AppDomain.CurrentDomain.UnhandledException += HandledDomainException;
}
base.OnStartup(e);
}
private void HandleTaskSchedulerUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
Log.Error(e.Exception.Message, e.Exception);
ShowExceptionDialog();
}
private void HandleDispatcherException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
e.Handled = true;
Log.Error(e.Exception.Message, e.Exception);
ShowExceptionDialog();
}
private void HandledDomainException(object sender, UnhandledExceptionEventArgs e)
{
var exception = (Exception)e.ExceptionObject;
Log.Error(exception.Message, exception);
ShowExceptionDialog();
}
private void ShowExceptionDialog()
{
var messageBoxTitle = "MSFS Pop Out Panel Manager - Critical Error!";
var messageBoxMessage = "Application has encountered a critical error and will be closed.\nPlease see the file 'error.log' for information.";
var messageBoxButtons = MessageBoxButton.OK;
// Let the user decide if the app should die or not (if applicable).
if (MessageBox.Show(messageBoxMessage, messageBoxTitle, messageBoxButtons) == MessageBoxResult.OK)
{
Application.Current.Shutdown();
}
}
}
public enum PROCESS_DPI_AWARENESS
{
Process_DPI_Unaware = 0,
Process_System_DPI_Aware = 1,
Process_Per_Monitor_DPI_Aware = 2
}
public enum DPI_AWARENESS_CONTEXT
{
DPI_AWARENESS_CONTEXT_UNAWARE = 16,
DPI_AWARENESS_CONTEXT_SYSTEM_AWARE = 17,
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = 18,
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = 34
}
public static class DpiAwareness
{
public static void Enable(ILog log4net)
{
// Windows 8.1 added support for per monitor DPI
if (Environment.OSVersion.Version >= new Version(6, 3, 0))
{
// Windows 10 creators update added support for per monitor v2
if (Environment.OSVersion.Version >= new Version(10, 0, 15063))
{
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
}
else
{
SetProcessDpiAwareness(PROCESS_DPI_AWARENESS.Process_Per_Monitor_DPI_Aware);
}
}
else
{
SetProcessDPIAware();
}
var process = DiagnosticManager.GetApplicationProcess();
PROCESS_DPI_AWARENESS outValue;
GetProcessDpiAwareness(process.Handle, out outValue);
log4net.Info($"DPI Awareness is set to: {outValue}");
}
[DllImport("User32.dll")]
internal static extern bool SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT dpiFlag);
[DllImport("SHCore.dll")]
internal static extern bool SetProcessDpiAwareness(PROCESS_DPI_AWARENESS awareness);
[DllImport("User32.dll")]
internal static extern bool SetProcessDPIAware();
[DllImport("SHCore.dll", SetLastError = true)]
internal static extern void GetProcessDpiAwareness(IntPtr hprocess, out PROCESS_DPI_AWARENESS awareness);
}
}

View file

@ -0,0 +1,116 @@
<mah:MetroWindow x:Class="MSFSPopoutPanelManager.WpfApp.ApplicationWindow"
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:tb="http://www.hardcodet.net/taskbar"
xmlns:c="clr-namespace:CalcBinding;assembly=CalcBinding"
xmlns:local="clr-namespace:MSFSPopoutPanelManager.WpfApp"
xmlns:shared="clr-namespace:MSFSPopoutPanelManager.Shared;assembly=Shared"
mc:Ignorable="d"
Title="MSFS Pop Out Panel Manager"
Height="670"
Width="920"
WindowStartupLocation="CenterScreen"
ResizeMode="CanMinimize"
Icon="logo.ico"
StateChanged="Window_StateChanged"
WindowState="{Binding InitialWindowState, Mode=OneWay}"
Loaded="Window_Loaded"
Closing="Window_Closing">
<Window.InputBindings>
<KeyBinding Command="{Binding Path=MinimizeAllPanelsCommand}" Modifiers="Ctrl" Key="M" />
</Window.InputBindings>
<Grid>
<tb:TaskbarIcon Name="notifyIcon"
ToolTipText="MSFS Pop Out Panel Manager"
IconSource="logo.ico"
MenuActivation="RightClick"
Visibility="Visible">
<tb:TaskbarIcon.ContextMenu>
<ContextMenu>
<MenuItem Header="Profiles" ItemsSource="{Binding DataStore.UserProfiles}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem" BasedOn="{StaticResource {x:Type MenuItem}}">
<Setter Property="MenuItem.Header" Value="{Binding Path=ProfileName}" />
<Setter Property="MenuItem.IsChecked" Value="{Binding Path=IsActive}" />
<Setter Property="MenuItem.Command" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}, Path=DataContext.UserProfileSelectCommand}" />
<Setter Property="MenuItem.CommandParameter" Value="{Binding Path=ProfileId}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
<MenuItem Header="Show Panel Location Overlay" IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}, Path=DataContext.PanelSelectionViewModel.IsShownPanelCoorOverlay}" Command="{Binding Path=ShowPanelCoorOverlayCommand}"></MenuItem>
<MenuItem Header="Start Pop Out" Command="{Binding Path=StartPopOutCommand}" InputGestureText="Ctrl+Alt+P"></MenuItem>
<Separator></Separator>
<MenuItem Header="Exit" Command="{Binding Path=ExitCommand}" ></MenuItem>
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
</tb:TaskbarIcon>
<DockPanel Grid.ColumnSpan="4">
<DockPanel DockPanel.Dock="Top">
<Menu Width="870" Background="#FF252525">
<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>
<Separator />
<MenuItem Name="menuItem_Exit" Header="Exit" Command="{Binding Path=ExitCommand}"/>
</MenuItem>
<MenuItem Header="View">
<MenuItem Name="menuItem_MinimizeAllPanels" Header="Minimize All Panels " InputGestureText="Ctrl+M" Command="{Binding Path=MinimizeAllPanelsCommand}" IsChecked="{Binding Path=IsMinimizedAllPanels, Mode=TwoWay}" IsEnabled="{c:Binding Path='IsShownPanelConfigurationScreen'}"/>
</MenuItem>
<MenuItem Header="Help">
<MenuItem Name="menuItem_UserGuide" Header="User Guide" Command="{Binding Path=UserGuideCommand}"/>
<MenuItem Name="menuItem_DownloadLatestRelease" Header="Download Latest Release" Command="{Binding Path=DownloadLatestReleaseCommand}"/>
</MenuItem>
</Menu>
<Image Width="20" ToolTip="{Binding Path=DataStore.CurrentMsfsPlaneTitle}">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DataStore.IsSimulatorStarted , Mode=OneWay}" Value="True">
<Setter Property="Source" Value="Resources/msfsconnected.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=DataStore.IsSimulatorStarted , Mode=OneWay}" Value="False">
<Setter Property="Source" Value="Resources/msfsdisconnected.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</DockPanel>
<DockPanel>
<StackPanel Name="panelSteps" DockPanel.Dock="Top" Height="505" Background="#FF323C64">
</StackPanel>
<WrapPanel DockPanel.Dock="Top" Orientation="Horizontal" Height="70" Background="#FF252523">
<Label Content="Status" Margin="5, 10, 5, 5"/>
<TextBox Name="txtStatus" Margin="5, 10, 5, 5" Height="55" TextWrapping="Wrap" IsReadOnly="True" Width="835" Background="#FF45494A" Text="{Binding Path=StatusMessage.Message, Mode=OneWay}">
<TextBox.Style>
<Style TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=StatusMessage.MessageType, Mode=OneWay}" Value="{x:Static shared:StatusMessageType.Error}">
<Setter Property="Foreground" Value="#FFFF6262"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</WrapPanel>
<WrapPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Background="#FF252525" Height="45">
<Label Margin="425, 0, 0, 5" FontSize="14" Content="Version" />
<Label Name="lblVersion" Margin="0, 0, 0, 5" FontSize="14" Content="{Binding Path = ApplicationVersion, Mode=OneWay}" />
</WrapPanel>
</DockPanel>
</DockPanel>
</Grid>
</mah:MetroWindow>

View file

@ -0,0 +1,103 @@
using Hardcodet.Wpf.TaskbarNotification;
using MahApps.Metro.Controls;
using MSFSPopoutPanelManager.Provider;
using MSFSPopoutPanelManager.Shared;
using MSFSPopoutPanelManager.WpfApp.ViewModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Interop;
namespace MSFSPopoutPanelManager.WpfApp
{
/// <summary>
/// Interaction logic for ApplicationWindow.xaml
/// </summary>
public partial class ApplicationWindow : MetroWindow
{
private ApplicationViewModel _viewModel;
// This command has to be here since it doesn't work in view model, window StateChanged never gets fire
public DelegateCommand RestoreWindowCommand => new DelegateCommand((o) => { this.WindowState = WindowState.Normal; }, (o) => { return true; });
public ApplicationWindow()
{
InitializeComponent();
_viewModel = new ApplicationViewModel();
_viewModel.ShowContextMenuBalloonTip += HandleShowContextMenuBalloonTip;
this.DataContext = _viewModel;
UserControlPanelSelection userControlPanelSelection = new UserControlPanelSelection(_viewModel.PanelSelectionViewModel);
UserControlPanelConfiguration userControlPanelConfiguration = new UserControlPanelConfiguration(_viewModel.PanelConfigurationViewModel);
Binding panelSelectionVisibilityBinding = new Binding()
{
Source = this.DataContext,
Path = new PropertyPath("IsShownPanelSelectionScreen"),
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
Converter = new BoolToVisibilityConverter()
};
BindingOperations.SetBinding(userControlPanelSelection, UserControl.VisibilityProperty, panelSelectionVisibilityBinding);
Binding panelConfigurationVisibilityBinding = new Binding()
{
Source = this.DataContext,
Path = new PropertyPath("IsShownPanelConfigurationScreen"),
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
Converter = new BoolToVisibilityConverter()
};
BindingOperations.SetBinding(userControlPanelConfiguration, UserControl.VisibilityProperty, panelConfigurationVisibilityBinding);
panelSteps.Children.Add(userControlPanelSelection);
panelSteps.Children.Add(userControlPanelConfiguration);
notifyIcon.DoubleClickCommand = RestoreWindowCommand;
}
private void HandleShowContextMenuBalloonTip(object sender, EventArgs<StatusMessage> e)
{
if (e.Value.MessageType == StatusMessageType.Error)
{
notifyIcon.ShowBalloonTip("MSFS Pop Out Panel Manager Error", e.Value.Message, BalloonIcon.Error);
}
}
private void Window_StateChanged(object sender, System.EventArgs e)
{
switch (this.WindowState)
{
case WindowState.Maximized:
this.ShowInTaskbar = true;
break;
case WindowState.Minimized:
if (_viewModel.DataStore.AppSetting.MinimizeToTray)
{
notifyIcon.Visibility = Visibility.Visible;
this.ShowInTaskbar = false;
}
break;
case WindowState.Normal:
notifyIcon.Visibility = Visibility.Hidden;
this.ShowInTaskbar = true;
// Fix always on top status once app is minimize and then restore
if (_viewModel.DataStore.AppSetting.AlwaysOnTop)
WindowManager.ApplyAlwaysOnTop(_viewModel.DataStore.ApplicationHandle, true);
break;
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_viewModel.DataStore.ApplicationHandle = new WindowInteropHelper(Window.GetWindow(this)).Handle;
_viewModel.DataStore.ApplicationWindow = this;
_viewModel.Initialize();
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
_viewModel.ExitCommand.Execute(null);
}
}
}

12
WpfApp/AssemblyInfo.cs Normal file
View file

@ -0,0 +1,12 @@
using System.Windows;
using System.Windows.Media;
[assembly: DisableDpiAwareness]
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View file

@ -0,0 +1,24 @@
<mah:MetroWindow x:Class="MSFSPopoutPanelManager.WpfApp.ConfirmationDialog"
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:local="clr-namespace:MSFSPopoutPanelManager.WpfApp"
mc:Ignorable="d"
Title="Confirmation"
ResizeMode="NoResize"
Height="180"
SizeToContent="Width">
<Grid>
<DockPanel Width="Auto">
<WrapPanel DockPanel.Dock="Top" HorizontalAlignment="Left" Margin="30,30,30,0" Height="45" Width="Auto">
<TextBlock Name="txtMessage" Text="Message" HorizontalAlignment="Left" TextWrapping="Wrap" Width="Auto"/>
</WrapPanel>
<WrapPanel DockPanel.Dock="Bottom" HorizontalAlignment="Right" Margin="0,30,30,0" Width="Auto">
<Button Name="btnDialogOk" Click="btnDialogYes_Click" Width="80" Margin="0,0,20,0">_Yes</Button>
<Button IsDefault="True" IsCancel="True" Width="80">_No</Button>
</WrapPanel>
</DockPanel>
</Grid>
</mah:MetroWindow>

View file

@ -0,0 +1,35 @@
using MahApps.Metro.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace MSFSPopoutPanelManager.WpfApp
{
/// <summary>
/// Interaction logic for ConfirmationDialog.xaml
/// </summary>
public partial class ConfirmationDialog : MetroWindow
{
public ConfirmationDialog(string title, string message)
{
InitializeComponent();
this.Title = title;
this.txtMessage.Text = message;
}
private void btnDialogYes_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = true;
}
}
}

3
WpfApp/FodyWeavers.xml Normal file
View file

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<PropertyChanged />
</Weavers>

74
WpfApp/FodyWeavers.xsd Normal file
View file

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="PropertyChanged" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="InjectOnPropertyNameChanged" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if the On_PropertyName_Changed feature is enabled.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="TriggerDependentProperties" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if the Dependent properties feature is enabled.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="EnableIsChangedProperty" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if the IsChanged property feature is enabled.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="EventInvokerNames" type="xs:string">
<xs:annotation>
<xs:documentation>Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="CheckForEquality" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="CheckForEqualityUsingBaseEquals" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if equality checks should use the Equals method resolved from the base class.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="UseStaticEqualsFromBase" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if equality checks should use the static Equals method resolved from the base class.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="SuppressWarnings" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to turn off build warnings from this weaver.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="SuppressOnPropertyNameChangedWarning" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to turn off build warnings about mismatched On_PropertyName_Changed methods.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

View file

@ -0,0 +1,43 @@
<mah:MetroWindow x:Class="MSFSPopoutPanelManager.WpfApp.OnScreenMessageDialog"
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:local="clr-namespace:MSFSPopoutPanelManager.WpfApp"
mc:Ignorable="d"
Height="Auto"
Width="Auto"
SizeToContent="WidthAndHeight"
Title="MSFS Pop Out Panel Manager"
WindowStartupLocation="CenterOwner"
Opacity="0.7"
ResizeMode="NoResize"
WindowStyle="None">
<Grid>
<DockPanel>
<WrapPanel DockPanel.Dock="Top" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="30,25,30,25" Height="75">
<Image Width="40" Height="40" HorizontalAlignment="Left">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=MessageIcon, Mode=OneWay}" Value="{x:Static local:MessageIcon.Success}">
<Setter Property="Source" Value="Resources/check.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=MessageIcon, Mode=OneWay}" Value="{x:Static local:MessageIcon.Failed}">
<Setter Property="Source" Value="Resources/cross.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=MessageIcon, Mode=OneWay}" Value="{x:Static local:MessageIcon.Info}">
<Setter Property="Source" Value="Resources/info.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Grid Height="75" VerticalAlignment="Center" Margin="10, 0, 0, 0" Width="Auto">
<TextBlock Name="txtMessage" Width="Auto" Text="Message" HorizontalAlignment="Center" VerticalAlignment="Center" TextWrapping="Wrap"/>
</Grid>
</WrapPanel>
</DockPanel>
</Grid>
</mah:MetroWindow>

View file

@ -0,0 +1,71 @@
using MahApps.Metro.Controls;
using MSFSPopoutPanelManager.Provider;
using System;
using System.Diagnostics;
using System.Drawing;
using System.Windows;
using System.Windows.Interop;
namespace MSFSPopoutPanelManager.WpfApp
{
/// <summary>
/// Interaction logic for OnScreenMessageDialog.xaml
/// </summary>
public partial class OnScreenMessageDialog : MetroWindow
{
public MessageIcon MessageIcon { get; set; }
public OnScreenMessageDialog(string message) : this(message, MessageIcon.Info, 2) { }
public OnScreenMessageDialog(string message, MessageIcon messageIcon) : this(message, messageIcon, 2) { }
public OnScreenMessageDialog(string message, int duration) : this(message, MessageIcon.Info, duration) { }
public OnScreenMessageDialog(string message, MessageIcon messageIcon, int duration)
{
InitializeComponent();
this.DataContext = this;
this.Topmost = true;
this.txtMessage.Text = message;
MessageIcon = messageIcon;
this.Loaded += (sender, e) =>
{
var dialogHandle = new WindowInteropHelper(Window.GetWindow(this)).Handle;
var simulatorProcessHandle = DiagnosticManager.GetSimulatorProcess().Handle;
Rectangle rectangle;
PInvoke.GetWindowRect(DiagnosticManager.GetSimulatorProcess().Handle, out rectangle);
Rectangle clientRectangle;
PInvoke.GetClientRect(DiagnosticManager.GetSimulatorProcess().Handle, out clientRectangle);
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();
timer.Elapsed += (sender, e) =>
{
Application.Current.Dispatcher.Invoke(() =>
{
this.Close(); timer.Enabled = false;
});
};
timer.Interval = duration * 1000;
timer.Enabled = true;
}
}
public enum MessageIcon
{
Info,
Success,
Failed
}
}

View file

@ -0,0 +1,22 @@
<Window x:Class="MSFSPopoutPanelManager.WpfApp.PanelCoorOverlay"
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:local="clr-namespace:MSFSPopoutPanelManager.WpfApp"
mc:Ignorable="d"
Title="PanelCoorOverlay"
ResizeMode="NoResize"
WindowStyle="None"
Height="46"
Width="55"
SizeToContent="WidthAndHeight"
Background="#01F0F0FF"
AllowsTransparency="True"
Topmost="True"
MouseMove="Window_MouseMove">
<Grid>
<Label Name="lblPanelIndex" Content="1" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Image Height="46" Width="55" HorizontalAlignment="Left" VerticalAlignment="Top" Opacity="1" Source="resources/transparent.png"/>
</Grid>
</Window>

View file

@ -0,0 +1,31 @@
using System;
using System.Windows;
using System.Windows.Interop;
namespace MSFSPopoutPanelManager.WpfApp
{
/// <summary>
/// Interaction logic for PanelCoorOverlay.xaml
/// </summary>
public partial class PanelCoorOverlay : Window
{
public bool IsEditingPanelLocation { get; set; }
public IntPtr WindowHandle { get; set; }
public PanelCoorOverlay(int panelIndex)
{
InitializeComponent();
this.lblPanelIndex.Content = panelIndex;
IsEditingPanelLocation = false;
}
private void Window_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (IsEditingPanelLocation && e.LeftButton == System.Windows.Input.MouseButtonState.Pressed)
{
this.DragMove();
}
}
}
}

BIN
WpfApp/Resources/check.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

BIN
WpfApp/Resources/cross.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

BIN
WpfApp/Resources/info.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,182 @@
<UserControl x:Class="MSFSPopoutPanelManager.WpfApp.UserControlPanelConfiguration"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MSFSPopoutPanelManager.WpfApp"
xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:c="clr-namespace:CalcBinding;assembly=CalcBinding"
mc:Ignorable="d"
Height="505"
Width="920"
Background="#FF323A64">
<UserControl.Resources>
<ResourceDictionary>
<Style x:Key="TextBoxColumnFocus" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Style.Triggers>
<Trigger Property="IsFocused" Value="true">
<Setter Property="Background" Value="#FF576573" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<UserControl.InputBindings>
<KeyBinding Command="{Binding Path=MinusTenPixelCommand}" CommandParameter="-10" Modifiers="Ctrl" Key="OemMinus"/>
<KeyBinding Command="{Binding Path=MinusOnePixelCommand}" CommandParameter="-1" Modifiers="Ctrl" Key="OemOpenBrackets" />
<KeyBinding Command="{Binding Path=PlusOnePixelCommand}" CommandParameter="1" Modifiers="Ctrl" Key="OemCloseBrackets" />
<KeyBinding Command="{Binding Path=PlusTenPixelCommand}" CommandParameter="10" Modifiers="Ctrl" Key="OemPlus" />
</UserControl.InputBindings>
<Grid>
<DockPanel>
<WrapPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="20,10,0,0" HorizontalAlignment="Left">
<Label Content="Panel Locations and Settings -" Margin="0,0,0,0" HorizontalAlignment="Left"/>
<Label Content="{Binding Path=DataStore.ActiveUserProfile.ProfileName}" Margin="0,0,0,0" HorizontalAlignment="Left"/>
<DataGrid Name="PanelConfigGrid" HorizontalAlignment="Center" Width="880" Height="390" Margin="0 10 0 0" AutoGenerateColumns="False" CanUserResizeColumns="False" HorizontalScrollBarVisibility="Disabled"
CanUserReorderColumns="False" CanUserResizeRows="False" HorizontalGridLinesBrush="#B9B9B9" VerticalGridLinesBrush="#B9B9B9" GridLinesVisibility="Horizontal" SelectionUnit="Cell"
BorderThickness="1" CanUserAddRows="False" CanUserSortColumns="False" KeyboardNavigation.TabNavigation="None" KeyboardNavigation.IsTabStop="False"
ItemsSource="{Binding Path=DataStore.ActiveUserProfile.PanelConfigs}">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background" Value="{x:Null}" />
<Setter Property="BorderBrush" Value="{x:Null}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="False">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{x:Null}" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock TextWrapping="Wrap" TextAlignment="Center" Text="{Binding}"></TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="controls:ControlsHelper.ContentCharacterCasing" Value="Normal"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="Height" Value="50"/>
<Setter Property="Background" Value="#FF576573"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="#FFB9B9B9"/>
<Setter Property="Padding" Value="5,0,5,0"/>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="DataGridCell_PreviewMouseLeftButtonDown"/>
<Setter Property="TextBlock.TextAlignment" Value="Center" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Panel Name" Width="280" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Name="PanelName" Width="380" BorderThickness="0" TextAlignment="Left" Text="{Binding Path=PanelName, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=LostFocus}"
SourceUpdated="GridData_SourceUpdated" IsReadOnly="{c:Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid, AncestorLevel=1}, Path='DataContext.DataStore.ActiveUserProfile.IsLocked or !DataContext.DataStore.AllowEdit'}" IsEnabled="{Binding Path=IsCustomPopout, Mode=OneWay}">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Focusable" Value="True"/>
<Setter Property="FontSize" Value="16" />
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="Margin" Value="5, 5, 5, 5"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsCustomPopout}" Value="False">
<Setter Property="FontStyle" Value="Italic" />
<Setter Property="Background" Value="#FF252525" />
<Setter Property="BorderBrush" Value="#FF252525" />
<Setter Property="IsReadOnly" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="X-Pos" Width="100">
<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'}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Y-Pos" Width="100">
<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'}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Width" Width="100">
<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'}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Height" Width="100">
<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'}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Always on Top" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="AlwaysOnTop" Width="100" Margin="40 0 0 0" IsChecked="{Binding Path=AlwaysOnTop, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"
SourceUpdated="GridData_SourceUpdated" IsEnabled="{c:Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid, AncestorLevel=1}, Path='!DataContext.DataStore.ActiveUserProfile.IsLocked and DataContext.DataStore.AllowEdit'}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Hide Titlebar" 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}"
SourceUpdated="GridData_SourceUpdated" IsEnabled="{c:Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid, AncestorLevel=1}, Path='!DataContext.DataStore.ActiveUserProfile.IsLocked and DataContext.DataStore.AllowEdit'}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</WrapPanel>
<WrapPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Margin="20,10,0,0" HorizontalAlignment="Left">
<Button Content="-10 px" ToolTip="Ctrl -" HorizontalAlignment="Center" Margin="0,0,0,0" Width="75" Command="{Binding Path=MinusTenPixelCommand}" CommandParameter="-10" IsEnabled="{c:Binding Path='!DataStore.ActiveUserProfile.IsLocked and DataStore.AllowEdit'}"/>
<Button Content="-1 px" ToolTip="Ctrl [" HorizontalAlignment="Center" Margin="20,0,0,0" Width="75" Command="{Binding Path=MinusOnePixelCommand}" CommandParameter="-1" IsEnabled="{c:Binding Path='!DataStore.ActiveUserProfile.IsLocked and DataStore.AllowEdit'}"/>
<Button Content="+1 px" ToolTip="Ctrl ]" HorizontalAlignment="Center" Margin="20,0,0,0" Width="75" Command="{Binding Path=PlusOnePixelCommand}" CommandParameter="1" IsEnabled="{c:Binding Path='!DataStore.ActiveUserProfile.IsLocked and DataStore.AllowEdit'}"/>
<Button Content="+10 px" ToolTip="Ctrl +" HorizontalAlignment="Center" Margin="20,0,0,0" Width="75" Command="{Binding Path=PlusTenPixelCommand}" CommandParameter="10" IsEnabled="{c:Binding Path='!DataStore.ActiveUserProfile.IsLocked and DataStore.AllowEdit'}"/>
<Button HorizontalAlignment="Center" Margin="390,0,0,0" Width="130" Click="LockPanels_Click" IsEnabled="{c:Binding Path='DataStore.AllowEdit'}">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Content" Value="Lock Panels"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DataStore.ActiveUserProfile.IsLocked, Mode=OneWay}" Value="True">
<Setter Property="Background" Value="Red"/>
<Setter Property="Content" Value="Unlock Panels"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</WrapPanel>
</DockPanel>
</Grid>
</UserControl>

View file

@ -0,0 +1,94 @@
using MSFSPopoutPanelManager.Model;
using MSFSPopoutPanelManager.WpfApp.ViewModel;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
namespace MSFSPopoutPanelManager.WpfApp
{
/// <summary>
/// Interaction logic for UserControlPanelConfiguration.xaml
/// </summary>
public partial class UserControlPanelConfiguration : UserControl
{
private PanelConfigurationViewModel _panelConfigurationViewModel;
public UserControlPanelConfiguration(PanelConfigurationViewModel panelConfigurationViewModel)
{
InitializeComponent();
_panelConfigurationViewModel = panelConfigurationViewModel;
this.DataContext = _panelConfigurationViewModel;
}
private void GridData_SourceUpdated(object sender, DataTransferEventArgs e)
{
var container = VisualTreeHelper.GetParent((Control) sender) as ContentPresenter;
var panelConfig = container.Content as PanelConfig;
var propertyName = (PanelConfigPropertyName) Enum.Parse(typeof(PanelConfigPropertyName), ((Control)sender).Name);
var panelConfigItem = new PanelConfigItem() { PanelIndex = panelConfig.PanelIndex, PanelConfigProperty = propertyName };
_panelConfigurationViewModel.PanelConfigUpdatedCommand.Execute(panelConfigItem);
}
private void DataGridCell_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender.GetType() == typeof(DataGridCell))
{
var cell = sender as DataGridCell;
var panelConfig = cell.DataContext as PanelConfig;
PanelConfigPropertyName selectedProperty = PanelConfigPropertyName.Invalid;
switch(cell.Column.Header)
{
case "Panel Name":
selectedProperty = PanelConfigPropertyName.PanelName;
break;
case "X-Pos":
selectedProperty = PanelConfigPropertyName.Left;
break;
case "Y-Pos":
selectedProperty = PanelConfigPropertyName.Top;
break;
case "Width":
selectedProperty = PanelConfigPropertyName.Width;
break;
case "Height":
selectedProperty = PanelConfigPropertyName.Height;
break;
case "Always on Top":
selectedProperty = PanelConfigPropertyName.AlwaysOnTop;
break;
case "Hide Titlebar":
selectedProperty = PanelConfigPropertyName.HideTitlebar;
break;
}
_panelConfigurationViewModel.SelectedPanelConfigItem = new PanelConfigItem() { PanelIndex = panelConfig.PanelIndex, PanelConfigProperty = selectedProperty };
}
}
private void LockPanels_Click(object sender, RoutedEventArgs e)
{
if(_panelConfigurationViewModel.DataStore.ActiveUserProfile.IsLocked)
{
ConfirmationDialog dialog = new ConfirmationDialog("Confirm Unlock Panels", "Are you sure you want to unlock all panels to make changes?");
dialog.Owner = Application.Current.MainWindow;
dialog.Topmost = true;
dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;
if ((bool)dialog.ShowDialog())
{
_panelConfigurationViewModel.LockPanelsCommand.Execute(null);
}
}
else
{
_panelConfigurationViewModel.LockPanelsCommand.Execute(null);
}
}
}
}

View file

@ -0,0 +1,133 @@
<UserControl x:Class="MSFSPopoutPanelManager.WpfApp.UserControlPanelSelection"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:c="clr-namespace:CalcBinding;assembly=CalcBinding"
xmlns:local="clr-namespace:MSFSPopoutPanelManager.WpfApp"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
mc:Ignorable="d"
Height="505"
Width="920"
Background="#FF323A64"
Loaded="UserControl_Loaded">
<UserControl.Resources>
<CollectionViewSource x:Key="UserProfilesViewSource" Source="{Binding Path=DataStore.UserProfiles, Mode=OneWay}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="ProfileName" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<Style x:Key="ProfileSelectedDependency" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DataStore.HasActiveUserProfileId, Mode=OneWay}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</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">
<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">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<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"
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}"/>
</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"/>
</CheckBox>
</WrapPanel>
<Label Content="2. Identify pop out panel locations in the game by clicking on them." Margin="0,15,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">
<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}"/>
</WrapPanel>
<DockPanel DockPanel.Dock="Right" Width="325" HorizontalAlignment="Center">
<Label DockPanel.Dock="Top" Content="Panel Locations" HorizontalAlignment="Center" Margin="0,10,0,0"/>
<DataGrid DockPanel.Dock="Top" HorizontalAlignment="Center" Width="290" Height="395" AutoGenerateColumns="False" CanUserResizeColumns="False" HorizontalScrollBarVisibility="Disabled"
CanUserReorderColumns="False" CanUserResizeRows="False" IsReadOnly="True" HorizontalGridLinesBrush="#B9B9B9" VerticalGridLinesBrush="#B9B9B9" GridLinesVisibility="Horizontal" BorderThickness="1"
CanUserAddRows="False" CanUserSortColumns="False" ItemsSource="{Binding Path=DataStore.ActiveProfilePanelCoordinates}">
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="TextBlock.TextAlignment" Value="Center" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Focusable" Value="False" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="IsHitTestVisible" Value="False" />
<Setter Property="Margin" Value="5, 5, 5, 0" />
<Setter Property="Height" Value="28" />
</Style>
</DataGrid.CellStyle>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="TextBlock.TextAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="controls:ControlsHelper.ContentCharacterCasing" Value="Normal"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="Height" Value="30"/>
<Setter Property="Background" Value="#FF576573"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="#FFB9B9B9"/>
<Setter Property="Padding" Value="5,0,5,0"/>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Panel" Width="97" Binding="{Binding PanelIndex}"/>
<DataGridTextColumn Header="X-Pos" Width="97" Binding="{Binding X}"/>
<DataGridTextColumn Header="Y-Pos" Width="97" Binding="{Binding Y}"/>
</DataGrid.Columns>
</DataGrid>
<CheckBox DockPanel.Dock="Bottom" Content="Show/Edit Panel Location Overlay" HorizontalAlignment="Center" Command="{Binding Path=EditPanelCoorOverlayCommand}" IsChecked="{Binding Path=IsEditingPanelCoorOverlay, Mode=TwoWay}"/>
</DockPanel>
</DockPanel>
</Grid>
</UserControl>

View file

@ -0,0 +1,115 @@
using MSFSPopoutPanelManager.WpfApp.ViewModel;
using System.Windows;
using System.Windows.Controls;
namespace MSFSPopoutPanelManager.WpfApp
{
/// <summary>
/// Interaction logic for UserControlPanelSelection.xaml
/// </summary>
public partial class UserControlPanelSelection : UserControl
{
private PanelSelectionViewModel _panelSelectionViewModel;
public UserControlPanelSelection(PanelSelectionViewModel panelSelectionViewModel)
{
InitializeComponent();
_panelSelectionViewModel = panelSelectionViewModel;
this.DataContext = panelSelectionViewModel;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
_panelSelectionViewModel.Initialize();
}
private void AddProfile_Click(object sender, RoutedEventArgs e)
{
AddProfileDialog dialog = new AddProfileDialog(_panelSelectionViewModel.DataStore.UserProfiles);
dialog.Owner = Application.Current.MainWindow;
dialog.Topmost = true;
dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;
if ((bool)dialog.ShowDialog())
{
_panelSelectionViewModel.AddProfileCommand.Execute(new AddProfileCommandParameter() { ProfileName = dialog.ProfileName, CopyProfileId = dialog.SelectedCopyProfileId });
}
}
private void DeleteProfile_Click(object sender, RoutedEventArgs e)
{
ConfirmationDialog dialog = new ConfirmationDialog("Confirm Delete", "Are you sure you want to delete the selected profile?");
dialog.Owner = Application.Current.MainWindow;
dialog.Topmost = true;
dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;
if ((bool)dialog.ShowDialog())
{
_panelSelectionViewModel.DeleteProfileCommand.Execute(null);
}
}
private void StartPanelSelection_Click(object sender, RoutedEventArgs e)
{
if (_panelSelectionViewModel.DataStore.ActiveUserProfile.PanelSourceCoordinates.Count > 0)
{
ConfirmationDialog dialog = new ConfirmationDialog("Confirm Overwrite", "WARNING! Are you sure you want to overwrite existing saved panel locations and all saved setttings for this profile?");
dialog.Owner = Application.Current.MainWindow;
dialog.Topmost = true;
dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;
if ((bool)dialog.ShowDialog())
{
_panelSelectionViewModel.StartPanelSelectionCommand.Execute(null);
}
}
else
{
_panelSelectionViewModel.StartPanelSelectionCommand.Execute(null);
}
}
private void SaveAutoPanningCamera_Click(object sender, RoutedEventArgs e)
{
if (_panelSelectionViewModel.DataStore.ActiveUserProfile.PanelSourceCoordinates.Count > 0)
{
ConfirmationDialog dialog = new ConfirmationDialog("Confirm Overwrite Auto Panning Camera", "WARNING! Are you sure you want to overwrite existing Auto Panning camera angle?");
dialog.Owner = Application.Current.MainWindow;
dialog.Topmost = true;
dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;
if ((bool)dialog.ShowDialog())
{
_panelSelectionViewModel.SaveAutoPanningCameraCommand.Execute(null);
}
}
}
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}");
dialog.Owner = Application.Current.MainWindow;
dialog.Topmost = true;
dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;
if ((bool)dialog.ShowDialog())
{
_panelSelectionViewModel.AddProfileBindingCommand.Execute(null);
}
}
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}");
dialog.Owner = Application.Current.MainWindow;
dialog.Topmost = true;
dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;
if ((bool)dialog.ShowDialog())
{
_panelSelectionViewModel.DeleteProfileBindingCommand.Execute(null);
}
}
}
}

View file

@ -0,0 +1,278 @@
using MSFSPopoutPanelManager.FsConnector;
using MSFSPopoutPanelManager.Model;
using MSFSPopoutPanelManager.Provider;
using MSFSPopoutPanelManager.Shared;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Windows;
namespace MSFSPopoutPanelManager.WpfApp.ViewModel
{
public class ApplicationViewModel : INotifyPropertyChanged
{
private UserProfileManager _userProfileManager;
private PanelPopOutManager _panelPopoutManager;
private SimConnectManager _simConnectManager;
// Using PropertyChanged.Fody
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler<EventArgs<StatusMessage>> ShowContextMenuBalloonTip;
public PanelSelectionViewModel PanelSelectionViewModel { get; private set; }
public PanelConfigurationViewModel PanelConfigurationViewModel { get; private set; }
public DataStore DataStore { get; set; }
public int ActiveUserProfileId { get; set; }
public string ApplicationVersion { get; private set; }
public WindowState InitialWindowState { get; private set; }
public StatusMessage StatusMessage { get; set; }
public bool IsShownPanelConfigurationScreen { get; set; }
public bool IsShownPanelSelectionScreen { get; set; }
public bool IsMinimizedAllPanels { get; set; }
public DelegateCommand RestartCommand => new DelegateCommand(OnRestart, CanExecute);
public DelegateCommand MinimizeAllPanelsCommand => new DelegateCommand(OnMinimizeAllPanels, CanExecute);
public DelegateCommand ExitCommand => new DelegateCommand(OnExit, CanExecute);
public DelegateCommand UserGuideCommand => new DelegateCommand((o) => { DiagnosticManager.OpenOnlineUserGuide(); }, CanExecute);
public DelegateCommand DownloadLatestReleaseCommand => new DelegateCommand((o) => { DiagnosticManager.OpenOnlineLatestDownload(); }, CanExecute);
public DelegateCommand UserProfileSelectCommand => new DelegateCommand(OnUserProfileSelected, CanExecute);
public DelegateCommand ShowPanelCoorOverlayCommand => new DelegateCommand(OnShowPanelCoorOverlay, CanExecute);
public DelegateCommand StartPopOutCommand => new DelegateCommand(OnStartPopOut, CanExecute);
public ApplicationViewModel()
{
Logger.OnStatusLogged += (sender, e) => { OnStatusMessageLogged(e); };
DataStore = new DataStore();
_userProfileManager = new UserProfileManager();
_simConnectManager = new SimConnectManager();
_simConnectManager.OnSimConnectDataRefreshed += (sender, e) =>
{
DataStore.CurrentMsfsPlaneTitle = e.Value.Title;
DataStore.ElectricalMasterBatteryStatus = e.Value.ElectricalMasterBattery;
};
_simConnectManager.OnConnected += (sender, e) => { DataStore.IsSimulatorStarted = true; };
_simConnectManager.OnDisconnected += (sender, e) => { DataStore.IsSimulatorStarted = false; };
_panelPopoutManager = new PanelPopOutManager(_userProfileManager, _simConnectManager);
_panelPopoutManager.OnPopOutCompleted += (sender, e) =>
{
Application.Current.Dispatcher.Invoke(() =>
{
if (e.Value)
{
var messageDialog = new OnScreenMessageDialog("Panels have been popped out succesfully.", MessageIcon.Success);
messageDialog.ShowDialog();
}
else
{
var messageDialog = new OnScreenMessageDialog("Unable to pop out panels.\nPlease see error message in the app.", MessageIcon.Failed);
messageDialog.ShowDialog();
}
});
};
PanelSelectionViewModel = new PanelSelectionViewModel(DataStore, _userProfileManager, _panelPopoutManager, _simConnectManager);
PanelSelectionViewModel.OnPopOutCompleted += (sender, e) => { ShowPanelSelection(false); PanelConfigurationViewModel.Initialize(); };
PanelConfigurationViewModel = new PanelConfigurationViewModel(DataStore, _userProfileManager);
InputHookManager.OnStartPopout += (source, e) => { OnStartPopOut(null); };
InputHookManager.StartHook();
}
public void Initialize()
{
var appSetting = new AppSetting();
appSetting.Load();
DataStore.AppSetting = appSetting;
DataStore.AppSetting.AlwaysOnTopChanged += OnAlwaysOnTopChanged;
DataStore.AppSetting.AutoPopOutPanelsChanged += (sender, e) =>
{
if (e.Value)
ActivateAutoPanelPopOut();
else
DeativateAutoPanelPopOut();
};
// Set application version
ApplicationVersion = DiagnosticManager.GetApplicationVersion();
// Set window state
if (DataStore.AppSetting.StartMinimized)
InitialWindowState = WindowState.Minimized;
// Set Always on Top
if (DataStore.AppSetting.AlwaysOnTop)
OnAlwaysOnTopChanged(this, new EventArgs<bool>(DataStore.AppSetting.AlwaysOnTop));
// Activate auto pop out panels
if (DataStore.AppSetting.AutoPopOutPanels)
ActivateAutoPanelPopOut();
ShowPanelSelection(true);
IsMinimizedAllPanels = false;
}
private void OnRestart(object commandParameter)
{
// Un-minimize all panels if applicable
if(IsMinimizedAllPanels)
{
DataStore.AllowEdit = true;
IsMinimizedAllPanels = false;
WindowManager.MinimizeAllPopoutPanels(false);
}
// Unhook all win events
PanelConfigurationViewModel.UnhookWinEvents();
// Clear logger
Logger.ClearStatus();
// Try to close all Cutome Panel window
DataStore.ActiveUserProfile.PanelConfigs.ToList().FindAll(p => p.PanelType == PanelType.CustomPopout).ForEach(panel => WindowManager.CloseWindow(panel.PanelHandle));
// Clear all panel windows handle for active profile
DataStore.ActiveUserProfile.PanelConfigs.ToList().ForEach(p => p.PanelHandle = IntPtr.Zero);
ShowPanelSelection(true);
}
private void OnExit(object commandParameter)
{
InputHookManager.EndHook();
_simConnectManager.Stop();
Application.Current.Shutdown();
}
private void OnMinimizeAllPanels(object commandParameter)
{
IsMinimizedAllPanels = !IsMinimizedAllPanels;
if (IsMinimizedAllPanels)
{
DataStore.AllowEdit = false;
WindowManager.MinimizeAllPopoutPanels(true);
Logger.LogStatus("All pop out panels have been minimized. Panel configuration has been disabled.", StatusMessageType.Info);
}
else
{
DataStore.AllowEdit = true;
WindowManager.MinimizeAllPopoutPanels(false);
Logger.ClearStatus();
}
}
private void OnUserProfileSelected(object commandParameter)
{
var profileId = Convert.ToInt32(commandParameter);
if (profileId != DataStore.ActiveUserProfileId)
DataStore.ActiveUserProfileId = profileId;
}
private void OnShowPanelCoorOverlay(object commandParameter)
{
PanelSelectionViewModel.IsEditingPanelCoorOverlay = !PanelSelectionViewModel.IsEditingPanelCoorOverlay;
PanelSelectionViewModel.EditPanelCoorOverlayCommand.Execute(null);
}
private void OnStartPopOut(object commandParameter)
{
ShowPanelSelection(true);
PanelSelectionViewModel.StartPopOutCommand.Execute(null);
}
private void OnAlwaysOnTopChanged(object sender, EventArgs<bool> e)
{
WindowManager.ApplyAlwaysOnTop(DataStore.ApplicationHandle, e.Value);
}
private void ShowPanelSelection(bool show)
{
IsShownPanelSelectionScreen = show;
IsShownPanelConfigurationScreen = !show;
}
private void OnStatusMessageLogged(EventArgs<StatusMessage> e)
{
StatusMessage = e.Value;
ShowContextMenuBalloonTip?.Invoke(this, new EventArgs<StatusMessage>(e.Value));
}
private bool CanExecute(object commandParameter)
{
return true;
}
private void ActivateAutoPanelPopOut()
{
_simConnectManager.OnFlightStarted += HandleOnFlightStarted;
_simConnectManager.OnFlightStopped += HandleOnFlightStopped;
}
private void DeativateAutoPanelPopOut()
{
DataStore.IsEnteredFlight = false;
_simConnectManager.OnFlightStarted -= HandleOnFlightStarted;
_simConnectManager.OnFlightStopped -= HandleOnFlightStopped;
}
private void HandleOnFlightStarted(object sender, EventArgs e)
{
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)
{
Thread.Sleep(DataStore.AppSetting.AutoPopOutPanelsWaitDelay.ReadyToFlyButton * 1000); // Wait for the ready to fly button
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();
// 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();
// Turn off power if needed after pop out
_simConnectManager.TurnOffpower();
});
}
}
private void HandleOnFlightStopped(object sender, EventArgs e)
{
DataStore.IsEnteredFlight = false;
}
}
}

View file

@ -0,0 +1,128 @@
using MSFSPopoutPanelManager.Model;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
namespace MSFSPopoutPanelManager.WpfApp.ViewModel
{
public class DataStore : INotifyPropertyChanged
{
private int _activeProfileId;
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler OnActiveUserProfileChanged;
public event EventHandler OnAllowEditChanged;
public DataStore()
{
_activeProfileId = -1;
_allowEdit = true;
IsFlightActive = true; // ToDo: temporary for testing
}
public AppSetting AppSetting { get; set; }
public ObservableCollection<UserProfile> UserProfiles { get; set; }
public ObservableCollection<PanelSourceCoordinate> ActiveProfilePanelCoordinates
{
get
{
if (ActiveUserProfile == null)
return new ObservableCollection<PanelSourceCoordinate>();
else
return ActiveUserProfile.PanelSourceCoordinates;
}
}
public ObservableCollection<PanelConfig> ActiveProfilePanelConfigs
{
get
{
if (ActiveUserProfile == null)
return new ObservableCollection<PanelConfig>();
else
return ActiveUserProfile.PanelConfigs;
}
}
public UserProfile ActiveUserProfile
{
get
{
if (ActiveUserProfileId == -1)
return null;
else
return UserProfiles.ToList().Find(x => x.ProfileId == ActiveUserProfileId);
}
}
private bool _allowEdit;
public bool AllowEdit
{
get
{
return _allowEdit;
}
set
{
if(value != _allowEdit)
{
_allowEdit = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("AllowEdit"));
OnAllowEditChanged?.Invoke(this, null);
}
}
}
}
public IntPtr ApplicationHandle { get; set; }
public Window ApplicationWindow { get; set; }
public int ActiveUserProfileId
{
get
{
return _activeProfileId;
}
set
{
_activeProfileId = value;
// Set active profile flag
UserProfiles.ToList().ForEach(p => p.IsActive = false);
var profile = UserProfiles.ToList().Find(p => p.ProfileId == value);
if(profile != null)
profile.IsActive = true;
OnActiveUserProfileChanged?.Invoke(this, null);
}
}
public bool HasActiveUserProfileId
{
get { return ActiveUserProfileId != -1; }
}
public string CurrentMsfsPlaneTitle { get; set; }
public bool HasCurrentMsfsPlaneTitle
{
get { return !String.IsNullOrEmpty(CurrentMsfsPlaneTitle); }
}
public bool ElectricalMasterBatteryStatus { get; set; }
public bool IsSimulatorStarted { get; set; }
public bool IsEnteredFlight { get; set; }
public bool IsFlightActive { get; set; }
}
}

View file

@ -0,0 +1,25 @@
using System;
using System.Windows.Input;
namespace MSFSPopoutPanelManager.WpfApp.ViewModel
{
public class DelegateCommand : ICommand
{
private readonly Action<object> _executeAction;
private readonly Func<object, bool> _canExecuteAction;
public DelegateCommand(Action<object> executeAction, Func<object, bool> canExecuteAction)
{
_executeAction = executeAction;
_canExecuteAction = canExecuteAction;
}
public void Execute(object parameter) => _executeAction(parameter);
public bool CanExecute(object parameter) => _canExecuteAction?.Invoke(parameter) ?? true;
public event EventHandler CanExecuteChanged;
public void InvokeCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}

View file

@ -0,0 +1,75 @@
using MSFSPopoutPanelManager.Model;
using MSFSPopoutPanelManager.Provider;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
namespace MSFSPopoutPanelManager.WpfApp.ViewModel
{
public class PanelConfigurationViewModel
{
private UserProfileManager _userProfileManager;
private PanelConfigurationManager _panelConfigurationManager;
// Using PropertyChanged.Fody
public event PropertyChangedEventHandler PropertyChanged;
public DataStore DataStore { get; set; }
public PanelConfigItem SelectedPanelConfigItem { get; set; }
public DelegateCommand LockPanelsCommand => new DelegateCommand(OnLockPanelsChanged, CanExecute);
public DelegateCommand PanelConfigUpdatedCommand => new DelegateCommand(OnPanelConfigUpdated, CanExecute);
public DelegateCommand MinusTenPixelCommand => new DelegateCommand(OnDataItemIncDec, CanExecute);
public DelegateCommand MinusOnePixelCommand => new DelegateCommand(OnDataItemIncDec, CanExecute);
public DelegateCommand PlusOnePixelCommand => new DelegateCommand(OnDataItemIncDec, CanExecute);
public DelegateCommand PlusTenPixelCommand => new DelegateCommand(OnDataItemIncDec, CanExecute);
public PanelConfigurationViewModel(DataStore dataStore, UserProfileManager userProfileManager)
{
DataStore = dataStore;
_userProfileManager = userProfileManager;
_panelConfigurationManager = new PanelConfigurationManager(_userProfileManager);
}
public void Initialize()
{
_userProfileManager.UserProfiles = DataStore.UserProfiles;
_panelConfigurationManager.UserProfile = DataStore.ActiveUserProfile;
_panelConfigurationManager.AllowEdit = DataStore.AllowEdit;
_panelConfigurationManager.HookWinEvent();
DataStore.OnAllowEditChanged += (sender, e) =>
{
_panelConfigurationManager.AllowEdit = DataStore.AllowEdit;
};
}
public void UnhookWinEvents()
{
_panelConfigurationManager.UnhookWinEvent();
}
private void OnLockPanelsChanged(object commandParamenter)
{
_panelConfigurationManager.LockPanelsUpdated();
}
private void OnPanelConfigUpdated(object panelConfigItem)
{
if(DataStore.AllowEdit)
_panelConfigurationManager.PanelConfigPropertyUpdated(panelConfigItem as PanelConfigItem);
}
private void OnDataItemIncDec(object commandParameter)
{
if (DataStore.AllowEdit)
_panelConfigurationManager.PanelConfigIncreaseDecrease(SelectedPanelConfigItem, Convert.ToInt32(commandParameter));
}
private bool CanExecute(object commandParameter)
{
return true;
}
}
}

View file

@ -0,0 +1,275 @@
using MSFSPopoutPanelManager.Model;
using MSFSPopoutPanelManager.Provider;
using MSFSPopoutPanelManager.Shared;
using System;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Interop;
namespace MSFSPopoutPanelManager.WpfApp.ViewModel
{
public class PanelSelectionViewModel : INotifyPropertyChanged
{
private UserProfileManager _userProfileManager;
private PanelSelectionManager _panelSelectionManager;
private PanelPopOutManager _panelPopoutManager;
private SimConnectManager _simConnectManager;
private bool _minimizeForPopOut;
// Using PropertyChanged.Fody
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler OnPopOutStarted;
public event EventHandler OnPopOutCompleted;
public event EventHandler OnShowPrePopOutMessage;
public DataStore DataStore { get; set; }
public bool IsEditingPanelCoorOverlay { get; set; }
public DelegateCommand AddProfileCommand => new DelegateCommand(OnAddProfile, CanExecute);
public DelegateCommand DeleteProfileCommand => new DelegateCommand(OnDeleteProfile, CanExecute);
public DelegateCommand SetDefaultProfileCommand => new DelegateCommand(OnSetDefaultProfile, CanExecute);
public DelegateCommand AddProfileBindingCommand => new DelegateCommand(OnAddProfileBinding, CanExecute);
public DelegateCommand DeleteProfileBindingCommand => new DelegateCommand(OnDeleteProfileBinding, CanExecute);
public DelegateCommand SetPowerOnRequiredCommand => new DelegateCommand((e) => _userProfileManager.WriteUserProfiles(), CanExecute);
public DelegateCommand StartPanelSelectionCommand => new DelegateCommand(OnStartPanelSelection, CanExecute);
public DelegateCommand StartPopOutCommand => new DelegateCommand(OnStartPopOut, CanExecute);
public DelegateCommand SaveAutoPanningCameraCommand => new DelegateCommand(OnSaveAutoPanningCamera, CanExecute);
public DelegateCommand EditPanelCoorOverlayCommand => new DelegateCommand(OnEditPanelCoorOverlay, CanExecute);
public PanelSelectionViewModel(DataStore dataStore, UserProfileManager userProfileManager, PanelPopOutManager panelPopoutManager, SimConnectManager simConnectManager)
{
DataStore = dataStore;
DataStore.OnActiveUserProfileChanged += (sender, e) => { IsEditingPanelCoorOverlay = false; RemoveAllPanelCoorOverlay(); };
_userProfileManager = userProfileManager;
_simConnectManager = simConnectManager;
_panelSelectionManager = new PanelSelectionManager(userProfileManager);
_panelSelectionManager.OnPanelLocationAdded += (sender, e) => { AddPanelCoorOverlay(e.Value); };
_panelSelectionManager.OnPanelLocationRemoved += (sender, e) => { RemoveLastAddedPanelCoorOverlay(); };
_panelSelectionManager.OnAllPanelLocationsRemoved += (sender, e) => { RemoveAllPanelCoorOverlay(); };
_panelSelectionManager.OnPanelSelectionCompleted += HandlePanelSelectionCompleted;
_panelPopoutManager = panelPopoutManager;
_panelPopoutManager.OnPopOutStarted += HandleOnPopOutStarted;
_panelPopoutManager.OnPopOutCompleted += HandleOnPopOutCompleted;
}
public void Initialize()
{
_userProfileManager.ReadUserProfiles();
DataStore.UserProfiles = _userProfileManager.UserProfiles;
var defaultProfile = _userProfileManager.GetDefaultProfile();
DataStore.ActiveUserProfileId = defaultProfile == null ? -1 : defaultProfile.ProfileId;
IsEditingPanelCoorOverlay = false;
InputHookManager.SubscribeToStartPopOutEvent = true;
}
private void OnAddProfile(object commandParameter)
{
var param = commandParameter as AddProfileCommandParameter;
if (param.CopyProfileId == -1)
DataStore.ActiveUserProfileId = _userProfileManager.AddUserProfile(param.ProfileName);
else
DataStore.ActiveUserProfileId = _userProfileManager.AddUserProfileByCopyingProfile(param.ProfileName, param.CopyProfileId);
}
private void OnDeleteProfile(object commandParameter)
{
if (_userProfileManager.DeleteUserProfile(DataStore.ActiveUserProfileId))
DataStore.ActiveUserProfileId = -1;
}
private void OnSetDefaultProfile(object commandParameter)
{
_userProfileManager.SetDefaultUserProfile(DataStore.ActiveUserProfileId);
}
private void OnAddProfileBinding(object commandParameter)
{
_userProfileManager.AddProfileBinding(DataStore.CurrentMsfsPlaneTitle, DataStore.ActiveUserProfileId);
}
private void OnDeleteProfileBinding(object commandParameter)
{
_userProfileManager.DeleteProfileBinding(DataStore.ActiveUserProfileId);
}
private void OnStartPanelSelection(object commandParameter)
{
WindowManager.MinimizeWindow(DataStore.ApplicationHandle); // Window hide doesn't work when try to reshow window after selection completes. So need to use minimize.
_panelSelectionManager.UserProfile = DataStore.ActiveUserProfile;
_panelSelectionManager.AppSetting = DataStore.AppSetting;
_panelSelectionManager.Start();
}
private void OnStartPopOut(object commandParameter)
{
Thread.Sleep(500); // allow time for the mouse to be stopped moving by the user
var simulatorProcess = DiagnosticManager.GetSimulatorProcess();
if (!(DataStore.IsSimulatorStarted && DataStore.IsFlightActive))
{
Logger.LogStatus("MSFS/SimConnect has not been started. Please try again at a later time.", StatusMessageType.Error);
return;
}
if (DataStore.ActiveUserProfile.PanelSourceCoordinates.Count > 0)
{
Logger.LogStatus("Panels pop out in progress.....", StatusMessageType.Info);
var messageDialog = new OnScreenMessageDialog($"Panels pop out in progress for profile:\n{DataStore.ActiveUserProfile.ProfileName}", DataStore.AppSetting.AutoPopOutPanelsWaitDelay.InitialCockpitView);
messageDialog.ShowDialog();
// Turn on power if required to pop out panels
_simConnectManager.TurnOnPower(DataStore.ActiveUserProfile.PowerOnRequiredForColdStart);
Thread.Sleep(DataStore.AppSetting.AutoPopOutPanelsWaitDelay.InstrumentationPowerOn * 1000); // Wait for battery to be turned on
_panelPopoutManager.UserProfile = DataStore.ActiveUserProfile;
_panelPopoutManager.AppSetting = DataStore.AppSetting;
_panelPopoutManager.StartPopout();
// Turn off power if needed after pop out
_simConnectManager.TurnOffpower();
}
}
private void OnSaveAutoPanningCamera(object commandParameter)
{
var simualatorProcess = DiagnosticManager.GetSimulatorProcess();
if (simualatorProcess != null && DataStore.IsFlightActive)
{
InputEmulationManager.SaveCustomViewZero(simualatorProcess.Handle);
Logger.LogStatus("Auto Panning Camera has been saved succesfully.", StatusMessageType.Info);
}
}
private void OnEditPanelCoorOverlay(object commandParameter)
{
if (IsEditingPanelCoorOverlay)
{
RemoveAllPanelCoorOverlay();
DataStore.ActiveUserProfile.PanelSourceCoordinates.ToList().ForEach(c => AddPanelCoorOverlay(c));
_panelSelectionManager.UserProfile = DataStore.ActiveUserProfile;
_panelSelectionManager.AppSetting = DataStore.AppSetting;
_panelSelectionManager.StartEditPanelLocations();
}
else
{
_panelSelectionManager.EndEditPanelLocations();
RemoveAllPanelCoorOverlay();
}
}
private void AddPanelCoorOverlay(PanelSourceCoordinate panelSourceCoordinate)
{
PanelCoorOverlay overlay = new PanelCoorOverlay(panelSourceCoordinate.PanelIndex);
overlay.IsEditingPanelLocation = IsEditingPanelCoorOverlay;
overlay.Loaded += (sender, e) =>
{
var overlay = (Window)sender;
var handle = new WindowInteropHelper(Window.GetWindow(overlay)).Handle;
panelSourceCoordinate.PanelHandle = handle;
PInvoke.MoveWindow(handle, (int)overlay.Left, (int)overlay.Top, (int)overlay.Width, (int)overlay.Height, false);
};
overlay.WindowStartupLocation = System.Windows.WindowStartupLocation.Manual;
overlay.Left = panelSourceCoordinate.X - overlay.Width / 2;
overlay.Top = panelSourceCoordinate.Y - overlay.Height / 2;
overlay.ShowInTaskbar = false;
overlay.Show();
}
private void RemoveLastAddedPanelCoorOverlay()
{
RemovePanelCoorOverlay(false);
}
private void RemoveAllPanelCoorOverlay()
{
RemovePanelCoorOverlay(true);
}
private void RemovePanelCoorOverlay(bool removeAll)
{
for (int i = Application.Current.Windows.Count - 1; i >= 1; i--)
{
if (Application.Current.Windows[i].GetType() == typeof(PanelCoorOverlay))
{
Application.Current.Windows[i].Close();
if (!removeAll)
break;
}
}
}
private void HandleOnPopOutStarted(object sender, EventArgs e)
{
// Hide panel coordinate overlays
IsEditingPanelCoorOverlay = false;
OnEditPanelCoorOverlay(null);
// Close all pop out panels
WindowManager.CloseAllCustomPopoutPanels();
// Temporary minimize the app for pop out process
_minimizeForPopOut = DataStore.ApplicationWindow.WindowState != WindowState.Minimized;
if (_minimizeForPopOut)
WindowManager.MinimizeWindow(DataStore.ApplicationHandle);
OnPopOutStarted?.Invoke(this, null);
}
private void HandleOnPopOutCompleted(object sender, EventArgs<bool> hasResult)
{
// Restore window state
if (_minimizeForPopOut)
{
WindowManager.BringWindowToForeground(DataStore.ApplicationHandle);
DataStore.ApplicationWindow.Show();
}
if (hasResult.Value)
{
OnPopOutCompleted?.Invoke(this, null);
}
}
private void HandlePanelSelectionCompleted(object sender, EventArgs e)
{
WindowManager.BringWindowToForeground(DataStore.ApplicationHandle);
DataStore.ApplicationWindow.Show();
IsEditingPanelCoorOverlay = true;
OnEditPanelCoorOverlay(null);
if (DataStore.ActiveUserProfile.PanelSourceCoordinates.Count > 0)
Logger.LogStatus("Panels selection is completed. Please click 'Start Pop Out' to start popping out these panels.", StatusMessageType.Info);
else
Logger.LogStatus("Panels selection is completed. No panel has been selected.", StatusMessageType.Info);
}
private bool CanExecute(object commandParameter)
{
return true;
}
}
public class AddProfileCommandParameter
{
public string ProfileName { get; set; }
public int CopyProfileId { get; set; }
}
}

Some files were not shown because too many files have changed in this diff Show more