1
0
Fork 0
mirror of https://github.com/hawkeye-stan/msfs-popout-panel-manager.git synced 2024-11-25 15:20:10 +00:00

Version 3.0

This commit is contained in:
hawkeye 2021-12-14 00:40:07 -05:00
parent 9aa8f1d2db
commit ee61fdbe88
98 changed files with 2714 additions and 3386 deletions

1
.gitignore vendored
View file

@ -7,6 +7,7 @@
*.user
*.userosscache
*.sln.docstates
*.backup
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

View file

@ -5,7 +5,7 @@
<TargetFramework>net5.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<Platforms>x64;AnyCPU</Platforms>
<Version>2.2.0.0</Version>
<Version>3.0</Version>
<AssemblyName>MSFSPopoutPanelManager</AssemblyName>
<RootNamespace>MSFSPopoutPanelManager</RootNamespace>
<ApplicationIcon>WindowManager.ico</ApplicationIcon>
@ -13,67 +13,46 @@
<Product>MSFS 2020 Popout Panel Manager</Product>
<PackageId>MSFS 2020 Popout Panel Manager</PackageId>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyVersion>2.2.0.0</AssemblyVersion>
<FileVersion>2.2.0.0</FileVersion>
<DebugType>None</DebugType>
<DebugSymbols>false</DebugSymbols>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.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>
<Compile Remove="ImageLibrary\**" />
<EmbeddedResource Remove="ImageLibrary\**" />
<None Remove="ImageLibrary\**" />
<None Remove="log4net.config" />
</ItemGroup>
<ItemGroup>
<Reference Include="UIAutomationClient">
<HintPath>..\..\..\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationClient.dll</HintPath>
</Reference>
<Reference Include="UIAutomationTypes">
<HintPath>..\..\..\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationTypes.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<None Remove="C:\Users\hawkeye\.nuget\packages\tesseract\4.1.1\build\\..\x86\leptonica-1.80.0.dll" />
</ItemGroup>
<ItemGroup>
<None Remove="C:\Users\hawkeye\.nuget\packages\tesseract\4.1.1\build\\..\x86\tesseract41.dll" />
<None Remove="Config\AnalysisData\analysisconfig.json" />
<None Remove="Config\planeprofile.json" />
<None Remove="LICENSE" />
<None Remove="README.md" />
<None Remove="VERSION.md" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AForge" Version="2.2.5" />
<PackageReference Include="AForge.Imaging" Version="2.2.5" />
<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="System.Text.Encoding.CodePages" Version="6.0.0" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
</ItemGroup>
<ItemGroup>
<Content Remove="C:\Users\hawkeye\.nuget\packages\tesseract.data.english\4.0.0\build\tessdata\eng.traineddata" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="ChildWindow.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Config\planeprofile.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Config\AnalysisData\analysisconfig.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="LICENSE">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="log4net.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="README.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
@ -82,181 +61,8 @@
</Content>
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="WindowManager.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="FileManager.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="UserData.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="README.md" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="AnalysisEngine.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="LICENSE" />
</ItemGroup>
<ItemGroup>
<None Update="Config\AnalysisData\cj4\cj4_mfd.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\cj4\cj4_multipurpose_control.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\cj4\cj4_pfd.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\cj4\cj4_standby_altitude_module.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\g1000\g1000_mfd.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\g1000\g1000_mfd2.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\g1000\g1000_pfd.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\g3000-kingair\g3000kingair_mfd.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\g3000-kingair\g3000kingair_mfd2.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\g3000-kingair\g3000kingair_pfd.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\g3000-kingair\g3000kingair_pfd2.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\g3000-kingair\g3000kingair_standby_altitude_module.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\g3000\g3000_mfd.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\g3000\g3000_mfd2.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\g3000\g3000_multipurpose_control.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\g3000\g3000_pfd.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\g3000\g3000_standby_altitude_module_1.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\g3000\g3000_standby_altitude_module_2.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\pms50-gtn750\pms50_gtn750_mfd.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\pms50-gtn750\pms50_gtn750_mfd2.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\pms50-gtn750\pms50_gtn750_pfd.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="config\matching\A320NX\a320nx_enginedisplay.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\a32nx\a32nx_engine_display.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="config\matching\A320NX\a320nx_messagepanel.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\a32nx\a32nx_message_panel.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="config\matching\A320NX\a320nx_multipurposecontrol.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\a32nx\a32nx_multipurpose_control.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="config\matching\A320NX\a320nx_navdisplay.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\a32nx\a32nx_nav_display.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\a32nx\a32nx_pfd.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="config\matching\A320NX\a320nx_standbyaltitudeindication.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="config\matching\A320NX\a320nx_standby_altitude_indication.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\a32nx\a32nx_standby_altitude_indicator.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="config\matching\A320NX\a320nx_systemdisplay.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\a32nx\a32nx_system_display.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\g1000nxi\g1000nxi_mfd.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\g1000nxi\g1000nxi_mfd2.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\AnalysisData\g1000nxi\g1000nxi_pfd.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\preprocessingdata\separation_button.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\PreprocessingData\separation_button_qhd.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\preprocessingdata\test.svg">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="config\preprocess\blockmatch_wqhd1.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="config\preprocess\G1000MFD.PNG">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="config\preprocess\G1000PFD.PNG">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="config\preprocess\G1000\g1000_mfd_wqhd.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="config\preprocess\g1000_mfd_wqhd.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="config\preprocess\g1000_pfd_wqhd..PNG">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="config\preprocess\G1000\g1000_pfd_wqhd.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="config\preprocess\g1000_pfd_wqhd.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="config\preprocessingdata\separation_button_wqhd.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="images\transparent.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
@ -264,208 +70,9 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="Vesion.md" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="PInvoke.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="Logger.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="Config\PreprocessingData\separation_button_wqhd.png" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="ImageOperation.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="Config\PreprocessingData\g1000_pfd_wqhd.png" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="Config\PreprocessingData\g1000_mfd_wqhd.png" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="PopoutCoorOverlay.Designer.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="PopoutCoorOverlay.resx" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="StartupForm.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="StartupForm.Designer.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="StartupForm.resx" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="UserControlPopOutStep.Designer.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="UserControlPopOutStep.resx" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="UserControlSeparateStep.Designer.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="UserControlSeparateStep.resx" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="UserControlStepAnalyze.Designer.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="UserControlStepAnalyze.resx" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="UserControlStepApplySettings.Designer.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="UserControlStepApplySettings.resx" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="PanelManager.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="UserControlCommon.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="UserControlCommon.resx" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="UserControlApplySettingsStep.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="UserControlPopOutStep.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="PopoutCoorOverlayForm.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="Properties\Resources.resx" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
<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>
<NativeLibs Remove="IdentifyPanelLocationModule.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="WindowManagementModule.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="PanelAnalysisModule.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="WindowProcess.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="Config\AnalysisData\analysisconfig.json" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="images\seperation_analysis.png" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="images\screenshot1.png" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="Config\planeprofile.json" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="Config\AnalysisData\g1000nxi\g1000nxi_mfd.png" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="Config\AnalysisData\g1000nxi\g1000nxi_pfd.png" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="Config\AnalysisData\g1000nxi\g1000nxi_mfd2.png" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="Modules\Enums.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="DataFileObjects.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="Modules\PlaneProfile.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="Modules\Structs.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="Modules\ImageAnalysis.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="UI\UserControlIdentifyPopOut.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="UI\UserControlApplySettings.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="Modules\PanelLocationSelectionModule.cs" />
</ItemGroup>
<ItemGroup>
<NativeLibs Remove="Modules\WindowManager.cs" />
</ItemGroup>
</Project>

View file

@ -1,22 +0,0 @@
using System;
namespace MSFSPopoutPanelManager
{
public class ChildWindow
{
public ChildWindow()
{
WindowType = WindowType.Undetermined;
}
public IntPtr Handle { get; set; }
public int PanelId { get; set; }
public string Title { get; set; }
public string ClassName { get; set; }
public WindowType WindowType { get; set; }
}
}

View file

@ -1,18 +0,0 @@
namespace MSFSPopoutPanelManager
{
public enum FlightSimResolution
{
HD,
QHD,
WQHD,
UHD
}
public enum WindowType
{
FlightSimMainWindow,
BuiltIn_Popout,
Custom_Popout,
Undetermined
}
}

View file

@ -1,301 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager
{
public class FileManager
{
private static string StartupPath;
static FileManager()
{
FileManager.StartupPath = Application.StartupPath;
}
public static List<PlaneProfile> ReadAllPlaneProfileData()
{
List<PlaneProfile> allProfiles = new List<PlaneProfile>();
allProfiles.AddRange(FileManager.ReadBuiltInPlaneProfileData());
allProfiles.AddRange(FileManager.ReadCustomPlaneProfileData());
return allProfiles;
}
public static List<PlaneProfile> ReadBuiltInPlaneProfileData()
{
try
{
using (StreamReader reader = new StreamReader(GetFilePathByType(FilePathType.ProfileData) + "planeprofile.json"))
{
return JsonConvert.DeserializeObject<List<PlaneProfile>>(reader.ReadToEnd());
}
}
catch
{
return new List<PlaneProfile>();
}
}
public static List<PlaneProfile> ReadCustomPlaneProfileData()
{
try
{
using (StreamReader reader = new StreamReader(GetFilePathByType(FilePathType.ProfileData) + "customplaneprofile.json"))
{
return JsonConvert.DeserializeObject<List<PlaneProfile>>(reader.ReadToEnd());
}
}
catch
{
return new List<PlaneProfile>();
}
}
public static bool WriteBuiltInPlaneProfileData(List<PlaneProfile> profiles)
{
try
{
using (StreamWriter file = File.CreateText(GetFilePathByType(FilePathType.ProfileData) + "planeprofile.json"))
{
JsonSerializer serializer = new JsonSerializer();
serializer.Serialize(file, profiles);
}
return true;
}
catch
{
return false;
}
}
public static bool WriteCustomPlaneProfileData(List<PlaneProfile> profiles)
{
try
{
using (StreamWriter file = File.CreateText(GetFilePathByType(FilePathType.ProfileData) + "customplaneprofile.json"))
{
JsonSerializer serializer = new JsonSerializer();
serializer.Serialize(file, profiles);
}
return true;
}
catch
{
return false;
}
}
public static UserData ReadUserData()
{
try
{
using (StreamReader reader = new StreamReader(GetFilePathByType(FilePathType.UserData) + "userdata.json"))
{
return JsonConvert.DeserializeObject<UserData>(reader.ReadToEnd());
}
}
catch
{
var userData = new UserData();
return userData;
}
}
public static UserPlaneProfile GetUserPlaneProfile(int profileId)
{
var userData = ReadUserData();
var userPlaneProfile = userData.Profiles.Find(x => x.ProfileId == profileId);
if (userPlaneProfile == null)
{
userPlaneProfile = new UserPlaneProfile();
userPlaneProfile.ProfileId = profileId;
userPlaneProfile.PanelSettings.PanelDestinationList = new List<PanelDestinationInfo>();
userPlaneProfile.PanelSourceCoordinates = new List<PanelSourceCoordinate>();
}
return userPlaneProfile;
}
public static void WriteUserData(UserData userData)
{
using (StreamWriter file = File.CreateText(GetFilePathByType(FilePathType.UserData) + "userdata.json"))
{
JsonSerializer serializer = new JsonSerializer();
serializer.Serialize(file, userData);
}
}
public static List<AnalysisData> ReadAllAnalysisTemplateData()
{
List<AnalysisData> allTemplates = new List<AnalysisData>();
allTemplates.AddRange(FileManager.ReadBuiltInAnalysisTemplateData());
allTemplates.AddRange(FileManager.ReadCustomAnalysisTemplateData());
return allTemplates;
}
public static List<AnalysisData> ReadBuiltInAnalysisTemplateData()
{
try
{
using (StreamReader reader = new StreamReader(GetFilePathByType(FilePathType.AnalysisData) + "analysisconfig.json"))
{
try
{
return JsonConvert.DeserializeObject<List<AnalysisData>>(reader.ReadToEnd());
}
catch(Exception ex)
{
throw new Exception("The file analysisconfig.json is invalid.");
}
}
}
catch
{
return new List<AnalysisData>();
}
}
public static List<AnalysisData> ReadCustomAnalysisTemplateData()
{
try
{
using (StreamReader reader = new StreamReader(GetFilePathByType(FilePathType.AnalysisData) + "customanalysisconfig.json"))
{
try
{
return JsonConvert.DeserializeObject<List<AnalysisData>>(reader.ReadToEnd());
}
catch (Exception ex)
{
throw new Exception("The file customanalysisconfig.json is invalid.");
}
}
}
catch
{
return new List<AnalysisData>();
}
}
public static void WriteCustomAnalysisTemplateData(List<AnalysisData> analysisDataList)
{
using (StreamWriter file = File.CreateText(GetFilePathByType(FilePathType.AnalysisData) + "customanalysisconfig.json"))
{
JsonSerializer serializer = new JsonSerializer();
serializer.Serialize(file, analysisDataList);
}
}
public static void RemoveCustomAnalysisTemplate(string analysisTemplateName)
{
try
{
var templates = ReadCustomAnalysisTemplateData();
var template = templates.Find(x => x.TemplateName == analysisTemplateName);
if (template != null)
{
var fullFilePath = GetFilePathByType(FilePathType.AnalysisData) + template.TemplateImagePath;
Directory.Delete(fullFilePath, true);
}
}
catch
{
}
}
public static Stream LoadAsStream(FilePathType filePathType, string fileName)
{
try
{
var fullFilePath = GetFilePathByType(filePathType) + fileName;
return new MemoryStream(File.ReadAllBytes(fullFilePath));
}
catch
{
Logger.LogStatus($"Unable to load file {fileName}");
return null;
}
}
public static Stream LoadAsStream(string fullFilePath)
{
return new MemoryStream(File.ReadAllBytes(fullFilePath));
}
public static void SaveFile(FilePathType filePathType, string subFolder, string fileName, MemoryStream memoryStream)
{
subFolder = String.IsNullOrEmpty(subFolder) ? String.Empty : subFolder + @"\";
var folderPath = GetFilePathByType(filePathType) + subFolder;
var fullFilePath = folderPath + fileName;
Directory.CreateDirectory(folderPath);
using (var file = new FileStream(fullFilePath, FileMode.Create, FileAccess.Write))
{
memoryStream.WriteTo(file);
}
}
public static List<string> GetFileNames(FilePathType filePathType, string subFolder, string filePrefix)
{
List<string> files = new List<string>();
var folderPath = GetFilePathByType(filePathType);
if (!String.IsNullOrEmpty(subFolder))
folderPath += subFolder + @"\";
string[] fileEntries = Directory.GetFiles(folderPath);
foreach (string fileEntry in fileEntries)
{
var fileName = Path.GetFileName(fileEntry);
if (!String.IsNullOrEmpty(filePrefix))
{
if(fileName.StartsWith(filePrefix))
files.Add(fileEntry);
}
else
files.Add(fileEntry);
}
return files;
}
public static string GetFilePathByType(FilePathType filePathType)
{
switch (filePathType)
{
case FilePathType.PreprocessingData:
return StartupPath + @"\Config\PreprocessingData\";
case FilePathType.AnalysisData:
return StartupPath + @"\Config\AnalysisData\";
case FilePathType.ProfileData:
case FilePathType.UserData:
return StartupPath + @"\Config\";
default:
return StartupPath;
}
}
}
public enum FilePathType
{
PreprocessingData,
AnalysisData,
ProfileData,
UserData,
Default
}
}

View file

@ -1,61 +0,0 @@
using AForge.Imaging;
using System;
using System.Drawing;
using System.Linq;
namespace MSFSPopoutPanelManager
{
public class ImageAnalysis
{
public static Point ExhaustiveTemplateMatchAnalysisAsync(Bitmap sourceImage, Bitmap templateImage, float similarityThreshHold, int panelStartingTop, int panelStartingLeft)
{
var x = panelStartingLeft - Convert.ToInt32(templateImage.Width * 1.5);
var y = 0;
var width = Convert.ToInt32(templateImage.Width * 1.5);
var height = sourceImage.Height;
var searchZone = new Rectangle(x, y, width, height);
var point = AnalyzeExpandImageBitmap(sourceImage, templateImage, similarityThreshHold, searchZone);
if (point != Point.Empty)
point.Y += panelStartingTop;
return point;
}
public static Point AnalyzeExpandImageBitmap(Bitmap sourceImage, Bitmap templateImage, float similarityThreshHold, Rectangle searchZone)
{
// Full image pixel to pixel matching algorithm
ExhaustiveTemplateMatching etm = new ExhaustiveTemplateMatching(similarityThreshHold);
TemplateMatch[] templateMatches = etm.ProcessImage(sourceImage, templateImage, searchZone);
if (templateMatches != null && templateMatches.Length > 0)
{
var match = templateMatches.OrderByDescending(x => x.Similarity).First(); // Just look at the first match
var xCoor = match.Rectangle.X + templateImage.Width / 12;
var yCoor = match.Rectangle.Y + templateImage.Height / 4;
return new Point(Convert.ToInt32(xCoor), Convert.ToInt32(yCoor));
}
return Point.Empty;
}
public static float ExhaustiveTemplateMatchAnalysisScore(Bitmap sourceImage, Bitmap templateImage, float similarityThreshHold)
{
// SUSAN corner block matching algorithm
SusanCornersDetector scd = new SusanCornersDetector(50, 8);
var points = scd.ProcessImage(sourceImage);
// process images searching for block matchings
ExhaustiveBlockMatching bm = new ExhaustiveBlockMatching(4, 8);
sourceImage = ImageOperation.ResizeImage(sourceImage, 800, 600);
templateImage = ImageOperation.ResizeImage(templateImage, 800, 600);
var templateMatches = bm.ProcessImage(sourceImage, points, templateImage);
return templateMatches.Count;
}
}
}

View file

@ -1,110 +0,0 @@
using AForge.Imaging;
using AForge.Imaging.Filters;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
namespace MSFSPopoutPanelManager
{
public class ImageOperation
{
public static byte[] ImageToByte(Bitmap image)
{
ImageConverter converter = new ImageConverter();
return (byte[])converter.ConvertTo(image, typeof(byte[]));
}
public static Bitmap ByteToImage(byte[] bytes)
{
return new Bitmap(new MemoryStream(bytes));
}
public static Bitmap ResizeImage(Bitmap sourceImage, double width, double height)
{
var bmp = new ResizeBilinear(Convert.ToInt32(width), Convert.ToInt32(height)).Apply(sourceImage);
return ImageOperation.ConvertToFormat(bmp, PixelFormat.Format24bppRgb);
}
public static Bitmap CropImage(Bitmap sourceImage, int x, int y, int width, int height)
{
Rectangle crop = new Rectangle(x, y, width, height);
var bmp = new Bitmap(crop.Width, crop.Height);
using (var gr = Graphics.FromImage(bmp))
{
gr.DrawImage(sourceImage, new Rectangle(0, 0, bmp.Width, bmp.Height), crop, GraphicsUnit.Pixel);
}
return ImageOperation.ConvertToFormat(bmp, PixelFormat.Format24bppRgb);
}
public static Bitmap ConvertToFormat(Bitmap image, PixelFormat format)
{
var copy = new Bitmap(image.Width, image.Height, format);
using (Graphics gr = Graphics.FromImage(copy))
{
gr.DrawImage(image, new Rectangle(0, 0, copy.Width, copy.Height));
}
return copy;
}
public static Bitmap TakeScreenShot(IntPtr windowHandle, bool maximized)
{
if (maximized)
{
const int SW_MAXIMIZE = 3;
PInvoke.ShowWindow(windowHandle, SW_MAXIMIZE);
}
// Set window to foreground so nothing can hide the window
PInvoke.SetForegroundWindow(windowHandle);
Thread.Sleep(500);
var rect = new Rect();
PInvoke.GetWindowRect(windowHandle, out rect);
var left = rect.Left;
var top = rect.Top;
var right = rect.Right;
var bottom = rect.Bottom;
var bounds = new Rectangle(left, top, right - left, bottom - top);
var bmp = new Bitmap(bounds.Width, bounds.Height);
using (Graphics g = Graphics.FromImage(bmp))
{
g.CopyFromScreen(new Point(bounds.Left, bounds.Top), Point.Empty, bounds.Size);
}
return ImageOperation.ConvertToFormat(bmp, PixelFormat.Format24bppRgb);
}
public static Bitmap HighLightMatchedPattern(Bitmap sourceImage, List<Rect> rectBoxes)
{
// Highlight the match in the source image
var data = sourceImage.LockBits(new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), ImageLockMode.ReadWrite, sourceImage.PixelFormat);
foreach (Rect rectBox in rectBoxes)
{
Rectangle rect = new Rectangle(rectBox.Left, rectBox.Top, rectBox.Width, rectBox.Height);
Drawing.Rectangle(data, rect, Color.Red);
}
sourceImage.UnlockBits(data);
sourceImage.Save(@".\debug.png");
return sourceImage;
}
public static Bitmap GetExpandButtonImage(int windowHeight)
{
var image = new Bitmap(FileManager.LoadAsStream(FilePathType.PreprocessingData, "separation_button.png"));
double template_image_ratio = Convert.ToDouble(windowHeight) / 1440; // expand button image was created on 1440p resolution
return ImageOperation.ResizeImage(image, Convert.ToInt32(image.Width * template_image_ratio), Convert.ToInt32(image.Height * template_image_ratio));
}
}
}

View file

@ -1,44 +0,0 @@
using System;
namespace MSFSPopoutPanelManager
{
public class Logger
{
public static event EventHandler<EventArgs<StatusMessage>> OnStatusLogged;
public static void LogStatus(string message)
{
var statusMessage = new StatusMessage() { Message = message, Priority = StatusPriority.Low };
OnStatusLogged?.Invoke(null, new EventArgs<StatusMessage>(statusMessage));
}
public static void LogStatus(string message, StatusPriority priority)
{
var statusMessage = new StatusMessage() { Message = message, Priority = priority };
OnStatusLogged?.Invoke(null, new EventArgs<StatusMessage>(statusMessage));
}
}
public class EventArgs<T> : EventArgs
{
public T Value { get; private set; }
public EventArgs(T val)
{
Value = val;
}
}
public class StatusMessage
{
public string Message { get; set; }
public StatusPriority Priority { get; set; }
}
public enum StatusPriority
{
High,
Low
}
}

View file

@ -1,506 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Text;
using System.Drawing.Imaging;
using System.Threading.Tasks;
using System.IO;
namespace MSFSPopoutPanelManager
{
public class PanelAnalysisModule
{
private Form _form;
public PanelAnalysisModule(Form form)
{
_form = form;
}
public void Analyze(WindowProcess simulatorProcess, int profileId, int panelsCount)
{
var panelsToBeIdentified = panelsCount;
// Get all child windows
var processZero = GetProcessZero();
// Move process zero childs back into simulator process
MoveChildWindowsIntoSimulatorProcess(simulatorProcess, processZero);
if (simulatorProcess.ChildWindows.Count > 0)
{
foreach(var customPopout in simulatorProcess.ChildWindows.FindAll(x => x.WindowType == WindowType.Undetermined))
{
while (panelsToBeIdentified > 1) // Do not have to separate the last panel
{
var coordinate = AnalyzeMergedPopoutWindows(customPopout.Handle);
if (!coordinate.IsEmpty)
SeparateUntitledPanel(customPopout.Handle, coordinate.X, coordinate.Y);
panelsToBeIdentified--;
}
panelsToBeIdentified = panelsCount;
}
}
// Now all newly pop out windows are in process zero, move them into flight simulator process
processZero = GetProcessZero();
MoveChildWindowsIntoSimulatorProcess(simulatorProcess, processZero);
// Analyze the content of the pop out panels
AnalyzePopoutWindows(simulatorProcess, profileId);
}
private WindowProcess GetProcessZero()
{
// Get process with PID of zero (PID zero launches all the popout windows for MSFS)
var process = Process.GetProcesses().ToList().Find(x => x.Id == 0);
var processZero = new WindowProcess()
{
ProcessId = process.Id,
ProcessName = process.ProcessName,
Handle = process.MainWindowHandle
};
GetChildWindows(processZero);
return processZero;
}
private void GetChildWindows(WindowProcess process)
{
int classNameLength = 256;
var childHandles = GetAllChildHandles(process.Handle);
childHandles.ForEach(childHandle =>
{
StringBuilder className = new StringBuilder(classNameLength);
PInvoke.GetClassName(childHandle, className, classNameLength);
if (className.ToString() == "AceApp")
{
process.ChildWindows.Add(new ChildWindow
{
ClassName = "AceApp",
Handle = childHandle,
Title = GetWindowTitle(childHandle)
});
}
});
}
private List<IntPtr> GetAllChildHandles(IntPtr parent)
{
var childHandles = new List<IntPtr>();
GCHandle gcChildhandlesList = GCHandle.Alloc(childHandles);
IntPtr pointerChildHandlesList = GCHandle.ToIntPtr(gcChildhandlesList);
try
{
PInvoke.EnumWindowProc childProc = new PInvoke.EnumWindowProc(EnumWindow);
PInvoke.EnumChildWindows(parent, childProc, pointerChildHandlesList);
}
finally
{
gcChildhandlesList.Free();
}
return childHandles;
}
private bool EnumWindow(IntPtr hWnd, IntPtr lParam)
{
var gcChildhandlesList = GCHandle.FromIntPtr(lParam);
if (gcChildhandlesList.Target == null)
return false;
var childHandles = gcChildhandlesList.Target as List<IntPtr>;
childHandles.Add(hWnd);
return true;
}
private string GetWindowTitle(IntPtr hWnd)
{
StringBuilder title = new StringBuilder(1024);
PInvoke.GetWindowText(hWnd, title, title.Capacity);
return String.IsNullOrEmpty(title.ToString()) ? null : title.ToString();
}
private void MoveChildWindowsIntoSimulatorProcess(WindowProcess simulatorProcess, WindowProcess processZero)
{
// The popout windows such as PFD and MFD attached itself to main window for Process ID zero instead of the MSFS process.
// Moving these windows back into MSFS main window
if (processZero != null)
{
// Clean up all existing simulator process child window data
simulatorProcess.ChildWindows.RemoveAll(x => x.WindowType == WindowType.Custom_Popout || x.WindowType == WindowType.BuiltIn_Popout);
foreach (var child in processZero.ChildWindows)
{
int parentProcessId;
PInvoke.GetWindowThreadProcessId(child.Handle, out parentProcessId);
if (simulatorProcess != null && parentProcessId == simulatorProcess.ProcessId && !simulatorProcess.ChildWindows.Exists(x => x.Handle == child.Handle))
{
if (String.IsNullOrEmpty(child.Title))
child.WindowType = WindowType.Undetermined;
else if (child.Title.Contains("(Custom)"))
child.WindowType = WindowType.Custom_Popout;
else if (child.Title.Contains("Microsoft Flight Simulator"))
child.WindowType = WindowType.FlightSimMainWindow;
else if (!String.IsNullOrEmpty(child.Title))
child.WindowType = WindowType.BuiltIn_Popout;
else
child.WindowType = WindowType.Undetermined;
simulatorProcess.ChildWindows.Add(child);
}
}
}
}
public Point AnalyzeMergedPopoutWindows(IntPtr windowHandle)
{
float EXHAUSTIVE_TEMPLATE_MATCHING_SIMILARITY_THRESHOLD = 0.86f;
var sourceImage = ImageOperation.TakeScreenShot(windowHandle, true);
var templateImage = ImageOperation.GetExpandButtonImage(sourceImage.Height);
var panelsStartingTop = GetPanelsStartingTop(windowHandle, sourceImage);
if (panelsStartingTop > sourceImage.Height / 2) // if usually the last panel occupied the entire window with no white menubar
return Point.Empty;
var panelsStartingLeft = GetPanelsStartingLeft(windowHandle, sourceImage, panelsStartingTop + 5);
var templateImageRatios = GetExpandButtonHeightRatio(windowHandle, sourceImage, 1);
var resizedSource = ImageOperation.CropImage(sourceImage, 0, panelsStartingTop, sourceImage.Width, sourceImage.Height / 12); // add around 100px per 1440p resolution
resizedSource.Save(FileManager.GetFilePathByType(FilePathType.PreprocessingData) + "source.png");
var resizedTemplate = ImageOperation.ResizeImage(templateImage, templateImage.Width * templateImageRatios[0], templateImage.Height * templateImageRatios[0]);
var point = ImageAnalysis.ExhaustiveTemplateMatchAnalysisAsync(resizedSource, resizedTemplate, EXHAUSTIVE_TEMPLATE_MATCHING_SIMILARITY_THRESHOLD, panelsStartingTop, panelsStartingLeft);
if (point.IsEmpty)
{
resizedTemplate = ImageOperation.ResizeImage(templateImage, templateImage.Width * templateImageRatios[1], templateImage.Height * templateImageRatios[1]);
point = ImageAnalysis.ExhaustiveTemplateMatchAnalysisAsync(resizedSource, resizedTemplate, EXHAUSTIVE_TEMPLATE_MATCHING_SIMILARITY_THRESHOLD, panelsStartingTop, panelsStartingLeft);
}
return point;
}
private List<double> GetExpandButtonHeightRatio(IntPtr windowHandle, Bitmap sourceImage, int numberOfRows, double percentFromLeft = 0.48)
{
var ratios = new List<double>();
const int SW_MAXIMIZE = 3;
PInvoke.ShowWindow(windowHandle, SW_MAXIMIZE);
PInvoke.SetForegroundWindow(windowHandle);
Thread.Sleep(200);
Rect rect = new Rect();
PInvoke.GetClientRect(windowHandle, out rect);
// Get a snippet of 1 pixel wide vertical strip of windows. We will choose the strip left of center.
// This is to determine when the actual panel's vertical pixel starts in the window. This will allow accurate sizing of the expand button image
var clientWindowHeight = rect.Bottom - rect.Top;
var left = Convert.ToInt32((rect.Right - rect.Left) * percentFromLeft); // look at around 48% from the left
var top = sourceImage.Height - clientWindowHeight;
// Using much faster image LockBits instead of GetPixel method
unsafe
{
var stripData = sourceImage.LockBits(new Rectangle(left, top, 1, clientWindowHeight), ImageLockMode.ReadWrite, sourceImage.PixelFormat);
int bytesPerPixel = Bitmap.GetPixelFormatSize(stripData.PixelFormat) / 8;
int heightInPixels = stripData.Height;
int widthInBytes = stripData.Width * bytesPerPixel;
byte* ptrFirstPixel = (byte*)stripData.Scan0;
// Find the first white pixel (the panel title bar)
for (int y = 0; y < heightInPixels; y++)
{
byte* currentLine = ptrFirstPixel + (y * stripData.Stride);
for (int x = 0; x < widthInBytes; x = x + bytesPerPixel)
{
int red = currentLine[x + 2];
int green = currentLine[x + 1];
int blue = currentLine[x];
if (red == 255 && green == 255 && blue == 255)
{
sourceImage.UnlockBits(stripData);
var unpopPanelSize = (clientWindowHeight - (y * 2)) / Convert.ToDouble(numberOfRows);
ratios.Add(unpopPanelSize / Convert.ToDouble(clientWindowHeight)); // 1 row of panel
ratios.Add(unpopPanelSize / 2 / Convert.ToDouble(clientWindowHeight)); // 2 rows of panel
return ratios;
}
}
}
sourceImage.UnlockBits(stripData);
}
return GetExpandButtonHeightRatio(windowHandle, sourceImage, numberOfRows, percentFromLeft - 0.01);
}
private int GetPanelsStartingTop(IntPtr windowHandle, Bitmap sourceImage, double percentFromLeft = 0.49)
{
const int SW_MAXIMIZE = 3;
PInvoke.ShowWindow(windowHandle, SW_MAXIMIZE);
PInvoke.SetForegroundWindow(windowHandle);
Thread.Sleep(250);
Rect rect = new Rect();
PInvoke.GetClientRect(windowHandle, out rect);
// Get a snippet of 1 pixel wide vertical strip of windows. We will choose the strip left of center.
// This is to determine when the actual panel's vertical pixel starts in the window. This will allow accurate sizing of the template image
var clientWindowHeight = rect.Bottom - rect.Top;
var left = Convert.ToInt32((rect.Right - rect.Left) * percentFromLeft); // look at around 49% from the left
var top = sourceImage.Height - clientWindowHeight;
if (top < 0 || left < 0)
return -1;
unsafe
{
var stripData = sourceImage.LockBits(new Rectangle(left, top, 1, clientWindowHeight), ImageLockMode.ReadWrite, sourceImage.PixelFormat);
int bytesPerPixel = Bitmap.GetPixelFormatSize(stripData.PixelFormat) / 8;
int heightInPixels = stripData.Height;
int widthInBytes = stripData.Width * bytesPerPixel;
byte* ptrFirstPixel = (byte*)stripData.Scan0;
for (int y = 0; y < heightInPixels; y++)
{
byte* currentLine = ptrFirstPixel + (y * stripData.Stride);
for (int x = 0; x < widthInBytes; x = x + bytesPerPixel)
{
int red = currentLine[x + 2];
int green = currentLine[x + 1];
int blue = currentLine[x];
if (red == 255 && green == 255 && blue == 255)
{
sourceImage.UnlockBits(stripData);
return y + top;
}
}
}
sourceImage.UnlockBits(stripData);
}
return GetPanelsStartingTop(windowHandle, sourceImage, percentFromLeft - 0.01);
}
private int GetPanelsStartingLeft(IntPtr windowHandle, Bitmap sourceImage, int top)
{
Rect rect = new Rect();
PInvoke.GetClientRect(windowHandle, out rect);
// Get a snippet of 1 pixel wide horizontal strip of windows
var clientWindowWidth = rect.Right - rect.Left;
unsafe
{
var stripData = sourceImage.LockBits(new Rectangle(0, top, clientWindowWidth, 1), ImageLockMode.ReadWrite, sourceImage.PixelFormat);
int bytesPerPixel = Bitmap.GetPixelFormatSize(stripData.PixelFormat) / 8;
int widthInPixels = stripData.Width;
int heightInBytes = stripData.Height * bytesPerPixel;
byte* ptrFirstPixel = (byte*)stripData.Scan0;
for (int x = 0; x < widthInPixels; x++)
{
byte* currentLine = ptrFirstPixel - (x * bytesPerPixel);
for (int y = 0; y < heightInBytes; y = y + bytesPerPixel)
{
int red = currentLine[y + 2];
int green = currentLine[y + 1];
int blue = currentLine[y];
if (red == 255 && green == 255 && blue == 255)
{
sourceImage.UnlockBits(stripData);
return sourceImage.Width - x;
}
}
}
sourceImage.UnlockBits(stripData);
}
return -1;
}
private void SeparateUntitledPanel(IntPtr windowHandle, int x, int y)
{
const uint MOUSEEVENTF_LEFTDOWN = 0x02;
const uint MOUSEEVENTF_LEFTUP = 0x04;
var point = new Point { X = x, Y = y };
Cursor.Position = new Point(point.X, point.Y);
// Wait for mouse to get into position
Thread.Sleep(500);
PInvoke.mouse_event(MOUSEEVENTF_LEFTDOWN, point.X, point.Y, 0, 0);
Thread.Sleep(200);
PInvoke.mouse_event(MOUSEEVENTF_LEFTUP, point.X, point.Y, 0, 0);
Cursor.Position = new Point(point.X + 50, point.Y + 50);
}
private void AnalyzePopoutWindows(WindowProcess simulatorProcess, int profileId)
{
var panelScores = new List<PanelScore>();
// Get analysis template data for the profile
var planeProfile = FileManager.ReadAllPlaneProfileData().Find(x => x.ProfileId == profileId);
var templateData = FileManager.ReadAllAnalysisTemplateData().Find(x => x.TemplateName == planeProfile.AnalysisTemplateName);
if(templateData == null)
{
CreateNewAnalysisTemplate(simulatorProcess, profileId);
return;
}
// Load the template images for the selected profile
var templates = new List<KeyValuePair<string, Bitmap>>();
foreach (var template in templateData.Templates)
{
foreach (var imagePath in template.ImagePaths)
{
templates.Add(new KeyValuePair<string, Bitmap>(template.PopoutName, ImageOperation.ConvertToFormat(new Bitmap(FileManager.LoadAsStream(FilePathType.AnalysisData, imagePath)), PixelFormat.Format24bppRgb)));
}
}
var popouts = simulatorProcess.ChildWindows.FindAll(x => x.WindowType == WindowType.Undetermined);
foreach (var popout in popouts)
{
popout.WindowType = WindowType.Custom_Popout;
// Resize all untitled pop out panels to 800x600 and set it to foreground
PInvoke.MoveWindow(popout.Handle, 0, 0, 800, 600, true);
PInvoke.SetForegroundWindow(popout.Handle);
Thread.Sleep(300); // ** this delay is important to allow the window to go into focus before screenshot is taken
var srcImage = ImageOperation.TakeScreenShot(popout.Handle, false);
var srcImageBytes = ImageOperation.ImageToByte(srcImage);
Parallel.ForEach(templates, new ParallelOptions { MaxDegreeOfParallelism = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * 0.75) * 1.0)) }, template =>
{
var src = ImageOperation.ByteToImage(srcImageBytes);
panelScores.Add(new PanelScore
{
WindowHandle = popout.Handle,
PanelName = template.Key,
Score = ImageAnalysis.ExhaustiveTemplateMatchAnalysisScore(src, template.Value, 0.85f)
});
});
}
// Gets the highest matching score for template matches for each panel
var panels = (from s in panelScores
group s by s.WindowHandle into g
select g.OrderByDescending(z => z.Score).FirstOrDefault()).ToList();
// Set the pop out panel title bar text to identify it
foreach (var panel in panels)
{
var title = $"{panel.PanelName} (Custom)";
simulatorProcess.ChildWindows.Find(x => x.Handle == panel.WindowHandle).Title = title;
PInvoke.SetWindowText(panel.WindowHandle, title);
}
}
private void CreateNewAnalysisTemplate(WindowProcess simulatorProcess, int profileId)
{
var planeProfile = FileManager.ReadAllPlaneProfileData().Find(x => x.ProfileId == profileId);
var popouts = simulatorProcess.ChildWindows.FindAll(x => x.WindowType == WindowType.Undetermined);
var images = new List<Bitmap>();
foreach (var popout in popouts)
{
popout.WindowType = WindowType.Custom_Popout;
// Resize all untitled pop out panels to 800x600 and set it to foreground
PInvoke.MoveWindow(popout.Handle, 0, 0, 800, 600, true);
PInvoke.SetForegroundWindow(popout.Handle);
Thread.Sleep(300); // ** this delay is important to allow the window to go into focus before screenshot is taken
var screenshot = ImageOperation.TakeScreenShot(popout.Handle, false);
images.Add(screenshot);
}
var customAnalysisDataList = FileManager.ReadCustomAnalysisTemplateData();
AnalysisData analysisData = new AnalysisData();
analysisData.TemplateName = planeProfile.ProfileName;
analysisData.IsUserTemplate = true;
for (var i = 0; i < popouts.Count; i++)
{
var panelName = "Panel" + (i + 1);
var imageName = @$"{panelName}.png";
var imagePath = analysisData.TemplateImagePath;
using (var memoryStream = new MemoryStream())
{
images[i].Save(memoryStream, ImageFormat.Png);
FileManager.SaveFile(FilePathType.AnalysisData, imagePath, imageName, memoryStream);
}
analysisData.Templates.Add(new Template()
{
PopoutId = i + 1,
PopoutName = panelName,
ImagePaths = new List<string>() { @$"{imagePath}/{imageName}" }
});
var panelTitle = $"{panelName} (Custom)";
simulatorProcess.ChildWindows.Find(x => x.Handle == popouts[i].Handle).Title = panelTitle;
PInvoke.SetWindowText(popouts[i].Handle, panelTitle);
}
customAnalysisDataList.Add(analysisData);
FileManager.WriteCustomAnalysisTemplateData(customAnalysisDataList);
}
private Bitmap CloneImage(Bitmap srcImage)
{
return srcImage.Clone(new Rectangle(0, 0, srcImage.Width, srcImage.Height), PixelFormat.Format24bppRgb);
}
}
public class PanelScore
{
public IntPtr WindowHandle { get; set; }
public string PanelName { get; set; }
public float Score { get; set; }
}
}

View file

@ -1,348 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager
{
public class PanelManager
{
private Form _appForm;
private const int MSFS_CONNECTION_RETRY_TIMEOUT = 2000;
private System.Timers.Timer _timer;
private WindowProcess _simulatorProcess;
private PanelLocationSelectionModule _panelLocationSelectionModule;
private PanelAnalysisModule _panelAnalysisModule;
private UserPlaneProfile _currentPlaneProfile;
public PanelManager(Form form)
{
_appForm = form;
_panelLocationSelectionModule = new PanelLocationSelectionModule(form);
_panelAnalysisModule = new PanelAnalysisModule(form);
_panelLocationSelectionModule.OnSelectionCompleted += (source, e) => { SavePanelSelectionLocation(); };
}
public event EventHandler OnSimulatorStarted;
public event EventHandler OnPanelSettingsChanged;
public event EventHandler OnAnalysisCompleted;
public PanelLocationSelectionModule PanelLocationSelection
{
get { return _panelLocationSelectionModule; }
}
public UserPlaneProfile CurrentPanelProfile
{
get { return _currentPlaneProfile; }
}
public void CheckSimulatorStarted()
{
// Autoconnect to flight simulator
_timer = new System.Timers.Timer();
_timer.Interval = MSFS_CONNECTION_RETRY_TIMEOUT;
_timer.Enabled = true;
_timer.Elapsed += (source, e) =>
{
foreach (var process in Process.GetProcesses())
{
if (process.ProcessName == "FlightSimulator" && _simulatorProcess == null)
{
_simulatorProcess = new WindowProcess()
{
ProcessId = process.Id,
ProcessName = process.ProcessName,
Handle = process.MainWindowHandle
};
_timer.Enabled = false;
OnSimulatorStarted?.Invoke(this, null);
}
}
};
}
public void PlaneProfileChanged(int? profileId, bool showCoordinateOverlay)
{
Logger.LogStatus(String.Empty);
if (profileId != null)
{
_currentPlaneProfile = FileManager.GetUserPlaneProfile((int) profileId);
_panelLocationSelectionModule.PlaneProfile = _currentPlaneProfile;
_panelLocationSelectionModule.ShowPanelLocationOverlay(showCoordinateOverlay);
_panelLocationSelectionModule.UpdatePanelLocationUI();
}
else
{
_panelLocationSelectionModule.PlaneProfile = null;
_panelLocationSelectionModule.ShowPanelLocationOverlay(showCoordinateOverlay);
_panelLocationSelectionModule.UpdatePanelLocationUI();
}
}
public void SetDefaultProfile()
{
var userData = FileManager.ReadUserData();
userData.DefaultProfileId = _currentPlaneProfile.ProfileId;
FileManager.WriteUserData(userData);
var profileName = FileManager.ReadAllPlaneProfileData().Find(x => x.ProfileId == _currentPlaneProfile.ProfileId).ProfileName;
Logger.LogStatus($"Profile '{profileName}' has been set as default.");
}
public void SavePanelSelectionLocation()
{
var profileId = _currentPlaneProfile.ProfileId;
var userData = FileManager.ReadUserData();
if (userData == null)
userData = new UserData();
if (!userData.Profiles.Exists(x => x.ProfileId == profileId))
{
userData.Profiles.Add(_currentPlaneProfile);
}
else
{
var profileIndex = userData.Profiles.FindIndex(x => x.ProfileId == profileId);
userData.Profiles[profileIndex] = _currentPlaneProfile;
}
FileManager.WriteUserData(userData);
Logger.LogStatus("Panel location coordinates have been saved.");
}
public void Analyze()
{
if (PanelLocationSelection.PanelCoordinates == null || PanelLocationSelection.PanelCoordinates.Count == 0)
{
Logger.LogStatus("No panel locations to be analyze. Please select at least one panel first.");
return;
}
Logger.LogStatus("Panel analysis in progress. Please wait...");
Thread.Sleep(1000); // allow time for the mouse to be stopped moving by the user
PanelLocationSelection.ShowPanelLocationOverlay(false);
WindowManager.ExecutePopout(_simulatorProcess.Handle, PanelLocationSelection.PanelCoordinates);
_panelAnalysisModule.Analyze(_simulatorProcess, _currentPlaneProfile.ProfileId, PanelLocationSelection.PanelCoordinates.Count);
PInvoke.SetForegroundWindow(_appForm.Handle);
// Get the identified panel windows and previously saved panel destination location
List<PanelDestinationInfo> panelDestinationList = new List<PanelDestinationInfo>();
var panels = _simulatorProcess.ChildWindows.FindAll(x => x.WindowType == WindowType.Custom_Popout || x.WindowType == WindowType.BuiltIn_Popout);
var hasExistingData = _currentPlaneProfile.PanelSettings.PanelDestinationList.Count > 0;
_currentPlaneProfile.PanelSettings.PanelDestinationList.ForEach(x => x.IsOpened = false);
foreach (var panel in panels)
{
var index = _currentPlaneProfile.PanelSettings.PanelDestinationList.FindIndex(x => x.PanelName == panel.Title);
if (index != -1)
{
_currentPlaneProfile.PanelSettings.PanelDestinationList[index].PanelHandle = panel.Handle;
_currentPlaneProfile.PanelSettings.PanelDestinationList[index].IsOpened = true;
_currentPlaneProfile.PanelSettings.PanelDestinationList[index].PanelType = panel.WindowType;
}
else
{
Rect rect = new Rect();
var window = PInvoke.GetWindowRect(panel.Handle, out rect);
PanelDestinationInfo panelDestinationInfo = new PanelDestinationInfo
{
PanelName = panel.Title,
PanelHandle = panel.Handle,
Left = rect.Left,
Top = rect.Top,
Width = rect.Right - rect.Left,
Height = rect.Bottom - rect.Top,
IsOpened = true,
PanelType = panel.WindowType
};
_currentPlaneProfile.PanelSettings.PanelDestinationList.Add(panelDestinationInfo);
}
}
OnAnalysisCompleted?.Invoke(this, null);
if (panelDestinationList.Count > 0)
Logger.LogStatus("Analysis has been completed. You may now drag the panels to their desire locations.");
else
Logger.LogStatus("No panel has been identified.");
if(hasExistingData)
ApplyPanelSettings();
PInvoke.SetForegroundWindow(_appForm.Handle);
}
public void ApplyPanelSettings()
{
var panels = _simulatorProcess.ChildWindows.FindAll(x => x.WindowType == WindowType.Custom_Popout || x.WindowType == WindowType.BuiltIn_Popout);
int applyCount = 0;
Parallel.ForEach(panels, panel =>
{
var panelDestinationInfo = _currentPlaneProfile.PanelSettings.PanelDestinationList.Find(x => x.PanelName == panel.Title);
if (panelDestinationInfo != null && panelDestinationInfo.Width != 0 && panelDestinationInfo.Height != 0)
{
if (panelDestinationInfo.Left != 0 && panelDestinationInfo.Top != 0)
{
// Apply locations
PInvoke.MoveWindow(panel.Handle, panelDestinationInfo.Left, panelDestinationInfo.Top, panelDestinationInfo.Width, panelDestinationInfo.Height, true);
applyCount++;
}
// Apply always on top
Thread.Sleep(300);
WindowManager.ApplyAlwaysOnTop(panel.Handle, _currentPlaneProfile.PanelSettings.AlwaysOnTop);
// Apply hide title bar
Thread.Sleep(300);
WindowManager.ApplyHidePanelTitleBar(panel.Handle, _currentPlaneProfile.PanelSettings.HidePanelTitleBar);
}
});
if(applyCount > 0)
Logger.LogStatus("Previously saved panel settings have been applied.");
else if (panels.Count > 0 && applyCount == 0)
Logger.LogStatus("Please move the newly identified panels to their desire locations. Once everything is perfect, click 'Save Settings' and these settings will be used in future flights.");
else
Logger.LogStatus("No panel has been found.");
PInvoke.SetForegroundWindow(_appForm.Handle);
}
public void SavePanelSettings()
{
var profileId = _currentPlaneProfile.ProfileId;
// Get latest panel destination locations from screen
var panels = _simulatorProcess.ChildWindows.FindAll(x => x.WindowType == WindowType.Custom_Popout || x.WindowType == WindowType.BuiltIn_Popout);
foreach(var panel in panels)
{
Rect rect = new Rect();
var window = PInvoke.GetWindowRect(panel.Handle, out rect);
var panelDestinationInfo = _currentPlaneProfile.PanelSettings.PanelDestinationList.Find(x => x.PanelName == panel.Title);
if (panelDestinationInfo == null)
{
panelDestinationInfo = new PanelDestinationInfo();
_currentPlaneProfile.PanelSettings.PanelDestinationList.Add(panelDestinationInfo);
}
panelDestinationInfo.PanelName = panel.Title;
panelDestinationInfo.Left = rect.Left;
panelDestinationInfo.Top = rect.Top;
panelDestinationInfo.Width = rect.Right - rect.Left;
panelDestinationInfo.Height = rect.Bottom - rect.Top;
}
var userData = FileManager.ReadUserData();
if (!userData.Profiles.Exists(x => x.ProfileId == _currentPlaneProfile.ProfileId))
userData.Profiles.Add(_currentPlaneProfile);
else
{
var profileIndex = userData.Profiles.FindIndex(x => x.ProfileId == _currentPlaneProfile.ProfileId);
userData.Profiles[profileIndex] = _currentPlaneProfile;
}
FileManager.WriteUserData(userData);
OnPanelSettingsChanged?.Invoke(this, null);
Logger.LogStatus("Panel settings have been saved.");
}
public void UpdatePanelLocationUI()
{
_panelLocationSelectionModule.UpdatePanelLocationUI();
}
public int AddUserProfile(string profileName, string analysisTemplateName)
{
var userPlaneProfiles = FileManager.ReadCustomPlaneProfileData();
int nextProfileId = 1000;
if (userPlaneProfiles.Count > 0)
nextProfileId = userPlaneProfiles.Max(x => x.ProfileId) + 1;
profileName = $"User - {profileName}";
var newPlaneProfile = new PlaneProfile()
{
ProfileId = nextProfileId,
ProfileName = profileName,
AnalysisTemplateName = analysisTemplateName == "New" ? profileName : analysisTemplateName,
IsUserProfile = true
};
userPlaneProfiles.Add(newPlaneProfile);
FileManager.WriteCustomPlaneProfileData(userPlaneProfiles);
return nextProfileId;
}
public void DeleteUserProfile(PlaneProfile planeProfile)
{
if (planeProfile.IsUserProfile)
{
// Remove custom plane profile data
var profiles = FileManager.ReadCustomPlaneProfileData();
profiles.RemoveAll(x => x.ProfileId == planeProfile.ProfileId);
FileManager.WriteCustomPlaneProfileData(profiles);
// Remove analysis template data
if (!profiles.Exists(x => x.AnalysisTemplateName == planeProfile.AnalysisTemplateName))
{
FileManager.RemoveCustomAnalysisTemplate(planeProfile.AnalysisTemplateName);
var templates = FileManager.ReadCustomAnalysisTemplateData();
templates.RemoveAll(x => x.TemplateName == planeProfile.AnalysisTemplateName);
FileManager.WriteCustomAnalysisTemplateData(templates);
}
// Remove profile from user data
var userData = FileManager.ReadUserData();
userData.Profiles.RemoveAll(x => x.ProfileId == planeProfile.ProfileId);
FileManager.WriteUserData(userData);
}
else
{
// Remove plane profile data
var profiles = FileManager.ReadBuiltInPlaneProfileData();
profiles.RemoveAll(x => x.ProfileId == planeProfile.ProfileId);
FileManager.WriteBuiltInPlaneProfileData(profiles);
// Remove profile from user data
var userData = FileManager.ReadUserData();
userData.Profiles.RemoveAll(x => x.ProfileId == planeProfile.ProfileId);
FileManager.WriteUserData(userData);
}
_currentPlaneProfile = null;
}
}
}

View file

@ -1,59 +0,0 @@
using System.Collections.Generic;
namespace MSFSPopoutPanelManager
{
public class PlaneProfile
{
public PlaneProfile()
{
ProfileId = 1000;
IsUserProfile = false;
AnalysisTemplateName = "none";
}
public int ProfileId { get; set; }
public string ProfileName { get; set; }
public string AnalysisTemplateName { get; set; }
public bool IsUserProfile { get; set; }
}
public class AnalysisData
{
public AnalysisData()
{
Templates = new List<Template>();
IsUserTemplate = false;
}
public string TemplateName { get; set; }
public bool IsUserTemplate { get; set; }
public List<Template> Templates { get; set; }
public string TemplateImagePath
{
get
{
return TemplateName.Replace(" ", "_").Replace("/", "_").Replace(@"\", "_");
}
}
}
public class Template
{
public Template()
{
ImagePaths = new List<string>();
}
public int PopoutId { get; set; }
public string PopoutName { get; set; }
public List<string> ImagePaths { get; set; }
}
}

View file

@ -1,37 +0,0 @@
using System.Runtime.InteropServices;
namespace MSFSPopoutPanelManager
{
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public int Width
{
get
{
return Right - Left;
}
}
public int Height
{
get
{
return Bottom - Top;
}
}
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
}
}

View file

@ -1,80 +0,0 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace MSFSPopoutPanelManager
{
public class UserData
{
public UserData()
{
Profiles = new List<UserPlaneProfile>();
DefaultProfileId = 1;
}
public List<UserPlaneProfile> Profiles { get; set; }
public int DefaultProfileId { get; set; }
}
public class UserPlaneProfile
{
public UserPlaneProfile()
{
PanelSourceCoordinates = new List<PanelSourceCoordinate>();
PanelSettings = new PanelSettings();
}
public int ProfileId { get; set; }
public PanelSettings PanelSettings { get; set; }
public List<PanelSourceCoordinate> PanelSourceCoordinates;
}
public class PanelSourceCoordinate
{
public int PanelIndex { get; set; }
public int X { get; set; }
public int Y { get; set; }
}
public class PanelSettings
{
public PanelSettings()
{
PanelDestinationList = new List<PanelDestinationInfo>();
AlwaysOnTop = false;
HidePanelTitleBar = false;
}
public List<PanelDestinationInfo> PanelDestinationList { get; set; }
public bool AlwaysOnTop { get; set; }
public bool HidePanelTitleBar { get; set; }
}
public class PanelDestinationInfo
{
public string PanelName { get; set; }
public int Top { get; set; }
public int Left { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public WindowType PanelType { get; set; }
[JsonIgnore]
public IntPtr PanelHandle { get; set; }
[JsonIgnore]
public bool IsOpened { get; set; }
}
}

View file

@ -1,96 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager
{
public class WindowManager
{
private const int SWP_NOMOVE = 0x0002;
private const int SWP_NOSIZE = 0x0001;
private const int SWP_ALWAYS_ON_TOP = SWP_NOMOVE | SWP_NOSIZE;
private const int GWL_STYLE = -16;
private const int WS_SIZEBOX = 0x00040000;
private const int WS_BORDER = 0x00800000;
private const int WS_DLGFRAME = 0x00400000;
private const int WS_CAPTION = WS_BORDER | WS_DLGFRAME;
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();
}
private static IntPtr CreateLParam(int LoWord, int HiWord)
{
return (IntPtr)((HiWord << 16) | (LoWord & 0xffff));
}
public static void ExecutePopout(IntPtr simulatorHandle, List<PanelSourceCoordinate> screenCoordinates)
{
PInvoke.SetForegroundWindow(simulatorHandle);
const uint MOUSEEVENTF_LEFTDOWN = 0x02;
const uint MOUSEEVENTF_LEFTUP = 0x04;
const uint KEYEVENTF_EXTENDEDKEY = 0x01;
const uint KEYEVENTF_KEYUP = 0x2;
const uint VK_RMENU = 0xA5; // Right Alternate key
foreach (var coor in screenCoordinates)
{
// Move the cursor to the flight simulator screen then move the cursor into position
PInvoke.SetCursorPos(0, 0);
PInvoke.SetCursorPos(coor.X, coor.Y);
// Wait for mouse to get into position
Thread.Sleep(1000);
// Force a left click first
PInvoke.mouse_event(MOUSEEVENTF_LEFTDOWN, coor.X, coor.Y, 0, 0);
Thread.Sleep(200);
PInvoke.mouse_event(MOUSEEVENTF_LEFTUP, coor.X, coor.Y, 0, 0);
Thread.Sleep(300);
// Pop out the screen by Alt Left click
PInvoke.keybd_event(Convert.ToByte(VK_RMENU), 0, KEYEVENTF_EXTENDEDKEY, 0);
PInvoke.mouse_event(MOUSEEVENTF_LEFTDOWN, coor.X, coor.Y, 0, 0);
Thread.Sleep(200);
PInvoke.mouse_event(MOUSEEVENTF_LEFTUP, coor.X, coor.Y, 0, 0);
PInvoke.keybd_event(Convert.ToByte(VK_RMENU), 0, KEYEVENTF_KEYUP, 0);
}
}
public static void ApplyHidePanelTitleBar(IntPtr handle, bool hideTitleBar)
{
var currentStyle = PInvoke.GetWindowLong(handle, GWL_STYLE).ToInt64();
if (hideTitleBar)
PInvoke.SetWindowLong(handle, GWL_STYLE, (uint)(currentStyle & ~(WS_CAPTION | WS_SIZEBOX)));
else
PInvoke.SetWindowLong(handle, GWL_STYLE, (uint)(currentStyle | (WS_CAPTION | WS_SIZEBOX)));
}
public static void ApplyAlwaysOnTop(IntPtr handle, bool alwaysOnTop)
{
if (alwaysOnTop)
{
Rect rect = new Rect();
PInvoke.GetWindowRect(handle, out rect);
PInvoke.SetWindowPos(handle, -1, rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top, SWP_ALWAYS_ON_TOP);
}
else
{
Rect rect = new Rect();
PInvoke.GetWindowRect(handle, out rect);
PInvoke.SetWindowPos(handle, -2, rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top, 0);
}
}
}
}

View file

@ -1,5 +1,11 @@
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;
@ -7,21 +13,28 @@ namespace MSFSPopoutPanelManager
{
static class Program
{
private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
/// <summary>
/// The main entry point for the application.
/// 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
@ -35,6 +48,36 @@ namespace MSFSPopoutPanelManager
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

@ -1,63 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace MSFSPopoutPanelManager.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MSFSPopoutPanelManager.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View file

@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<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>

89
Provider/FileManager.cs Normal file
View file

@ -0,0 +1,89 @@
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)
{
return new AppSettingData();
}
}
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

@ -0,0 +1,110 @@
using MSFSPopoutPanelManager.Shared;
using System;
using System.Threading;
namespace MSFSPopoutPanelManager.Provider
{
public class InputEmulationManager
{
const uint MOUSEEVENTF_LEFTDOWN = 0x02;
const uint MOUSEEVENTF_LEFTUP = 0x04;
const uint KEYEVENTF_EXTENDEDKEY = 0x01;
const uint KEYEVENTF_KEYDOWN = 0x0;
const uint KEYEVENTF_KEYUP = 0x2;
const uint VK_RMENU = 0xA5;
const uint VK_LMENU = 0xA4;
const uint VK_LCONTROL = 0xA2;
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);
Thread.Sleep(300);
PInvoke.mouse_event(MOUSEEVENTF_LEFTDOWN, x, y, 0, 0);
Thread.Sleep(200);
PInvoke.mouse_event(MOUSEEVENTF_LEFTUP, x, y, 0, 0);
}
public static void PopOutPanel(int x, int y)
{
PInvoke.SetCursorPos(x, y);
Thread.Sleep(300);
PInvoke.keybd_event(Convert.ToByte(VK_RMENU), 0, KEYEVENTF_EXTENDEDKEY, 0);
PInvoke.mouse_event(MOUSEEVENTF_LEFTDOWN, x, y, 0, 0);
Thread.Sleep(200);
PInvoke.mouse_event(MOUSEEVENTF_LEFTUP, x, y, 0, 0);
PInvoke.keybd_event(Convert.ToByte(VK_RMENU), 0, KEYEVENTF_KEYUP, 0);
}
public static void CenterView(IntPtr hwnd)
{
PInvoke.SetForegroundWindow(hwnd);
Thread.Sleep(500);
PInvoke.SetFocus(hwnd);
Thread.Sleep(300);
// First center view using Ctrl-Space
PInvoke.keybd_event(Convert.ToByte(VK_LCONTROL), 0, KEYEVENTF_KEYDOWN, 0);
PInvoke.keybd_event(Convert.ToByte(VK_SPACE), 0, KEYEVENTF_KEYDOWN, 0);
Thread.Sleep(200);
PInvoke.keybd_event(Convert.ToByte(VK_SPACE), 0, KEYEVENTF_KEYUP, 0);
PInvoke.keybd_event(Convert.ToByte(VK_LCONTROL), 0, KEYEVENTF_KEYUP, 0);
Thread.Sleep(200);
}
public static void SaveCustomViewZero(IntPtr hwnd)
{
PInvoke.SetForegroundWindow(hwnd);
Thread.Sleep(500);
PInvoke.SetFocus(hwnd);
Thread.Sleep(300);
// Set view using Ctrl-Alt-0
PInvoke.keybd_event(Convert.ToByte(VK_LCONTROL), 0, KEYEVENTF_KEYDOWN, 0);
PInvoke.keybd_event(Convert.ToByte(VK_LMENU), 0, KEYEVENTF_KEYDOWN, 0);
PInvoke.keybd_event(Convert.ToByte(KEY_0), 0, KEYEVENTF_KEYDOWN, 0);
Thread.Sleep(200);
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)
{
PInvoke.SetForegroundWindow(hwnd);
Thread.Sleep(500);
PInvoke.SetFocus(hwnd);
Thread.Sleep(300);
// First center view using Ctrl-Space
PInvoke.keybd_event(Convert.ToByte(VK_LCONTROL), 0, KEYEVENTF_KEYDOWN, 0);
PInvoke.keybd_event(Convert.ToByte(VK_SPACE), 0, KEYEVENTF_KEYDOWN, 0);
Thread.Sleep(200);
PInvoke.keybd_event(Convert.ToByte(VK_SPACE), 0, KEYEVENTF_KEYUP, 0);
PInvoke.keybd_event(Convert.ToByte(VK_LCONTROL), 0, KEYEVENTF_KEYUP, 0);
Thread.Sleep(200);
// Then load view using Alt-0
PInvoke.keybd_event(Convert.ToByte(VK_LMENU), 0, KEYEVENTF_KEYDOWN, 0);
PInvoke.keybd_event(Convert.ToByte(KEY_0), 0, KEYEVENTF_KEYDOWN, 0);
Thread.Sleep(200);
PInvoke.keybd_event(Convert.ToByte(KEY_0), 0, KEYEVENTF_KEYUP, 0);
PInvoke.keybd_event(Convert.ToByte(VK_LMENU), 0, KEYEVENTF_KEYUP, 0);
}
}
}

View file

@ -1,26 +1,31 @@
using Gma.System.MouseKeyHook;
using MSFSPopoutPanelManager.Shared;
using MSFSPopoutPanelManager.UI;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager
namespace MSFSPopoutPanelManager.Provider
{
public class PanelLocationSelectionModule
public class PanelSelectionManager
{
private IKeyboardMouseEvents _mouseHook;
private int _panelIndex;
private Form _appForm;
public event EventHandler OnLocationListChanged;
public event EventHandler OnSelectionStarted;
public event EventHandler OnSelectionCompleted;
public event EventHandler<EventArgs<PanelSourceCoordinate>> OnPanelAdded;
public event EventHandler OnPanelSubtracted;
public PanelLocationSelectionModule(Form appForm)
public List<PanelSourceCoordinate> PanelCoordinates { get; set; }
public PanelSelectionManager()
{
_appForm = appForm;
PanelCoordinates = new List<PanelSourceCoordinate>();
}
public UserPlaneProfile PlaneProfile { get; set; }
public Form AppForm { get; set; }
public void Start()
{
@ -32,22 +37,22 @@ namespace MSFSPopoutPanelManager
_panelIndex = 1;
PlaneProfile.PanelSourceCoordinates = new List<PanelSourceCoordinate>();
PlaneProfile.PanelSettings = new PanelSettings();
PanelCoordinates = new List<PanelSourceCoordinate>();
ShowPanelLocationOverlay(true);
OnSelectionStarted?.Invoke(this, null);
UpdatePanelLocationUI();
DrawPanelLocationOverlay();
Logger.LogStatus("Panels selection has started.");
Logger.Status("Panels selection has started.", StatusMessageType.Info);
}
public void Reset()
{
PlaneProfile.PanelSourceCoordinates = new List<PanelSourceCoordinate>();
PanelCoordinates = new List<PanelSourceCoordinate>();
_panelIndex = 1;
UpdatePanelLocationUI();
DrawPanelLocationOverlay();
}
public void DrawPanelLocationOverlay()
@ -58,9 +63,9 @@ namespace MSFSPopoutPanelManager
Application.OpenForms[i].Close();
}
if (PlaneProfile != null && PlaneProfile.PanelSourceCoordinates != null && PlaneProfile.PanelSourceCoordinates.Count > 0)
if (PanelCoordinates.Count > 0)
{
foreach (var coor in PlaneProfile.PanelSourceCoordinates)
foreach (var coor in PanelCoordinates)
WindowManager.AddPanelLocationSelectionOverlay(coor.PanelIndex.ToString(), coor.X, coor.Y);
}
}
@ -74,14 +79,6 @@ namespace MSFSPopoutPanelManager
}
}
public List<PanelSourceCoordinate> PanelCoordinates
{
get
{
return PlaneProfile.PanelSourceCoordinates;
}
}
private void Stop()
{
if (_mouseHook != null)
@ -91,9 +88,20 @@ namespace MSFSPopoutPanelManager
_mouseHook = null;
}
// 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);
Thread.Sleep(500);
}
}
OnSelectionCompleted?.Invoke(this, null);
UpdatePanelLocationUI();
DrawPanelLocationOverlay();
}
private void HandleMouseHookMouseDownExt(object sender, MouseEventExtArgs e)
@ -107,47 +115,44 @@ namespace MSFSPopoutPanelManager
{
Stop();
if (PlaneProfile.PanelSourceCoordinates.Count > 0)
Logger.LogStatus($"Panels selection completed and {PlaneProfile.PanelSourceCoordinates.Count} panel(s) have been selected. Please click 'Analyze' to start popping out these panels.");
if (PanelCoordinates.Count > 0)
Logger.Status("Panels selection is completed. Please click 'Start Pop Out' to start popping out these panels.", StatusMessageType.Info);
else
Logger.LogStatus("Panels selection completed. No panel has been selected.");
Logger.Status("Panels selection is completed. No panel has been selected.", StatusMessageType.Info);
// Bring app windows back to top
PInvoke.SetForegroundWindow(_appForm.Handle);
PInvoke.SetForegroundWindow(AppForm.Handle);
}
else if (shiftPressed && Application.OpenForms.Count > 1)
else if (shiftPressed && Application.OpenForms.Count >= 1)
{
// Remove last drawn overlay and last value
// Remove last drawn overlay
Application.OpenForms[Application.OpenForms.Count - 1].Close();
PlaneProfile.PanelSourceCoordinates.RemoveAt(PlaneProfile.PanelSourceCoordinates.Count - 1);
PanelCoordinates.RemoveAt(PanelCoordinates.Count - 1);
OnPanelSubtracted?.Invoke(this, e);
_panelIndex--;
UpdatePanelLocationUI();
DrawPanelLocationOverlay();
}
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;
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)
{
PlaneProfile.PanelSourceCoordinates.Add(new PanelSourceCoordinate() { PanelIndex = _panelIndex, X = e.X, Y = e.Y });
var newPanelCoordinates = new PanelSourceCoordinate() { PanelIndex = _panelIndex, X = e.X, Y = e.Y };
PanelCoordinates.Add(newPanelCoordinates);
OnPanelAdded?.Invoke(this, new EventArgs<PanelSourceCoordinate>(newPanelCoordinates));
WindowManager.AddPanelLocationSelectionOverlay(_panelIndex.ToString(), e.X, e.Y);
_panelIndex++;
}
UpdatePanelLocationUI();
DrawPanelLocationOverlay();
}
}
}
public void UpdatePanelLocationUI()
{
OnLocationListChanged?.Invoke(this, null);
DrawPanelLocationOverlay();
}
}
}

View file

@ -0,0 +1,473 @@
using MSFSPopoutPanelManager.Shared;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
using System.Threading.Tasks;
namespace MSFSPopoutPanelManager.Provider
{
public class PopoutSeparationManager
{
private const int RETRY_COUNT = 5;
private IntPtr _simulatorHandle;
private UserProfileData _profile;
private List<PanelConfig> _panels;
public PopoutSeparationManager(IntPtr simulatorHandle, UserProfileData profile)
{
_simulatorHandle = simulatorHandle;
_profile = profile;
_panels = new List<PanelConfig>();
}
public bool StartPopout()
{
// If enable, load the current viewport into custom view by Ctrl-Alt-0
if (FileManager.ReadAppSettingData().UseAutoPanning)
{
var simualatorProcess = WindowManager.GetSimulatorProcess();
if (simualatorProcess != null)
{
InputEmulationManager.LoadCustomViewZero(simualatorProcess.Handle);
Thread.Sleep(500);
}
}
Task<List<PanelConfig>> popoutPanelTask = Task<List<PanelConfig>>.Factory.StartNew(() =>
{
return ExecutePopoutSeparation();
});
popoutPanelTask.Wait();
var popoutReslts = popoutPanelTask.Result;
if (popoutReslts != null)
{
if (_profile.PanelConfigs.Count > 0)
{
LoadAndApplyPanelConfigs(popoutReslts);
Logger.Status("Panels have been popped out succesfully. Previously saved panel settings have been applied.", StatusMessageType.Info);
}
else
{
_profile.PanelConfigs = popoutReslts;
Logger.Status("Panels have been popped out succesfully. Please click 'Save Profile' once you're done making adjustment to the panels.", StatusMessageType.Info);
}
// If enable, center the view port by Ctrl-Space
if (FileManager.ReadAppSettingData().UseAutoPanning)
{
var simualatorProcess = WindowManager.GetSimulatorProcess();
if (simualatorProcess != null)
{
InputEmulationManager.CenterView(simualatorProcess.Handle);
Thread.Sleep(500);
}
}
return true;
}
return false;
}
public List<PanelConfig> ExecutePopoutSeparation()
{
_panels.Clear();
// Must close out all existing custom pop out panels
PInvoke.EnumWindows(new PInvoke.CallBack(EnumCustomPopoutCallBack), 0);
if(_panels.Count > 0)
{
Logger.BackgroundStatus("Please close all existing panel pop outs before continuing.", StatusMessageType.Error);
return null;
}
_panels.Clear();
PInvoke.SetForegroundWindow(_simulatorHandle);
try
{
for (var i = 0; i < _profile.PanelSourceCoordinates.Count; i++)
{
PopoutPanel(_profile.PanelSourceCoordinates[i].X, _profile.PanelSourceCoordinates[i].Y);
if (i == 0)
{
int retry = 0;
while (retry < RETRY_COUNT)
{
PInvoke.EnumWindows(new PInvoke.CallBack(EnumCustomPopoutCallBack), 0);
if (GetPopoutPanelCountByType(PanelType.CustomPopout) == 0)
retry += 1;
else
{
var panel = GetCustomPopoutPanelByIndex(i);
panel.PanelName = $"Panel{i + 1} (Custom)";
PInvoke.SetWindowText(panel.PanelHandle, panel.PanelName);
break;
}
}
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.");
}
if (i >= 1) // only separate with 2 or more panels
{
int retry = 0;
while (retry < RETRY_COUNT)
{
SeparatePanel(i, _panels[0].PanelHandle);
PInvoke.EnumWindows(new PInvoke.CallBack(EnumCustomPopoutCallBack), i);
if (GetPopoutPanelCountByType(PanelType.CustomPopout) != i + 1)
retry += 1;
else
{
// Panel has successfully popped out
var panel = GetCustomPopoutPanelByIndex(i);
PInvoke.MoveWindow(panel.PanelHandle, 0, 0, 800, 600, true);
panel.PanelName = $"Panel{i + 1} (Custom)";
PInvoke.SetWindowText(panel.PanelHandle, panel.PanelName);
break;
}
}
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.");
}
}
// 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 ");
// Add the built-in pop outs (ie. ATC, VFR Map) to the panel list
PInvoke.EnumWindows(new PInvoke.CallBack(EnumBuiltinPopoutCallBack), _profile.PanelSourceCoordinates.Count + 1);
// 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;
PInvoke.MoveWindow(_panels[i].PanelHandle, -8 + _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)
{
Logger.BackgroundStatus(ex.Message, StatusMessageType.Error);
return null;
}
catch
{
throw;
}
}
private void LoadAndApplyPanelConfigs(List<PanelConfig> popoutResults)
{
int index;
popoutResults.ForEach(resultPanel =>
{
if (resultPanel.PanelType == PanelType.CustomPopout)
{
index = _profile.PanelConfigs.FindIndex(x => x.PanelIndex == resultPanel.PanelIndex);
if (index > -1)
_profile.PanelConfigs[index].PanelHandle = resultPanel.PanelHandle;
}
else
{
index = _profile.PanelConfigs.FindIndex(x => x.PanelName == resultPanel.PanelName);
if (index > -1)
_profile.PanelConfigs[index].PanelHandle = resultPanel.PanelHandle;
else
_profile.PanelConfigs.Add(resultPanel);
}
});
_profile.PanelConfigs.RemoveAll(x => x.PanelHandle == IntPtr.Zero && x.PanelType == PanelType.BuiltInPopout);
//_profile.PanelSettings.ForEach(panel =>
Parallel.ForEach(_profile.PanelConfigs, panel =>
{
if (panel != null && panel.Width != 0 && panel.Height != 0)
{
// Apply panel name
if (panel.PanelType == PanelType.CustomPopout)
{
PInvoke.SetWindowText(panel.PanelHandle, panel.PanelName);
Thread.Sleep(500);
}
// Apply locations
PInvoke.MoveWindow(panel.PanelHandle, panel.Left, panel.Top, panel.Width, panel.Height, true);
Thread.Sleep(1000);
// Apply always on top
if (panel.AlwaysOnTop)
{
WindowManager.ApplyAlwaysOnTop(panel.PanelHandle, true, new Rectangle(panel.Left, panel.Top, panel.Width, panel.Height));
Thread.Sleep(1000);
}
// Apply hide title bar
if (panel.HideTitlebar)
{
WindowManager.ApplyHidePanelTitleBar(panel.PanelHandle, true);
}
}
});
}
private int GetPopoutPanelCountByType(PanelType panelType)
{
return _panels.FindAll(x => x.PanelType == panelType).Count;
}
private PanelConfig GetCustomPopoutPanelByIndex(int index)
{
return _panels.Find(x => x.PanelType == PanelType.CustomPopout && x.PanelIndex == index + 1);
}
private void PopoutPanel(int x, int y)
{
InputEmulationManager.LeftClick(x, y);
Thread.Sleep(200);
InputEmulationManager.PopOutPanel(x, y);
}
private void SeparatePanel(int index, IntPtr hwnd)
{
// Resize all windows to 800x600 when separating and shimmy the panel
// 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);
// Find the magnifying glass coordinate
var point = AnalyzeMergedWindows(hwnd);
InputEmulationManager.LeftClick(point.X, point.Y);
}
public bool EnumCustomPopoutCallBack(IntPtr hwnd, int index)
{
var panelInfo = GetPanelWindowInfo(hwnd);
if(panelInfo != null && panelInfo.PanelType == PanelType.CustomPopout)
{
if (!_panels.Exists(x => x.PanelHandle == hwnd))
{
panelInfo.PanelIndex = index + 1; // Panel index starts at 1
_panels.Add(panelInfo);
}
}
return true;
}
public bool EnumBuiltinPopoutCallBack(IntPtr hwnd, int index)
{
var panelInfo = GetPanelWindowInfo(hwnd);
if (panelInfo != null && panelInfo.PanelType == PanelType.BuiltInPopout)
{
if (!_panels.Exists(x => x.PanelHandle == hwnd))
{
panelInfo.PanelIndex = index;
_panels.Add(panelInfo);
}
}
return true;
}
private PanelConfig GetPanelWindowInfo(IntPtr hwnd)
{
var className = PInvoke.GetClassName(hwnd);
if (className == "AceApp") // MSFS windows designation
{
var caption = PInvoke.GetWindowText(hwnd);
var panelInfo = new PanelConfig();
panelInfo.PanelHandle = hwnd;
panelInfo.PanelName = caption;
if (String.IsNullOrEmpty(caption) || caption.IndexOf("Custom") > -1)
panelInfo.PanelType = PanelType.CustomPopout;
else if (caption.IndexOf("Microsoft Flight Simulator") > -1) // MSFS main game window
return null;
else
panelInfo.PanelType = PanelType.BuiltInPopout;
return panelInfo;
}
return null;
}
private Point AnalyzeMergedWindows(IntPtr hwnd)
{
var sourceImage = ImageOperation.TakeScreenShot(hwnd);
Rectangle rectangle;
PInvoke.GetClientRect(hwnd, out rectangle);
var panelMenubarTop = GetPanelMenubarTop(sourceImage, rectangle);
if (panelMenubarTop > sourceImage.Height)
return Point.Empty;
var panelMenubarBottom = GetPanelMenubarBottom(sourceImage, rectangle);
if (panelMenubarTop > sourceImage.Height)
return Point.Empty;
var panelsStartingLeft = GetPanelMenubarStartingLeft(sourceImage, rectangle, panelMenubarTop + 5);
// The center of magnifying glass icon is around (2.7 x height of menubar) to the right of the panel menubar starting left
// But need to use higher number here to click the left side of magnifying glass icon because on some panel, the ratio is smaller
var menubarHeight = panelMenubarBottom - panelMenubarTop;
var magnifyingIconXCoor = panelsStartingLeft - Convert.ToInt32(menubarHeight * 3.2); // ToDo: play around with this multiplier to find the best for all resolutions
var magnifyingIconYCoor = panelMenubarTop + Convert.ToInt32(menubarHeight / 2);
return new Point(magnifyingIconXCoor, magnifyingIconYCoor);
}
private int GetPanelMenubarTop(Bitmap sourceImage, Rectangle rectangle)
{
// Get a snippet of 1 pixel wide vertical strip of windows. We will choose the strip left of center.
// This is to determine when the actual panel's vertical pixel starts in the window. This will allow accurate sizing of the template image
var left = Convert.ToInt32((rectangle.Width) * 0.70); // look at around 70% from the left
var top = sourceImage.Height - rectangle.Height;
if (top < 0 || left < 0)
return -1;
unsafe
{
var stripData = sourceImage.LockBits(new Rectangle(left, top, 1, rectangle.Height), ImageLockMode.ReadWrite, sourceImage.PixelFormat);
int bytesPerPixel = Bitmap.GetPixelFormatSize(stripData.PixelFormat) / 8;
int heightInPixels = stripData.Height;
int widthInBytes = stripData.Width * bytesPerPixel;
byte* ptrFirstPixel = (byte*)stripData.Scan0;
for (int y = 0; y < heightInPixels; y++)
{
byte* currentLine = ptrFirstPixel + (y * stripData.Stride);
for (int x = 0; x < widthInBytes; x = x + bytesPerPixel)
{
int red = currentLine[x + 2];
int green = currentLine[x + 1];
int blue = currentLine[x];
if (red == 255 && green == 255 && blue == 255)
{
sourceImage.UnlockBits(stripData);
return y + top;
}
}
}
sourceImage.UnlockBits(stripData);
}
return -1;
}
private int GetPanelMenubarBottom(Bitmap sourceImage, Rectangle rectangle)
{
// Get a snippet of 1 pixel wide vertical strip of windows. We will choose the strip about 25% from the left of the window
var left = Convert.ToInt32((rectangle.Width) * 0.25); // look at around 25% from the left
var top = sourceImage.Height - rectangle.Height;
if (top < 0 || left < 0)
return -1;
unsafe
{
var stripData = sourceImage.LockBits(new Rectangle(left, top, 1, rectangle.Height), ImageLockMode.ReadWrite, sourceImage.PixelFormat);
int bytesPerPixel = Bitmap.GetPixelFormatSize(stripData.PixelFormat) / 8;
int heightInPixels = stripData.Height;
int widthInBytes = stripData.Width * bytesPerPixel;
byte* ptrFirstPixel = (byte*)stripData.Scan0;
int menubarBottom = -1;
for (int y = 0; y < heightInPixels; y++)
{
byte* currentLine = ptrFirstPixel + (y * stripData.Stride);
for (int x = 0; x < widthInBytes; x = x + bytesPerPixel)
{
int red = currentLine[x + 2];
int green = currentLine[x + 1];
int blue = currentLine[x];
if (red == 255 && green == 255 && blue == 255)
{
// found the top of menu bar
menubarBottom = y + top;
}
else if(menubarBottom > -1) /// it is no longer white in color, we hit menubar bottom
{
sourceImage.UnlockBits(stripData);
return menubarBottom;
}
}
}
sourceImage.UnlockBits(stripData);
}
return -1;
}
private int GetPanelMenubarStartingLeft(Bitmap sourceImage, Rectangle rectangle, int top)
{
unsafe
{
var stripData = sourceImage.LockBits(new Rectangle(0, top, rectangle.Width, 1), ImageLockMode.ReadWrite, sourceImage.PixelFormat);
int bytesPerPixel = Bitmap.GetPixelFormatSize(stripData.PixelFormat) / 8;
int widthInPixels = stripData.Width;
int heightInBytes = stripData.Height * bytesPerPixel;
byte* ptrFirstPixel = (byte*)stripData.Scan0;
for (int x = 0; x < widthInPixels; x++)
{
byte* currentLine = ptrFirstPixel - (x * bytesPerPixel);
for (int y = 0; y < heightInBytes; y = y + bytesPerPixel)
{
int red = currentLine[y + 2];
int green = currentLine[y + 1];
int blue = currentLine[y];
if (red == 255 && green == 255 && blue == 255)
{
sourceImage.UnlockBits(stripData);
return sourceImage.Width - x;
}
}
}
sourceImage.UnlockBits(stripData);
}
return -1;
}
}
}

87
Provider/WindowManager.cs Normal file
View file

@ -0,0 +1,87 @@
using MSFSPopoutPanelManager.Shared;
using MSFSPopoutPanelManager.UI;
using System;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager.Provider
{
public class WindowManager
{
private const int SWP_NOMOVE = 0x0002;
private const int SWP_NOSIZE = 0x0001;
private const int SWP_ALWAYS_ON_TOP = SWP_NOMOVE | SWP_NOSIZE;
private const int GWL_STYLE = -16;
private const int WS_SIZEBOX = 0x00040000;
private const int WS_BORDER = 0x00800000;
private const int WS_DLGFRAME = 0x00400000;
private const int WS_CAPTION = WS_BORDER | WS_DLGFRAME;
private const int HWND_TOPMOST = -1;
private const int HWND_NOTOPMOST = -2;
private const uint WM_CLOSE = 0x0010;
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, GWL_STYLE).ToInt64();
if (hideTitleBar)
PInvoke.SetWindowLong(handle, GWL_STYLE, (uint)(currentStyle & ~(WS_CAPTION | WS_SIZEBOX)));
else
PInvoke.SetWindowLong(handle, GWL_STYLE, (uint)(currentStyle | (WS_CAPTION | WS_SIZEBOX)));
}
public static void ApplyAlwaysOnTop(IntPtr handle, bool alwaysOnTop, Rectangle panelRectangle)
{
if (alwaysOnTop)
PInvoke.SetWindowPos(handle, HWND_TOPMOST, panelRectangle.Left, panelRectangle.Top, panelRectangle.Width, panelRectangle.Height, SWP_ALWAYS_ON_TOP);
else
PInvoke.SetWindowPos(handle, HWND_NOTOPMOST, panelRectangle.Left, panelRectangle.Top, panelRectangle.Width, panelRectangle.Height, 0);
}
public static void CloseWindow(IntPtr handle)
{
PInvoke.SendMessage(handle, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}
public static WindowProcess GetSimulatorProcess()
{
return GetProcess("FlightSimulator");
}
public static WindowProcess GetApplicationProcess()
{
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;
}
}
}

137
README.md
View file

@ -1,27 +1,34 @@
# MSFS Pop Out Panel Manager
MSFS Pop Out Panel Manager is an application for MSFS 2020 which helps pop out, save and re-position pop out panels such as PDF and MFD to be used by applications such as Sim Innovations Air Manager's overlay. This is a **PROOF OF CONCEPT** application (to also be used by experimental feature in my other github development project - MSFS Touch Panel). I welcome any 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 anything you created. But please abide by licensing and make it open source:)
MSFS Pop Out Panel Manager is an application for MSFS 2020 which helps pop out, save and re-position pop out panels to be used by applications such as Sim Innovations Air Manager or to place pop out panels onto another monitor automatically.
## Pop Out Panel Positioning Annoyance
In MSFS, by holding **RIGHT ALT** + **LEFT CLICKING** some instrumentation panels, these panels will pop out as floating windows that can be moved to a different monitor. But this needs to be done every time you start a new flight, ALT-RIGHT clicking, split out child windows, move these windows to final location, rinse and repeat. For predefined toolbar menu windows such as ATC, Checklist, VFR Map, their positions can be saved easily and reposition at the start of each new flight using 3rd party windows positioning tool because these windows have a **TITLE** in the title bar when they are popped out. But panels such as PFD and MFD on G1000 or the multi control panel on G3000 or panels on A320/CJ4 do not have window title. This makes remembering their last used position more difficult and it seems very annoying to resize and readjust their positions to be used by Air Manager or other overlay tool on each new flight.
[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.0 NEW FEATURES!
* 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, realtime readout as you position panels, a more intuitive user interface and status messages.
* Technical: Rewritten code base to improve code structure and performance.
## History: Pop Out Panel Positioning Annoyance
In MSFS, by holding **RIGHT ALT** + **LEFT CLICKING** some instrumentation panels, these panels will pop out as floating windows that can be moved to a different monitor. But this needs to be done every time you start a new flight, ALT-RIGHT clicking, split out child windows, move these windows to final location, rinse and repeat. For predefined toolbar menu windows such as ATC, Checklist, VFR Map, their positions can be saved easily and reposition at the start of each new flight using 3rd party windows positioning tool because these windows have a **TITLE** in the title bar when they are popped out. But any custom popouts such as PFD and MFD do not have window title. This makes remembering their last used position more difficult and it seems very annoying to resize and re-adjust their positions to be used by Air Manager or other overlay tool on each new flight.
## Concepts of the Application
What if you can do the setup once by defining on screen where the pop out windows will be, click a button, and the application will figure out everything for you. Then you just need to drag these pop out windows to their final desire location (again doing it once) on screen and it will save their final positions. Next time when you start a flight, you just re-position your initial game screen pop out coordinates and click a button and Viola, everything will be done for you. No ALT-Right clicking, no splitting windows manually (imaging you just pop out 7 windows for A32NX), no re-positioning windows to their final destination on screen. The pop outs will even have a title for you to use if you prefer using PowerToys fancy zone to position the windows yourself.
What if you can do the setup once by defining on screen where the pop out panels will be, click a button, and the application will pop these panels out and separate them for you. Then you just need to move these panels to their final positions. Next time when you start a flight, with a single button click, your panels will automatically pop out for you and move to their preconfigured positions. Easy peasy!
Version 1.0 of this application uses OCR (text recognition) technology to try to determine what pop out window is what. It works for some windows but not others. As one of the user in Flightsimulator.com forum asked, can the application pop out windows from A32NX where some of the windows have no text. Also, this version does not do auto pop out. So I scraped that idea of improving OCR and think about using something else instead.
Before v3.0 of the application, heavy image recognition was used to figure out how to pop out and separate the panels, figure out which panel is which by plane type and configure them accordingly. Although, the image recognition is reasonably accurate, there is lot to be desired. Especially on Cold Start, image recognition will not work at all because all panels are black to start with.
Version 2.0 uses image recognition and it seems to do a pretty decent job in popping out panels and figure out what window is what. I setup very simple image recognition data (aka. mostly non-scale image match for now) for most planes in MSFS and tested it mainly on 3440x1440 and 1920x1080 resolution. Regular 1440p should work as expected. 4K resolution may or may not work since I don't have a 4K screen to test but I scaled up some of the image recognition data for 4K so it may work! The image recognition uses a confident score to determine what the pop out window most likely to be.
With v3.0, redesign from the ground up about how to pop out and separate the panels and navigate around many of Asobo's bug, many of the image recognition code is now removed and replaced by pixel calculation algorithm to figure out how to separate panels accurately when they're being popped out in all display resolutions. Also, there is no longer the need to identify what panel is what since everything is done by pop out sequence.
The v2.0 application concept is as follow:
1. First allow user to define where all the 'ALT-Right Click' panels on the screen will be for a given plane profile and save these information.
2. Using image recognition, the application figures out how to pop out these panels and also do the clicking for you.
3. Once the pop out panels are split out, the application will start image recognition to determine the type and content for each panel. It also add title bar to these pop out window. Built-in toolbar panel such as ATC, VFR Map will work too.
4. After pop out panels are analyzed, the user will move these panels to their desire locations. An UI will also allow the user to resize and position these windows to pixel perfect location.
5. User will then save these data and start flying.
6. On subsequent flight, user will just need to reposition the definition of the 'ALT-Right Click' panels and the application will do the rest.
## How to Use?
[Here](images/doc/userguide.mp4) is a video of what the app will do.
[Here](images/doc/userguide.mp4) is a video of what the app will do. TBD: will get update to show v3.0.
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.
@ -29,104 +36,92 @@ The v2.0 application concept is as follow:
<img src="images/doc/frameworkdownload.png" width="1000" hspace="10"/>
</p>
2. Once the game starts and you're at the beginning of flight, first select a plane profile (for example A32NX by FlybyWire)
3. You then click "START PANEL SELECTION" to define where the pop out panels will be using LEFT CLICK and CTRL-LEFT CLICK when done to save these information.
2. First create a new plane profile (for example A32NX by FlybyWire)
<p align="center">
<img src="images/doc/screenshot2.png" width="1000" hspace="10"/>
<img src="images/doc/v3.0/s5.png" width="600" hspace="10"/>
</p>
4. Make sure all panels are ON and active. The application's image recognition engine will not be able to analyze and distinguish blank panel screens.
5. Now, click "ANALYZE". **!!!IMPORTANT, please make sure there are no other window obstructing pop out coordinates (where the numbers with circle are). Also, please DO NOT move your mouse during this time since the application is simulating mouse movements.** At last, please be patient (even using multi-thread, the execution of mouse movement will still take some time). The application will start popping out windows one by one and you will see a lot of windows movement on the screen. If something goes wrong, just close all the pop outs and try do "ANALYZE" again.
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.
<p align="center">
<img src="images/doc/seperation_analysis.png" width="1000" hspace="10"/>
<img src="images/doc/v3.0/s1.png" width="1000" hspace="10"/>
</p>
6. Once analysis is completed, you will see a list of panels that have successfully processed by the application.
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.
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 give a default name. You can name them anything you want if desire. Please go ahead and click "Save Profile". This will save the initial pop out configuration for this profile.
<p align="center">
<img src="images/doc/screenshot6.png" width="1000" hspace="10"/>
<img src="images/doc/v3.0/s2.png" width="1000" hspace="10"/>
</p>
7. Please go ahead and drag the pop out panels into their final positions. You can also directly type in coordinates for these panels to move and resize them and then click "APPLY SETTINGS". Please click "SAVE SETTINGS" to update the grid data if you use the mouse to drag the panels. Here is an example when panels are dragged to a secondary monitor and click "SAVE SETTINGS".
6. Now, start the panel configruation 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 "Save Profile" again.
<p align="center">
<img src="images/doc/screenshot7.png" width="1000" hspace="10"/>
<img src="images/doc/v3.0/s3.png" width="600" hspace="10"/>
</p>
7. You can also select "Panels always on top" and/or "Hide panel title bar" if desire. !! IMPORTANT, panels without title bar cannot be moved. Please just unchecked "Hide panel title bar" then "Apply Settings" to get the panel's title bar back to move them.
8. Once all the panels are at their final positions, click "SAVE SETTINGS" again and you're done. You can now close or minimize the application.
9. On subsequent flight, just reposition the pop out position for the selected plane profile and click "ANALYZE" and see the magic happens. Screenshots below used FBW A32NX as example since some of the pop outs were hidden when the flight starts.
<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". This will close all the pop out (except the main menu bar ones) 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".
<p align="center">
<img src="images/doc/screenshot3.png" width="1000" hspace="10"/>
</p>
Move your screen down a little bit by holding Right-Click in flight simulator until everything lines up. You can then click "ANALYZE".
<p align="center">
<img src="images/doc/screenshot4.png" width="1000" hspace="10"/>
</p>
10. Since the initial pop out positions may be different from plane to plane even when they are using the same instrumentation system, you can easily add new profiles in a configuration file and points it to the same analysisTemplateName. You can edit the configuration file [planeprofile.json](Config/planeprofile.json) in the **config** folder of the application to add additional profile. Look for profileId 1 and 2 in the file as example where both Cessna 172 and DA62 both uses Working Title G1000 NXi.
## Add or Delete Plane Profile
## User Profile Data Files
The ability to add or delete plane profile is added starting in version 2.1 of the application.
The user plane profile data and application settings data are stored as JSON files under the following folder path.
<p align="center">
<img src="images/doc/screenshot9.png" width="600" hspace="10"/>
</p>
* userdata/userprofiledata.json
* userdata/appsettingdata.json
* For any existing built-in profile, you can delete them if they're not being used to reduce clutter. You can recreate them later by adding a new profile and selecting a built-in predefined image analysis template for the instrumentation set for that plane.
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 tranfer it over to the new file JSON attribute of "PanelConfigs". Please edit at your own risk.
* To add a new profile for plane either by using built-in image recognition data for predefined instrumentation set or create a new one, please click "Add profile" button.
<p align="center">
<img src="images/doc/screenshot8.png" width="600" hspace="10"/>
</p>
## Current Known Issue
1. Please specify a profile name and use alphanumeric characters only. The profile name will also be used to create windows folder for image recognition data.
* You may encounter a bug with MSFS where a pop out panel is not Right-Alt clickable or the same panel is able to be popped out twice. This causes the application to not function properly. A new MSFS bug introduced since SU7 which I haven't encountered before?
* 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 coordiates. 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 differnt 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.
2. Select either predefined image recognition analysis template or "New" for custom template. By selecting "New", the application will create custom image recognition data from the pop out panels.
3. Select "OK" when done. You should see the new profile displayed in the application. Now perform the same procedure to select pop out panel location in the game and click "Analyze".
4. Once analysis is completed, you should see the following if using a "New" template. The panel name will be generic such as "Panel 1", "Panel 2", and so forth. They work the same as built-in predefined templates. Continue to move pop out panels to their final screen locations and click "Save Settings".
<p align="center">
<img src="images/doc/screenshot10.png" width="600" hspace="10"/>
</p>
5. On next flight, your custom user profile will be available for selection.
## User Plane Profile Data
The user created plane profile and associate image recognition data are stored in the following folders in the application.
* Config/customplaneprofile.json - the list of user created plane profile.
* Config/AnalysisData/customanalysisconfig.json - define the location for custom plane profile image recognition data.
* Config/AnalsysData/User - XXXXXX - folders where custom image recognition data is stored for user created plane profile.
## Common Problem Resolution
- Unable to pop out windows correctly - the predefined pop out panel coordinate may not line up correctly or movement of mouse is interfering with pop out execution. Please try to reposition the screen into pop out coordinates. Or you can close and restart the application, close all the opened pop outs, and try the analysis again.
- Pop out windows are not recognized correctly - it is limitation of current implementation of image recognition algorithm. More sophisticated image recognition such as [SIFT](https://en.wikipedia.org/wiki/Scale-invariant_feature_transform) will be needed. This is to-do item on my list for future version of app. Also, the panel screen maybe blank which causes the image recognition engine to fail.
- Night time or different world location causes image recognition to fail - application has builtin redundancy image recognition data for this purpose (such as MFD recognition for G1000 where 75% of the screen is a map). But I may not have anticipate all the use cases yet. Please provide feedback and it will help me to improve the image recognition engine.
* Unable to pop out panels when creating a profile for the first time with error such as "Unabled 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 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 thie by manually line up the panel circles identifier and do a force save view by pressing Ctrl-Alt-0.
* 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.
## Author
Stanley Kwok
[hawkeyesk@outlook.com](mailto:hawkeyesk@outlook.com)
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.
[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
[DarkUI](http://www.darkui.com/) by Robin Perria

19
Shared/AppSettingData.cs Normal file
View file

@ -0,0 +1,19 @@
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; }
}
}

View file

@ -5,7 +5,7 @@ using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace MSFSPopoutPanelManager
namespace MSFSPopoutPanelManager.Shared
{
public class Autostart
{
@ -75,8 +75,6 @@ namespace MSFSPopoutPanelManager
public static void Deactivate()
{
//Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var filePath = GetFilePath();
if (filePath != null && File.Exists(filePath))

9
Shared/Enums.cs Normal file
View file

@ -0,0 +1,9 @@
namespace MSFSPopoutPanelManager.Shared
{
public enum PanelType
{
FlightSimMainWindow,
BuiltInPopout,
CustomPopout
}
}

14
Shared/EventArgs.cs Normal file
View file

@ -0,0 +1,14 @@
using System;
namespace MSFSPopoutPanelManager.Shared
{
public class EventArgs<T> : EventArgs
{
public T Value { get; private set; }
public EventArgs(T val)
{
Value = val;
}
}
}

34
Shared/ImageOperation.cs Normal file
View file

@ -0,0 +1,34 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
namespace MSFSPopoutPanelManager.Shared
{
public class ImageOperation
{
public static Bitmap TakeScreenShot(IntPtr windowHandle)
{
// Set window to foreground so nothing can hide the window
PInvoke.SetForegroundWindow(windowHandle);
Thread.Sleep(300);
Rectangle rectangle;
PInvoke.GetWindowRect(windowHandle, out rectangle);
var left = rectangle.Left;
var top = rectangle.Top;
var width = rectangle.Right - rectangle.Left;
var height = rectangle.Bottom - rectangle.Top;
var bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb);
using (Graphics g = Graphics.FromImage(bmp))
{
g.CopyFromScreen(new Point(left, top), Point.Empty, rectangle.Size);
}
return bmp;
}
}
}

50
Shared/Logger.cs Normal file
View file

@ -0,0 +1,50 @@
using System;
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)
{
var statusMessage = new StatusMessage() { Message = message, MessageType = MessageType };
OnStatusLogged?.Invoke(null, new EventArgs<StatusMessage>(statusMessage));
}
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);
}
}
public class StatusMessage
{
public string Message { get; set; }
public StatusMessageType MessageType { get; set; }
}
public enum StatusMessageType
{
Info,
Error
}
public class PopoutManagerException : Exception
{
public PopoutManagerException(string message) : base(message) { }
}
}

View file

@ -1,46 +1,47 @@
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
namespace MSFSPopoutPanelManager
namespace MSFSPopoutPanelManager.Shared
{
public class PInvoke
{
[DllImport("user32.dll")]
public static extern bool ClientToScreen(IntPtr hWnd, ref System.Drawing.Point lpPoint);
[DllImport("user32.dll")]
public static extern IntPtr CreateWindowEx(int dwExStyle, string lpClassName, string lpWindowName, int dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DestroyWindow(IntPtr hwnd);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr lParam);
[DllImport("user32")]
public static extern int EnumWindows(CallBack x, int y);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int GetClassName(IntPtr hWnd, StringBuilder strPtrClassName, Int32 nMaxCount);
private static extern int GetClassName(IntPtr hWnd, StringBuilder strPtrClassName, Int32 nMaxCount);
public static string GetClassName(IntPtr hwnd)
{
StringBuilder sb = new StringBuilder(255);
GetClassName(hwnd, sb, sb.Capacity);
return sb.ToString();
}
[DllImport("user32.dll")]
public static extern bool GetClientRect(IntPtr hWnd, out Rect lpRect);
public static extern bool GetClientRect(IntPtr hWnd, out Rectangle lpRect);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetCursorPos(out POINT lpPoint);
public static extern bool GetCursorPos(out Point lpPoint);
[DllImport("user32.dll")]
public static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
public static extern int GetWindowRect(IntPtr hwnd, out Rect lpRect);
public static extern int GetWindowRect(IntPtr hwnd, out Rectangle lpRect);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpWindowText, int nMaxCount);
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpWindowText, int nMaxCount);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
public static string GetWindowText(IntPtr hwnd)
{
StringBuilder sb = new StringBuilder(255);
GetWindowText(hwnd, sb, sb.Capacity);
return sb.ToString();
}
[DllImport("user32.dll")]
public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, int dwExtraInfo);
@ -54,11 +55,14 @@ namespace MSFSPopoutPanelManager
[DllImport("User32.dll")]
public static extern bool SetCursorPos(int X, int Y);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetFocus(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);
[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 int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong);
@ -69,10 +73,14 @@ namespace MSFSPopoutPanelManager
[DllImport("user32.dll")]
public static extern bool SetWindowText(System.IntPtr hwnd, System.String lpString);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventProc lpfnWinEventProc, int idProcess, int idThread, uint dwflags);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
public static extern int UnhookWinEvent(IntPtr hWinEventHook);
public delegate bool EnumWindowProc(IntPtr hwnd, IntPtr lParam);
public delegate bool CallBack(IntPtr hwnd, int lParam);
public delegate void WinEventProc(IntPtr hWinEventHook, uint iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime);
}
}

58
Shared/UserProfileData.cs Normal file
View file

@ -0,0 +1,58 @@
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>();
}
public int ProfileId { get; set; }
public string ProfileName { get; set; }
public bool IsDefaultProfile { get; set; }
public List<PanelSourceCoordinate> PanelSourceCoordinates;
public List<PanelConfig> PanelConfigs { get; set; }
}
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,21 +1,13 @@
using System;
using System.Collections.Generic;
namespace MSFSPopoutPanelManager
{
public class WindowProcess
{
public WindowProcess()
{
ChildWindows = new List<ChildWindow>();
}
public int ProcessId { get; set; }
public string ProcessName { get; set; }
public IntPtr Handle { get; set; }
public List<ChildWindow> ChildWindows { get; set; }
}
}

View file

@ -1,5 +1,5 @@

namespace MSFSPopoutPanelManager
namespace MSFSPopoutPanelManager.UI
{
partial class AddProfileForm
{
@ -29,25 +29,12 @@ namespace MSFSPopoutPanelManager
/// </summary>
private void InitializeComponent()
{
this.darkLabel1 = new DarkUI.Controls.DarkLabel();
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.comboBoxTemplates = new DarkUI.Controls.DarkComboBox();
this.SuspendLayout();
//
// darkLabel1
//
this.darkLabel1.AutoSize = true;
this.darkLabel1.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.darkLabel1.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.darkLabel1.Location = new System.Drawing.Point(33, 83);
this.darkLabel1.Name = "darkLabel1";
this.darkLabel1.Size = new System.Drawing.Size(128, 20);
this.darkLabel1.TabIndex = 1;
this.darkLabel1.Text = "Analysis Template";
//
// darkLabel2
//
this.darkLabel2.AutoSize = true;
@ -79,7 +66,7 @@ namespace MSFSPopoutPanelManager
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, 141);
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;
@ -92,7 +79,7 @@ namespace MSFSPopoutPanelManager
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, 141);
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;
@ -100,46 +87,34 @@ namespace MSFSPopoutPanelManager
this.buttonCancel.UseVisualStyleBackColor = false;
this.buttonCancel.Click += new System.EventHandler(this.buttonCancel_Click);
//
// comboBoxTemplates
//
this.comboBoxTemplates.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable;
this.comboBoxTemplates.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.comboBoxTemplates.FormattingEnabled = true;
this.comboBoxTemplates.Location = new System.Drawing.Point(177, 79);
this.comboBoxTemplates.Name = "comboBoxTemplates";
this.comboBoxTemplates.Size = new System.Drawing.Size(356, 28);
this.comboBoxTemplates.TabIndex = 24;
//
// 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, 205);
this.ClientSize = new System.Drawing.Size(562, 173);
this.ControlBox = false;
this.Controls.Add(this.comboBoxTemplates);
this.Controls.Add(this.buttonCancel);
this.Controls.Add(this.buttonOK);
this.Controls.Add(this.textBoxProfileName);
this.Controls.Add(this.darkLabel2);
this.Controls.Add(this.darkLabel1);
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 darkLabel1;
private DarkUI.Controls.DarkLabel darkLabel2;
private DarkUI.Controls.DarkTextBox textBoxProfileName;
private System.Windows.Forms.Button buttonOK;
private System.Windows.Forms.Button buttonCancel;
private DarkUI.Controls.DarkComboBox comboBoxTemplates;
}
}

View file

@ -1,25 +1,19 @@
using DarkUI.Forms;
using MSFSPopoutPanelManager.UIController;
using System;
using System.Data;
using System.Linq;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager
namespace MSFSPopoutPanelManager.UI
{
public partial class AddProfileForm : DarkForm
{
private PanelManager _panelManager;
public event EventHandler<EventArgs<int>> OnAddProfile;
public AddProfileForm(PanelManager panelManager)
public AddProfileForm()
{
InitializeComponent();
_panelManager = panelManager;
SetTemplateDropDown();
}
public string ProfileName { get { return textBoxProfileName.Text.Trim(); } }
private void textBoxProfileName_KeyPress(object sender, KeyPressEventArgs e)
{
e.Handled = !(Char.IsLetterOrDigit(e.KeyChar) ||
@ -33,37 +27,16 @@ namespace MSFSPopoutPanelManager
buttonOK.Enabled = textBoxProfileName.Text.Trim().Length > 0;
}
public void SetTemplateDropDown()
{
try
{
var templates = FileManager.ReadAllAnalysisTemplateData();
var templateNames = templates.OrderBy(x => x.TemplateName).Select(x => x.TemplateName).Distinct().ToList();
templateNames.Insert(0, "New");
comboBoxTemplates.DataSource = templateNames;
}
catch (Exception ex)
{
Logger.LogStatus(ex.Message);
}
}
private void buttonCancel_Click(object sender, EventArgs e)
{
this.Close();
DialogResult = DialogResult.Cancel;
Close();
}
private void buttonOK_Click(object sender, EventArgs e)
{
var profileName = textBoxProfileName.Text.Trim();
var analysisTemplateName = Convert.ToString(comboBoxTemplates.SelectedValue);
var profileId = _panelManager.AddUserProfile(profileName, analysisTemplateName);
OnAddProfile?.Invoke(this, new EventArgs<int>(profileId));
this.Close();
DialogResult = DialogResult.OK;
Close();
}
}
}
}

119
UI/ConfirmDialogForm.Designer.cs generated Normal file
View file

@ -0,0 +1,119 @@

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;
}
}

30
UI/ConfirmDialogForm.cs Normal file
View file

@ -0,0 +1,30 @@
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();
}
}
}

60
UI/ConfirmDialogForm.resx Normal file
View file

@ -0,0 +1,60 @@
<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,5 +1,5 @@

namespace MSFSPopoutPanelManager
namespace MSFSPopoutPanelManager.UI
{
partial class PopoutCoorOverlayForm
{
@ -37,13 +37,13 @@ namespace MSFSPopoutPanelManager
//
this.lblPanelIndex.BackColor = System.Drawing.Color.Transparent;
this.lblPanelIndex.CausesValidation = false;
this.lblPanelIndex.Font = new System.Drawing.Font("Segoe UI", 24F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
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(62, 44);
this.lblPanelIndex.Size = new System.Drawing.Size(55, 46);
this.lblPanelIndex.TabIndex = 0;
this.lblPanelIndex.Text = "1";
this.lblPanelIndex.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
@ -53,7 +53,7 @@ namespace MSFSPopoutPanelManager
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CausesValidation = false;
this.ClientSize = new System.Drawing.Size(62, 44);
this.ClientSize = new System.Drawing.Size(55, 46);
this.ControlBox = false;
this.Controls.Add(this.lblPanelIndex);
this.DoubleBuffered = true;

View file

@ -1,14 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager
namespace MSFSPopoutPanelManager.UI
{
public partial class PopoutCoorOverlayForm : Form
{

View file

@ -60,22 +60,21 @@
<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>
iVBORw0KGgoAAAANSUhEUgAAAD4AAAAsCAYAAAA93wvUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAMkSURBVGhD5drNrgxBGMbxQ7Cw9LETa86ORBDchs8tFtwA
V8ANCNYkLGyESAg3wZoFZ4Vj5zsynn8yb3JSefp0V3X19NBv8ksmb71TVceM7qqpXpnNZpNkk1Ngk1Ng
kxVslaNyTR7Ja1mXH3O8JkcbNdTyHtfXIGyyh31yQ95LbvAe3ksfru+qbLLAbrklPyXirdyWC3JYqNkx
x2tytFHzTiLog76ocWNVYZOZTssnIf7IQzkurrbJFjkpD4Q+CPo8I66+N5vsaJvwaUW8klVxtTnog74i
GIOxXG0xm+xgpzwR4rtcFVfXxxWhb4KxGNPVFbHJFvzrPxbis3BFdnU10DdjEIxZ7ZO3yRZ3hPgoB8TV
1MQYjEUwtqvJZpOb4GJDfJMhP+kUY30V4py4miw22YDbS1y9L4urGRJjEix+9oqr6cwmG8QV/IW49kWI
q33vr7xNGvvll/yWg/PcGBibOTAX5uRqOrFJg6UkcU9c+yLdF4I5ufZObDLB5uGDECfE1SzSKSGYU/HG
xiYTXFEJ1tMsLV3NIjGHWNsX31lsMnFdiGr30ApiLcGW1rW3sskEe2aCnZRrHwNzIZiba29lk4k3QhwS
1z4G5kIwN9feyiYTsVYedH+cibkQzM21t7LJRPy4wA8Irn0MzIVgbq69lU0mJvuHL+NXfZcQg37VJ3tx
m+ztLBYw7M5c+xhipzjoAuaYEJNbsi7bJoU5EINvUrBM21J+eycG35aCYx3umcvwQwQHDsyl11GTTTbg
WId4Lq59EV4K0ftCa5MNWMDEYuaSuJohMSbBHHovpmxyE2eF4KfesX5ePi+uJotNthjzQOGuuJpsNtmC
DcLGI6Qj4upqoO/478WY1TZKNtkBB3jPhOBg76K4uj7okxMb4qmMfmgYtkt87QmuuDWOibllxdWbYAzG
crXFbDITF7yNDwawwCh5MIAVWfpgQJVzMscmC+wR7q3xowURj4JwFWYbyS2ITw68JkcbNemjIOTo041V
hU32wGrqpqxJbvAe3vtPPfyTYvPAvZctLXtmHu36Ipx5gdfxuBc11BZvOErY5P9vtvIXQ8rHsl8eoTUA
AAAASUVORK5CYII=
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,5 +1,5 @@

namespace MSFSPopoutPanelManager
namespace MSFSPopoutPanelManager.UI
{
partial class StartupForm
{
@ -33,7 +33,7 @@ namespace MSFSPopoutPanelManager
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(StartupForm));
this.panelSteps = new System.Windows.Forms.Panel();
this.linkLabel1 = new System.Windows.Forms.LinkLabel();
this.labelMsfsRunning = new System.Windows.Forms.Label();
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();
@ -43,6 +43,8 @@ namespace MSFSPopoutPanelManager
this.checkBoxMinimizeToTray = new DarkUI.Controls.DarkCheckBox();
this.lblVersion = new DarkUI.Controls.DarkLabel();
this.checkBoxAutoStart = new DarkUI.Controls.DarkCheckBox();
this.checkBoxAutoPanning = new DarkUI.Controls.DarkCheckBox();
this.checkBoxAlwaysOnTop = new DarkUI.Controls.DarkCheckBox();
this.panelStatus.SuspendLayout();
this.SuspendLayout();
//
@ -51,7 +53,7 @@ namespace MSFSPopoutPanelManager
this.panelSteps.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(61)))), ((int)(((byte)(101)))), ((int)(((byte)(171)))));
this.panelSteps.Location = new System.Drawing.Point(0, 64);
this.panelSteps.Name = "panelSteps";
this.panelSteps.Size = new System.Drawing.Size(862, 405);
this.panelSteps.Size = new System.Drawing.Size(915, 405);
this.panelSteps.TabIndex = 0;
//
// linkLabel1
@ -68,27 +70,29 @@ namespace MSFSPopoutPanelManager
this.linkLabel1.VisitedLinkColor = System.Drawing.Color.FromArgb(((int)(((byte)(128)))), ((int)(((byte)(255)))), ((int)(((byte)(255)))));
this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabel1_LinkClicked);
//
// labelMsfsRunning
// labelMsfsConnection
//
this.labelMsfsRunning.AutoSize = true;
this.labelMsfsRunning.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.labelMsfsRunning.ForeColor = System.Drawing.Color.Red;
this.labelMsfsRunning.Location = new System.Drawing.Point(704, 546);
this.labelMsfsRunning.Name = "labelMsfsRunning";
this.labelMsfsRunning.RightToLeft = System.Windows.Forms.RightToLeft.Yes;
this.labelMsfsRunning.Size = new System.Drawing.Size(143, 20);
this.labelMsfsRunning.TabIndex = 10;
this.labelMsfsRunning.Text = "MSFS is not Running";
this.labelMsfsRunning.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
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(766, 546);
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.Enabled = false;
this.panelStatus.Location = new System.Drawing.Point(0, 469);
this.panelStatus.Name = "panelStatus";
this.panelStatus.Size = new System.Drawing.Size(860, 74);
this.panelStatus.Size = new System.Drawing.Size(915, 74);
this.panelStatus.TabIndex = 20;
//
// darkLabel3
@ -112,7 +116,7 @@ namespace MSFSPopoutPanelManager
this.txtBoxStatus.Multiline = true;
this.txtBoxStatus.Name = "txtBoxStatus";
this.txtBoxStatus.ReadOnly = true;
this.txtBoxStatus.Size = new System.Drawing.Size(780, 46);
this.txtBoxStatus.Size = new System.Drawing.Size(835, 46);
this.txtBoxStatus.TabIndex = 23;
//
// notifyIcon1
@ -148,7 +152,7 @@ namespace MSFSPopoutPanelManager
//
this.checkBoxMinimizeToTray.AutoSize = true;
this.checkBoxMinimizeToTray.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.checkBoxMinimizeToTray.Location = new System.Drawing.Point(13, 546);
this.checkBoxMinimizeToTray.Location = new System.Drawing.Point(13, 545);
this.checkBoxMinimizeToTray.Name = "checkBoxMinimizeToTray";
this.checkBoxMinimizeToTray.Size = new System.Drawing.Size(189, 24);
this.checkBoxMinimizeToTray.TabIndex = 23;
@ -158,7 +162,7 @@ namespace MSFSPopoutPanelManager
//
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(383, 572);
this.lblVersion.Location = new System.Drawing.Point(423, 591);
this.lblVersion.Name = "lblVersion";
this.lblVersion.Size = new System.Drawing.Size(48, 15);
this.lblVersion.TabIndex = 24;
@ -168,25 +172,46 @@ namespace MSFSPopoutPanelManager
//
this.checkBoxAutoStart.AutoSize = true;
this.checkBoxAutoStart.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.checkBoxAutoStart.Location = new System.Drawing.Point(222, 546);
this.checkBoxAutoStart.Location = new System.Drawing.Point(210, 545);
this.checkBoxAutoStart.Name = "checkBoxAutoStart";
this.checkBoxAutoStart.Size = new System.Drawing.Size(95, 24);
this.checkBoxAutoStart.TabIndex = 25;
this.checkBoxAutoStart.Text = "Auto Start";
this.checkBoxAutoStart.CheckedChanged += new System.EventHandler(this.checkBoxAutoStart_CheckedChanged);
//
// checkBoxAutoPanning
//
this.checkBoxAutoPanning.AutoSize = true;
this.checkBoxAutoPanning.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.checkBoxAutoPanning.Location = new System.Drawing.Point(441, 545);
this.checkBoxAutoPanning.Name = "checkBoxAutoPanning";
this.checkBoxAutoPanning.Size = new System.Drawing.Size(116, 24);
this.checkBoxAutoPanning.TabIndex = 27;
this.checkBoxAutoPanning.Text = "Auto Panning";
//
// checkBoxAlwaysOnTop
//
this.checkBoxAlwaysOnTop.AutoSize = true;
this.checkBoxAlwaysOnTop.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.checkBoxAlwaysOnTop.Location = new System.Drawing.Point(311, 545);
this.checkBoxAlwaysOnTop.Name = "checkBoxAlwaysOnTop";
this.checkBoxAlwaysOnTop.Size = new System.Drawing.Size(124, 24);
this.checkBoxAlwaysOnTop.TabIndex = 28;
this.checkBoxAlwaysOnTop.Text = "Always on Top";
//
// StartupForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(859, 591);
this.ClientSize = new System.Drawing.Size(914, 611);
this.Controls.Add(this.checkBoxAlwaysOnTop);
this.Controls.Add(this.checkBoxAutoPanning);
this.Controls.Add(this.checkBoxAutoStart);
this.Controls.Add(this.lblVersion);
this.Controls.Add(this.checkBoxMinimizeToTray);
this.Controls.Add(this.darkLabel2);
this.Controls.Add(this.darkLabel1);
this.Controls.Add(this.linkLabel1);
this.Controls.Add(this.labelMsfsRunning);
this.Controls.Add(this.labelMsfsConnection);
this.Controls.Add(this.panelStatus);
this.Controls.Add(this.panelSteps);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
@ -195,7 +220,6 @@ namespace MSFSPopoutPanelManager
this.Name = "StartupForm";
this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
this.Text = "MSFS Pop Out Panel Manager";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.StartupForm_FormClosing);
this.Load += new System.EventHandler(this.StartupForm_Load);
this.Resize += new System.EventHandler(this.StartupForm_Resize);
this.panelStatus.ResumeLayout(false);
@ -208,7 +232,7 @@ namespace MSFSPopoutPanelManager
#endregion
private System.Windows.Forms.Panel panelSteps;
private System.Windows.Forms.Label labelMsfsRunning;
private System.Windows.Forms.Label labelMsfsConnection;
private System.Windows.Forms.Panel panelStatus;
private System.Windows.Forms.NotifyIcon notifyIcon1;
private System.Windows.Forms.LinkLabel linkLabel1;
@ -219,5 +243,7 @@ namespace MSFSPopoutPanelManager
private DarkUI.Controls.DarkLabel lblVersion;
private DarkUI.Controls.DarkLabel darkLabel3;
private DarkUI.Controls.DarkCheckBox checkBoxAutoStart;
private DarkUI.Controls.DarkCheckBox checkBoxAutoPanning;
private DarkUI.Controls.DarkCheckBox checkBoxAlwaysOnTop;
}
}

View file

@ -1,67 +1,104 @@
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
namespace MSFSPopoutPanelManager.UI
{
public partial class StartupForm : DarkForm
{
private SynchronizationContext _syncRoot;
private PanelManager _panelManager;
private UserControlPanelSelection _ucPanelSelection;
private UserControlApplySettings _ucApplySettings;
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 StartUpController _controller;
public StartupForm()
{
InitializeComponent();
_syncRoot = SynchronizationContext.Current;
_ucPanelSelection = new UserControlPanelSelection();
_ucPanelConfiguration = new UserControlPanelConfiguration();
panelSteps.Controls.Add(_ucPanelSelection);
panelSteps.Controls.Add(_ucPanelConfiguration);
// Set version number
lblVersion.Text += System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
_controller = new StartUpController(this);
_controller.OnSimConnectionChanged += HandleSimConnectionChanged;
_controller.OnPanelSelectionActivated += (source, e) => { _ucPanelSelection.Visible = true; _ucPanelConfiguration.Visible = false; };
_controller.OnPanelConfigurationActivated += (source, e) => { _ucPanelSelection.Visible = false; _ucPanelConfiguration.Visible = true; };
_controller.Initialize();
checkBoxMinimizeToTray.DataBindings.Add("Checked", _controller, "IsMinimizeToTray");
checkBoxMinimizeToTray.CheckedChanged += (source, e) => _controller.SetMinimizeToTray(checkBoxMinimizeToTray.Checked);
checkBoxAlwaysOnTop.DataBindings.Add("Checked", _controller, "IsAlwaysOnTop");
checkBoxAlwaysOnTop.CheckedChanged += (source, e) => _controller.SetAlwaysOnTop(checkBoxAlwaysOnTop.Checked);
checkBoxAutoStart.DataBindings.Add("Checked", _controller, "IsAutoStart");
checkBoxAutoStart.CheckedChanged += (source, e) => _controller.SetAutoStart(checkBoxAutoStart.Checked);
checkBoxAutoPanning.DataBindings.Add("Checked", _controller, "UseAutoPanning");
checkBoxAutoPanning.CheckedChanged += (source, e) => _controller.SetAutoPanning(checkBoxAutoPanning.Checked);
Logger.OnStatusLogged += Logger_OnStatusLogged;
_panelManager = new PanelManager(this);
_panelManager.OnSimulatorStarted += PanelManager_OnSimulatorStarted;
Logger.OnBackgroundStatusLogged += Logger_OnBackgroundStatusLogged;
}
_ucPanelSelection = new UserControlPanelSelection(_panelManager);
_ucPanelSelection.Visible = true;
_ucApplySettings = new UserControlApplySettings(_panelManager);
_ucApplySettings.OnRestart += (source, e) => { _ucPanelSelection.Visible = true; _ucApplySettings.Visible = false; };
_ucApplySettings.Visible = false;
panelSteps.Controls.Add(_ucPanelSelection);
panelSteps.Controls.Add(_ucApplySettings);
_panelManager.OnAnalysisCompleted += (source, e) => { _ucPanelSelection.Visible = false; _ucApplySettings.Visible = true; };
_panelManager.CheckSimulatorStarted();
checkBoxAutoStart.Checked = Autostart.CheckIsAutoStart();
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)
{
_syncRoot.Post((arg) =>
if (e != null)
{
var msg = arg as string;
if (msg != null)
txtBoxStatus.Text = msg;
}, e.Value.Message);
txtBoxStatus.ForeColor = e.Value.MessageType == StatusMessageType.Info ? INFO_MESSAGE_COLOR : ERROR_MESSAGE_COLOR;
txtBoxStatus.Text = e.Value.Message;
}
if (e.Value.MessageType == StatusMessageType.Error)
PInvoke.SetForegroundWindow(Handle);
}
private void PanelManager_OnSimulatorStarted(object sender, EventArgs e)
private void Logger_OnBackgroundStatusLogged(object sender, EventArgs<StatusMessage> e)
{
_syncRoot.Post((arg) =>
{
panelStatus.Enabled = true;
labelMsfsRunning.Text = "MSFS is running";
labelMsfsRunning.ForeColor = Color.LightGreen;
}, null);
var statusMessage = arg as StatusMessage;
if (statusMessage != null)
{
txtBoxStatus.ForeColor = statusMessage.MessageType == StatusMessageType.Info ? INFO_MESSAGE_COLOR : ERROR_MESSAGE_COLOR;
txtBoxStatus.Text = statusMessage.Message;
}
if (e.Value.MessageType == StatusMessageType.Error)
PInvoke.SetForegroundWindow(Handle);
}, e.Value);
}
private void StartupForm_Load(object sender, EventArgs e)
@ -83,12 +120,6 @@ namespace MSFSPopoutPanelManager
}
}
private void StartupForm_FormClosing(object sender, FormClosingEventArgs e)
{
// Put all panels popout back to original state
//_windowManager.RestorePanelTitleBar();
}
private void notifyIcon1_DoubleClick(object sender, EventArgs e)
{
ShowInTaskbar = true;
@ -102,15 +133,5 @@ namespace MSFSPopoutPanelManager
Process.Start(new ProcessStartInfo("https://github.com/hawkeye-stan/msfs-popout-panel-manager") { UseShellExecute = true });
}
private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
{
if (checkBoxAutoStart.Checked)
Autostart.Activate();
else
Autostart.Deactivate();
}
}
}
}

View file

@ -1,105 +0,0 @@
using System;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager
{
public partial class UserControlApplySettings : UserControlCommon
{
private BindingList<PanelDestinationInfo> _panelInfoList;
public UserControlApplySettings(PanelManager panelManager) : base(panelManager)
{
InitializeComponent();
panelManager.OnAnalysisCompleted += PanelManager_OnAnalysisCompleted;
panelManager.OnPanelSettingsChanged += PanelManager_OnAnalysisCompleted;
}
public event EventHandler OnRestart;
private void PanelManager_OnAnalysisCompleted(object sender, EventArgs e)
{
_panelInfoList = new BindingList<PanelDestinationInfo>(PanelManager.CurrentPanelProfile.PanelSettings.PanelDestinationList.OrderBy(x => x.PanelName).ToList());
dataGridViewPanels.AutoGenerateColumns = false;
dataGridViewPanels.AutoSize = false;
dataGridViewPanels.DataSource = _panelInfoList;
checkBoxAlwaysOnTop.Checked = PanelManager.CurrentPanelProfile.PanelSettings.AlwaysOnTop;
checkBoxHidePanelTitleBar.Checked = PanelManager.CurrentPanelProfile.PanelSettings.HidePanelTitleBar;
}
private void buttonRestart_Click(object sender, EventArgs e)
{
OnRestart?.Invoke(this, null);
PanelManager.UpdatePanelLocationUI();
}
private void buttonApplySettings_Click(object sender, EventArgs e)
{
PanelManager.ApplyPanelSettings();
}
private void buttonSaveSettings_Click(object sender, EventArgs e)
{
PanelManager.SavePanelSettings();
}
private void checkBoxHidePanelTitleBar_CheckedChanged(object sender, EventArgs e)
{
PanelManager.CurrentPanelProfile.PanelSettings.HidePanelTitleBar = checkBoxHidePanelTitleBar.Checked;
}
private void checkBoxAlwaysOnTop_CheckedChanged(object sender, EventArgs e)
{
PanelManager.CurrentPanelProfile.PanelSettings.AlwaysOnTop = checkBoxAlwaysOnTop.Checked;
}
private void dataGridViewPanels_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
var panelName = Convert.ToString(dataGridViewPanels[0, e.RowIndex].FormattedValue);
var left = Convert.ToInt32(dataGridViewPanels[1, e.RowIndex].FormattedValue);
var top = Convert.ToInt32(dataGridViewPanels[2, e.RowIndex].FormattedValue);
var width = Convert.ToInt32(dataGridViewPanels[3, e.RowIndex].FormattedValue);
var height = Convert.ToInt32(dataGridViewPanels[4, e.RowIndex].FormattedValue);
var panel = PanelManager.CurrentPanelProfile.PanelSettings.PanelDestinationList.Find(x => x.PanelName == panelName);
PInvoke.MoveWindow(panel.PanelHandle, left, top, width, height, true);
}
private void dataGridViewPanels_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if (e.ColumnIndex == 0)
{
DataGridView dgv = sender as DataGridView;
PanelDestinationInfo data = dgv.Rows[e.RowIndex].DataBoundItem as PanelDestinationInfo;
if(!data.IsOpened && data.PanelType == WindowType.Custom_Popout)
{
dataGridViewPanels.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.PaleVioletRed;
}
}
}
private void dataGridViewPanels_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
{
// must be numbers
if(e.ColumnIndex >= 1 && e.ColumnIndex <= 4)
{
int i = 0;
bool result = int.TryParse(Convert.ToString(e.FormattedValue), out i);
if (!result)
e.Cancel = true;
}
}
}
}

View file

@ -1,21 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager
{
public class UserControlCommon : UserControl
{
protected PanelManager PanelManager { get; set; }
public UserControlCommon() { }
public UserControlCommon(PanelManager panelManager)
{
PanelManager = panelManager;
}
}
}

View file

@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<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,7 +1,7 @@

namespace MSFSPopoutPanelManager
namespace MSFSPopoutPanelManager.UI
{
partial class UserControlApplySettings
partial class UserControlPanelConfiguration
{
/// <summary>
/// Required designer variable.
@ -30,10 +30,15 @@ namespace MSFSPopoutPanelManager
private void InitializeComponent()
{
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle8 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle9 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle10 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle4 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle5 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle6 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle7 = 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();
@ -41,12 +46,15 @@ namespace MSFSPopoutPanelManager
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.buttonRestart = new System.Windows.Forms.Button();
this.checkBoxAlwaysOnTop = new System.Windows.Forms.CheckBox();
this.checkBoxHidePanelTitleBar = new System.Windows.Forms.CheckBox();
this.buttonApplySettings = new System.Windows.Forms.Button();
this.buttonSaveSettings = new System.Windows.Forms.Button();
this.buttonPixelMinusLarge = 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.panel1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.dataGridViewPanels)).BeginInit();
this.SuspendLayout();
@ -58,7 +66,7 @@ namespace MSFSPopoutPanelManager
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(860, 277);
this.panel1.Size = new System.Drawing.Size(915, 348);
this.panel1.TabIndex = 1;
//
// dataGridViewPanels
@ -67,99 +75,140 @@ namespace MSFSPopoutPanelManager
this.dataGridViewPanels.AllowUserToDeleteRows = false;
this.dataGridViewPanels.AllowUserToResizeColumns = false;
this.dataGridViewPanels.AllowUserToResizeRows = false;
dataGridViewCellStyle1.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle1.ForeColor = System.Drawing.Color.Black;
dataGridViewCellStyle1.Padding = new System.Windows.Forms.Padding(3);
this.dataGridViewPanels.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle1;
this.dataGridViewPanels.CausesValidation = false;
dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.Control;
dataGridViewCellStyle1.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle1.ForeColor = System.Drawing.SystemColors.WindowText;
dataGridViewCellStyle1.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle1.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.dataGridViewPanels.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle1;
dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Control;
dataGridViewCellStyle2.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle2.ForeColor = System.Drawing.SystemColors.WindowText;
dataGridViewCellStyle2.Padding = new System.Windows.Forms.Padding(10, 3, 3, 3);
dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.dataGridViewPanels.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle2;
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});
dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
dataGridViewCellStyle3.BackColor = System.Drawing.SystemColors.Window;
dataGridViewCellStyle3.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle3.ForeColor = System.Drawing.Color.White;
dataGridViewCellStyle3.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle3.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
this.dataGridViewPanels.DefaultCellStyle = dataGridViewCellStyle3;
this.Height,
this.AlwaysOnTop,
this.HideTitlebar});
dataGridViewCellStyle8.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
dataGridViewCellStyle8.BackColor = System.Drawing.SystemColors.Window;
dataGridViewCellStyle8.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle8.ForeColor = System.Drawing.Color.White;
dataGridViewCellStyle8.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle8.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle8.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
this.dataGridViewPanels.DefaultCellStyle = dataGridViewCellStyle8;
this.dataGridViewPanels.Location = new System.Drawing.Point(20, 35);
this.dataGridViewPanels.MultiSelect = false;
this.dataGridViewPanels.Name = "dataGridViewPanels";
dataGridViewCellStyle4.BackColor = System.Drawing.SystemColors.Control;
dataGridViewCellStyle4.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle4.ForeColor = System.Drawing.SystemColors.WindowText;
dataGridViewCellStyle4.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle4.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle4.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.dataGridViewPanels.RowHeadersDefaultCellStyle = dataGridViewCellStyle4;
dataGridViewCellStyle9.BackColor = System.Drawing.SystemColors.Control;
dataGridViewCellStyle9.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle9.ForeColor = System.Drawing.SystemColors.WindowText;
dataGridViewCellStyle9.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle9.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle9.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.dataGridViewPanels.RowHeadersDefaultCellStyle = dataGridViewCellStyle9;
this.dataGridViewPanels.RowHeadersVisible = false;
dataGridViewCellStyle5.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
dataGridViewCellStyle5.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle5.ForeColor = System.Drawing.SystemColors.WindowText;
dataGridViewCellStyle5.Padding = new System.Windows.Forms.Padding(3);
this.dataGridViewPanels.RowsDefaultCellStyle = dataGridViewCellStyle5;
dataGridViewCellStyle10.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle10.ForeColor = System.Drawing.SystemColors.WindowText;
dataGridViewCellStyle10.Padding = new System.Windows.Forms.Padding(3);
this.dataGridViewPanels.RowsDefaultCellStyle = dataGridViewCellStyle10;
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(820, 225);
this.dataGridViewPanels.Size = new System.Drawing.Size(874, 310);
this.dataGridViewPanels.TabIndex = 8;
this.dataGridViewPanels.CellEndEdit += new System.Windows.Forms.DataGridViewCellEventHandler(this.dataGridViewPanels_CellEndEdit);
this.dataGridViewPanels.CellFormatting += new System.Windows.Forms.DataGridViewCellFormattingEventHandler(this.dataGridViewPanels_CellFormatting);
this.dataGridViewPanels.CellValidating += new System.Windows.Forms.DataGridViewCellValidatingEventHandler(this.dataGridViewPanels_CellValidating);
//
// PanelName
//
this.PanelName.DataPropertyName = "PanelName";
dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
this.PanelName.DefaultCellStyle = dataGridViewCellStyle2;
dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
this.PanelName.DefaultCellStyle = dataGridViewCellStyle3;
this.PanelName.FillWeight = 80F;
this.PanelName.HeaderText = "Panel Name";
this.PanelName.Name = "PanelName";
this.PanelName.ReadOnly = true;
this.PanelName.Width = 355;
this.PanelName.Width = 300;
//
// Left
//
this.Left.DataPropertyName = "Left";
dataGridViewCellStyle4.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
this.Left.DefaultCellStyle = dataGridViewCellStyle4;
this.Left.FillWeight = 80F;
this.Left.HeaderText = "X Pos";
this.Left.MaxInputLength = 6;
this.Left.Name = "Left";
this.Left.Width = 115;
this.Left.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.Left.Width = 95;
//
// Top
//
this.Top.DataPropertyName = "Top";
dataGridViewCellStyle5.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
this.Top.DefaultCellStyle = dataGridViewCellStyle5;
this.Top.FillWeight = 80F;
this.Top.HeaderText = "Y Pos";
this.Top.MaxInputLength = 6;
this.Top.Name = "Top";
this.Top.Width = 115;
this.Top.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.Top.Width = 95;
//
// Width
//
this.Width.DataPropertyName = "Width";
dataGridViewCellStyle6.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
this.Width.DefaultCellStyle = dataGridViewCellStyle6;
this.Width.FillWeight = 80F;
this.Width.HeaderText = "Width";
this.Width.MaxInputLength = 6;
this.Width.Name = "Width";
this.Width.Width = 115;
this.Width.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.Width.Width = 95;
//
// Height
//
this.Height.DataPropertyName = "Height";
dataGridViewCellStyle7.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
this.Height.DefaultCellStyle = dataGridViewCellStyle7;
this.Height.FillWeight = 80F;
this.Height.HeaderText = "Height";
this.Height.MaxInputLength = 6;
this.Height.Name = "Height";
this.Height.Width = 115;
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
//
@ -177,84 +226,91 @@ namespace MSFSPopoutPanelManager
this.buttonRestart.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonRestart.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonRestart.ForeColor = System.Drawing.Color.White;
this.buttonRestart.Location = new System.Drawing.Point(732, 354);
this.buttonRestart.Location = new System.Drawing.Point(787, 354);
this.buttonRestart.Name = "buttonRestart";
this.buttonRestart.Size = new System.Drawing.Size(107, 35);
this.buttonRestart.TabIndex = 19;
this.buttonRestart.Text = "Restart";
this.buttonRestart.UseVisualStyleBackColor = false;
this.buttonRestart.Click += new System.EventHandler(this.buttonRestart_Click);
//
// checkBoxAlwaysOnTop
//
this.checkBoxAlwaysOnTop.AutoSize = true;
this.checkBoxAlwaysOnTop.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.checkBoxAlwaysOnTop.ForeColor = System.Drawing.Color.White;
this.checkBoxAlwaysOnTop.Location = new System.Drawing.Point(20, 283);
this.checkBoxAlwaysOnTop.Name = "checkBoxAlwaysOnTop";
this.checkBoxAlwaysOnTop.Size = new System.Drawing.Size(159, 24);
this.checkBoxAlwaysOnTop.TabIndex = 20;
this.checkBoxAlwaysOnTop.Text = "Panel always on top";
this.checkBoxAlwaysOnTop.UseVisualStyleBackColor = true;
this.checkBoxAlwaysOnTop.CheckedChanged += new System.EventHandler(this.checkBoxAlwaysOnTop_CheckedChanged);
//
// checkBoxHidePanelTitleBar
//
this.checkBoxHidePanelTitleBar.AutoSize = true;
this.checkBoxHidePanelTitleBar.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.checkBoxHidePanelTitleBar.ForeColor = System.Drawing.Color.White;
this.checkBoxHidePanelTitleBar.Location = new System.Drawing.Point(207, 283);
this.checkBoxHidePanelTitleBar.Name = "checkBoxHidePanelTitleBar";
this.checkBoxHidePanelTitleBar.Size = new System.Drawing.Size(294, 24);
this.checkBoxHidePanelTitleBar.TabIndex = 21;
this.checkBoxHidePanelTitleBar.Text = "Hide panel title bar (Custom panel only)";
this.checkBoxHidePanelTitleBar.UseVisualStyleBackColor = true;
this.checkBoxHidePanelTitleBar.CheckedChanged += new System.EventHandler(this.checkBoxHidePanelTitleBar_CheckedChanged);
//
// buttonApplySettings
//
this.buttonApplySettings.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonApplySettings.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonApplySettings.ForeColor = System.Drawing.Color.White;
this.buttonApplySettings.Location = new System.Drawing.Point(19, 354);
this.buttonApplySettings.Name = "buttonApplySettings";
this.buttonApplySettings.Size = new System.Drawing.Size(145, 35);
this.buttonApplySettings.TabIndex = 22;
this.buttonApplySettings.Text = "Apply Settings";
this.buttonApplySettings.UseVisualStyleBackColor = false;
this.buttonApplySettings.Click += new System.EventHandler(this.buttonApplySettings_Click);
//
// buttonSaveSettings
//
this.buttonSaveSettings.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonSaveSettings.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonSaveSettings.ForeColor = System.Drawing.Color.White;
this.buttonSaveSettings.Location = new System.Drawing.Point(207, 354);
this.buttonSaveSettings.Location = new System.Drawing.Point(624, 354);
this.buttonSaveSettings.Name = "buttonSaveSettings";
this.buttonSaveSettings.Size = new System.Drawing.Size(145, 35);
this.buttonSaveSettings.TabIndex = 23;
this.buttonSaveSettings.Text = "Save Settings";
this.buttonSaveSettings.Text = "Save Profile";
this.buttonSaveSettings.UseVisualStyleBackColor = false;
this.buttonSaveSettings.Click += new System.EventHandler(this.buttonSaveSettings_Click);
//
// UserControlApplySettings
// buttonPixelMinusLarge
//
this.buttonPixelMinusLarge.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonPixelMinusLarge.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonPixelMinusLarge.ForeColor = System.Drawing.Color.White;
this.buttonPixelMinusLarge.Location = new System.Drawing.Point(20, 354);
this.buttonPixelMinusLarge.Name = "buttonPixelMinusLarge";
this.buttonPixelMinusLarge.Size = new System.Drawing.Size(69, 35);
this.buttonPixelMinusLarge.TabIndex = 24;
this.buttonPixelMinusLarge.Text = "-10 px";
this.buttonPixelMinusLarge.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.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.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.buttonPixelPlusLarge.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.buttonPixelPlusLarge);
this.Controls.Add(this.buttonPixelPlusSmall);
this.Controls.Add(this.buttonPixelMinusSmall);
this.Controls.Add(this.buttonPixelMinusLarge);
this.Controls.Add(this.buttonSaveSettings);
this.Controls.Add(this.buttonApplySettings);
this.Controls.Add(this.checkBoxHidePanelTitleBar);
this.Controls.Add(this.checkBoxAlwaysOnTop);
this.Controls.Add(this.buttonRestart);
this.Controls.Add(this.panel1);
this.Name = "UserControlApplySettings";
this.Size = new System.Drawing.Size(860, 405);
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);
this.PerformLayout();
}
@ -264,14 +320,17 @@ namespace MSFSPopoutPanelManager
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Button buttonRestart;
private System.Windows.Forms.DataGridView dataGridViewPanels;
private System.Windows.Forms.CheckBox checkBoxAlwaysOnTop;
private System.Windows.Forms.CheckBox checkBoxHidePanelTitleBar;
private System.Windows.Forms.Button buttonApplySettings;
private System.Windows.Forms.Button buttonSaveSettings;
private System.Windows.Forms.Button buttonPixelMinusLarge;
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;
}
}

View file

@ -0,0 +1,113 @@
using MSFSPopoutPanelManager.Shared;
using MSFSPopoutPanelManager.UIController;
using System;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager.UI
{
public partial class UserControlPanelConfiguration : UserControl
{
private PanelConfigurationController _controller;
public UserControlPanelConfiguration()
{
InitializeComponent();
_controller = new PanelConfigurationController();
_controller.RefreshDataUI += (source, e) => dataGridViewPanels.Refresh();
_controller.HightlightSelectedPanel += HandleHighlightSelectedPanel;
dataGridViewPanels.AutoGenerateColumns = false;
dataGridViewPanels.AutoSize = false;
dataGridViewPanels.DataSource = _controller.PanelConfigs;
dataGridViewPanels.CellBeginEdit += HandleCellBeginEdit;
dataGridViewPanels.CellValidating += HandleCellValidating;
dataGridViewPanels.CellValueChanged += HandleCellValueChanged;
dataGridViewPanels.CellContentClick += HandleCellValueChanged;
buttonSaveSettings.Click += (source, e) => { dataGridViewPanels.EndEdit(); _controller.SaveSettings(); };
buttonRestart.Click += (source, e) => _controller.BackToPanelSelection();
buttonPixelPlusLarge.Click += (source, e) => HandleCellValueIncrDecr(10);
buttonPixelPlusSmall.Click += (source, e) => HandleCellValueIncrDecr(1);
buttonPixelMinusLarge.Click += (source, e) => HandleCellValueIncrDecr(-10);
buttonPixelMinusSmall.Click += (source, e) => HandleCellValueIncrDecr(-1);
}
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.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 HandleCellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
{
if (e.ColumnIndex >= 0 && e.RowIndex >= 0)
{
var column = (PanelConfigDataColumn)Enum.Parse(typeof(PanelConfigDataColumn), dataGridViewPanels.Columns[e.ColumnIndex].Name);
// Disallow cell edit
var dgv = sender as DataGridView;
var data = dgv.Rows[e.RowIndex].DataBoundItem as PanelConfig;
if (column == PanelConfigDataColumn.PanelName || column == PanelConfigDataColumn.AlwaysOnTop || column == PanelConfigDataColumn.HideTitlebar)
{
if (data.PanelType == PanelType.BuiltInPopout)
e.Cancel = true;
}
}
}
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;
}
}
}

View file

@ -72,4 +72,10 @@
<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>
</root>

View file

@ -1,5 +1,5 @@

namespace MSFSPopoutPanelManager
namespace MSFSPopoutPanelManager.UI
{
partial class UserControlPanelSelection
{
@ -29,6 +29,18 @@ namespace MSFSPopoutPanelManager
/// </summary>
private void InitializeComponent()
{
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle6 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle7 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle8 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle4 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle5 = 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();
@ -42,18 +54,106 @@ namespace MSFSPopoutPanelManager
this.buttonPanelSelection = new System.Windows.Forms.Button();
this.label3 = new System.Windows.Forms.Label();
this.label6 = new System.Windows.Forms.Label();
this.textBoxPanelLocations = new System.Windows.Forms.TextBox();
this.panel4 = new System.Windows.Forms.Panel();
this.checkBoxShowPanelLocation = new System.Windows.Forms.CheckBox();
this.buttonAnalyze = new System.Windows.Forms.Button();
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;
dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
dataGridViewCellStyle1.ForeColor = System.Drawing.Color.Black;
this.dataGridViewPanelCoor.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle1;
this.dataGridViewPanelCoor.CausesValidation = false;
dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Control;
dataGridViewCellStyle2.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle2.ForeColor = System.Drawing.SystemColors.WindowText;
dataGridViewCellStyle2.Padding = new System.Windows.Forms.Padding(18, 3, 3, 3);
dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
this.dataGridViewPanelCoor.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle2;
this.dataGridViewPanelCoor.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridViewPanelCoor.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.PanelIndex,
this.X,
this.Y});
dataGridViewCellStyle6.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
dataGridViewCellStyle6.BackColor = System.Drawing.SystemColors.Window;
dataGridViewCellStyle6.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle6.ForeColor = System.Drawing.SystemColors.ControlText;
dataGridViewCellStyle6.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle6.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle6.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
this.dataGridViewPanelCoor.DefaultCellStyle = dataGridViewCellStyle6;
this.dataGridViewPanelCoor.Location = new System.Drawing.Point(18, 42);
this.dataGridViewPanelCoor.MultiSelect = false;
this.dataGridViewPanelCoor.Name = "dataGridViewPanelCoor";
this.dataGridViewPanelCoor.ReadOnly = true;
dataGridViewCellStyle7.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
dataGridViewCellStyle7.BackColor = System.Drawing.SystemColors.Control;
dataGridViewCellStyle7.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle7.ForeColor = System.Drawing.SystemColors.WindowText;
dataGridViewCellStyle7.Padding = new System.Windows.Forms.Padding(3);
dataGridViewCellStyle7.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle7.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle7.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.dataGridViewPanelCoor.RowHeadersDefaultCellStyle = dataGridViewCellStyle7;
this.dataGridViewPanelCoor.RowHeadersVisible = false;
dataGridViewCellStyle8.Padding = new System.Windows.Forms.Padding(3);
this.dataGridViewPanelCoor.RowsDefaultCellStyle = dataGridViewCellStyle8;
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, 309);
this.dataGridViewPanelCoor.TabIndex = 18;
//
// PanelIndex
//
this.PanelIndex.DataPropertyName = "PanelIndex";
dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
dataGridViewCellStyle3.ForeColor = System.Drawing.Color.Black;
this.PanelIndex.DefaultCellStyle = dataGridViewCellStyle3;
this.PanelIndex.HeaderText = "Panel";
this.PanelIndex.Name = "PanelIndex";
this.PanelIndex.ReadOnly = true;
//
// X
//
this.X.DataPropertyName = "X";
dataGridViewCellStyle4.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
dataGridViewCellStyle4.ForeColor = System.Drawing.Color.Black;
this.X.DefaultCellStyle = dataGridViewCellStyle4;
this.X.HeaderText = "X-Pos";
this.X.Name = "X";
this.X.ReadOnly = true;
//
// Y
//
this.Y.DataPropertyName = "Y";
dataGridViewCellStyle5.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
dataGridViewCellStyle5.ForeColor = System.Drawing.Color.Black;
this.Y.DefaultCellStyle = dataGridViewCellStyle5;
this.Y.HeaderText = "Y-Pos";
this.Y.Name = "Y";
this.Y.ReadOnly = true;
//
// panel1
//
this.panel1.Controls.Add(this.buttonDeleteProfile);
@ -64,49 +164,44 @@ namespace MSFSPopoutPanelManager
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(571, 118);
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.Enabled = false;
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(289, 76);
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;
this.buttonDeleteProfile.Click += new System.EventHandler(this.buttonDeleteProfile_Click);
//
// 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(158, 76);
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;
this.buttonAddProfile.Click += new System.EventHandler(this.buttonAddProfile_Click);
//
// buttonSetDefault
//
this.buttonSetDefault.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonSetDefault.Enabled = false;
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(32, 76);
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;
this.buttonSetDefault.Click += new System.EventHandler(this.buttonSetDefault_Click);
//
// label2
//
@ -129,7 +224,6 @@ namespace MSFSPopoutPanelManager
this.comboBoxProfile.Name = "comboBoxProfile";
this.comboBoxProfile.Size = new System.Drawing.Size(445, 28);
this.comboBoxProfile.TabIndex = 5;
this.comboBoxProfile.SelectedIndexChanged += new System.EventHandler(this.comboBoxProfile_SelectedIndexChanged);
//
// panel2
//
@ -140,7 +234,7 @@ namespace MSFSPopoutPanelManager
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(571, 191);
this.panel2.Size = new System.Drawing.Size(579, 191);
this.panel2.TabIndex = 8;
//
// label1
@ -161,9 +255,9 @@ namespace MSFSPopoutPanelManager
this.label5.ForeColor = System.Drawing.Color.White;
this.label5.Location = new System.Drawing.Point(35, 115);
this.label5.Name = "label5";
this.label5.Size = new System.Drawing.Size(372, 20);
this.label5.Size = new System.Drawing.Size(517, 20);
this.label5.TabIndex = 11;
this.label5.Text = "CTRL + LEFT CLICK when all panels have been selected.";
this.label5.Text = "CTRL + LEFT CLICK when all panels have been selected or to cancel selection.";
//
// label4
//
@ -179,16 +273,14 @@ namespace MSFSPopoutPanelManager
// buttonPanelSelection
//
this.buttonPanelSelection.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonPanelSelection.Enabled = false;
this.buttonPanelSelection.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonPanelSelection.ForeColor = System.Drawing.Color.White;
this.buttonPanelSelection.Location = new System.Drawing.Point(35, 146);
this.buttonPanelSelection.Location = new System.Drawing.Point(32, 142);
this.buttonPanelSelection.Name = "buttonPanelSelection";
this.buttonPanelSelection.Size = new System.Drawing.Size(170, 35);
this.buttonPanelSelection.TabIndex = 9;
this.buttonPanelSelection.Text = "Start Panel Selection";
this.buttonPanelSelection.UseVisualStyleBackColor = false;
this.buttonPanelSelection.Click += new System.EventHandler(this.buttonPanelSelection_Click);
//
// label3
//
@ -196,82 +288,65 @@ namespace MSFSPopoutPanelManager
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(563, 47);
this.label3.Size = new System.Drawing.Size(551, 47);
this.label3.TabIndex = 7;
this.label3.Text = "2. Identify the pop out panels in the game by clicking on them. Their locations w" +
"ill be saved and for use on future flights. (You only need to do this once per p" +
"rofile)";
"ill be saved for use on future flights. (You only need to do this once per profi" +
"le)";
//
// 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(88, 10);
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";
//
// textBoxPanelLocations
//
this.textBoxPanelLocations.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.textBoxPanelLocations.ForeColor = System.Drawing.SystemColors.WindowText;
this.textBoxPanelLocations.Location = new System.Drawing.Point(6, 41);
this.textBoxPanelLocations.Multiline = true;
this.textBoxPanelLocations.Name = "textBoxPanelLocations";
this.textBoxPanelLocations.ReadOnly = true;
this.textBoxPanelLocations.Size = new System.Drawing.Size(271, 277);
this.textBoxPanelLocations.TabIndex = 12;
//
// panel4
//
this.panel4.Controls.Add(this.dataGridViewPanelCoor);
this.panel4.Controls.Add(this.checkBoxShowPanelLocation);
this.panel4.Controls.Add(this.textBoxPanelLocations);
this.panel4.Controls.Add(this.label6);
this.panel4.Location = new System.Drawing.Point(569, 0);
this.panel4.Location = new System.Drawing.Point(577, 0);
this.panel4.Name = "panel4";
this.panel4.Size = new System.Drawing.Size(288, 403);
this.panel4.Size = new System.Drawing.Size(338, 403);
this.panel4.TabIndex = 13;
//
// checkBoxShowPanelLocation
//
this.checkBoxShowPanelLocation.AutoSize = true;
this.checkBoxShowPanelLocation.Checked = true;
this.checkBoxShowPanelLocation.CheckState = System.Windows.Forms.CheckState.Checked;
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(35, 337);
this.checkBoxShowPanelLocation.Location = new System.Drawing.Point(67, 361);
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;
this.checkBoxShowPanelLocation.CheckedChanged += new System.EventHandler(this.checkBoxShowPanelLocation_CheckedChanged);
//
// buttonAnalyze
// buttonStartPopOut
//
this.buttonAnalyze.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(17)))), ((int)(((byte)(158)))), ((int)(((byte)(218)))));
this.buttonAnalyze.Enabled = false;
this.buttonAnalyze.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.buttonAnalyze.ForeColor = System.Drawing.Color.White;
this.buttonAnalyze.Location = new System.Drawing.Point(32, 41);
this.buttonAnalyze.Name = "buttonAnalyze";
this.buttonAnalyze.Size = new System.Drawing.Size(107, 35);
this.buttonAnalyze.TabIndex = 18;
this.buttonAnalyze.Text = "Analyze";
this.buttonAnalyze.UseVisualStyleBackColor = false;
this.buttonAnalyze.Click += new System.EventHandler(this.buttonAnalyze_Click);
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.buttonAnalyze);
this.panel3.Controls.Add(this.buttonStartPopOut);
this.panel3.Controls.Add(this.label7);
this.panel3.Enabled = false;
this.panel3.ForeColor = System.Drawing.Color.White;
this.panel3.Location = new System.Drawing.Point(0, 309);
this.panel3.Name = "panel3";
this.panel3.Size = new System.Drawing.Size(571, 94);
this.panel3.Size = new System.Drawing.Size(579, 94);
this.panel3.TabIndex = 14;
//
// label7
@ -281,9 +356,9 @@ namespace MSFSPopoutPanelManager
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(292, 20);
this.label7.Size = new System.Drawing.Size(348, 20);
this.label7.TabIndex = 7;
this.label7.Text = "3. Pop out and analyze the selected panels.";
this.label7.Text = "3. Start the pop out process for the selected panels.";
//
// UserControlPanelSelection
//
@ -295,7 +370,8 @@ namespace MSFSPopoutPanelManager
this.Controls.Add(this.panel2);
this.Controls.Add(this.panel1);
this.Name = "UserControlPanelSelection";
this.Size = new System.Drawing.Size(860, 405);
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);
@ -320,15 +396,17 @@ namespace MSFSPopoutPanelManager
private System.Windows.Forms.Label label4;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label6;
private System.Windows.Forms.TextBox textBoxPanelLocations;
private System.Windows.Forms.Panel panel3;
private System.Windows.Forms.Label label10;
private System.Windows.Forms.Panel panel4;
private System.Windows.Forms.CheckBox checkBoxShowPanelLocation;
private System.Windows.Forms.Button buttonAnalyze;
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,165 +1,163 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using MSFSPopoutPanelManager.Shared;
using MSFSPopoutPanelManager.UIController;
using System;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager
namespace MSFSPopoutPanelManager.UI
{
public partial class UserControlPanelSelection : UserControlCommon
public partial class UserControlPanelSelection : UserControl
{
private SynchronizationContext _syncRoot;
private PanelSelectionController _controller;
public UserControlPanelSelection(PanelManager panelManager) : base(panelManager)
public UserControlPanelSelection()
{
InitializeComponent();
_syncRoot = SynchronizationContext.Current;
_controller = new PanelSelectionController();
panelManager.OnSimulatorStarted += (source, e) => { _syncRoot.Post((arg) => { panel3.Enabled = true; }, null); };
// Listen to controller event
_controller.OnUIStateChanged += HandleOnUIStateChanged;
_controller.Initialize();
panelManager.PanelLocationSelection.OnSelectionStarted += PanelLocationSelection_OnSelectionStarted;
panelManager.PanelLocationSelection.OnSelectionCompleted += PanelLocationSelection_OnSelectionCompleted;
panelManager.PanelLocationSelection.OnLocationListChanged += PanelLocationSelection_OnLocationListChanged;
// Set bindings
comboBoxProfile.DisplayMember = "ProfileName";
comboBoxProfile.ValueMember = "ProfileId";
comboBoxProfile.DataSource = _controller.PlaneProfileList;
comboBoxProfile.DataBindings.Add("SelectedValue", _controller, "SelectedProfileId");
comboBoxProfile.SelectedValue = -1; // forced a default
comboBoxProfile.SelectedIndexChanged += HandleProfileChanged;
var defaultProfileId = FileManager.ReadUserData().DefaultProfileId;
SetProfileDropDown(defaultProfileId);
buttonAddProfile.Click += HandleAddProfile;
buttonDeleteProfile.Click += HandleDeleteProfile;
buttonSetDefault.Click += (source, e) => _controller.SetDefaultProfile();
buttonPanelSelection.Click += HandlePanelSelectionStarted;
buttonStartPopOut.Click += (source, e) => _controller.StartPopOut(ParentForm);
dataGridViewPanelCoor.AutoGenerateColumns = false;
dataGridViewPanelCoor.AutoSize = false;
dataGridViewPanelCoor.DataSource = _controller.PanelCoordinates;
checkBoxShowPanelLocation.DataBindings.Add("Checked", _controller, "ShowPanelLocationOverlay");
checkBoxShowPanelLocation.CheckedChanged += (source, e) => _controller.ShowPanelLocationOverlayChanged(checkBoxShowPanelLocation.Checked);
}
public event EventHandler<EventArgs<bool>> OnAnalyzeAvailabilityChanged;
private void PanelLocationSelection_OnSelectionStarted(object sender, EventArgs e)
private void HandleAddProfile(object sender, EventArgs e)
{
buttonPanelSelection.Enabled = false;
buttonAnalyze.Enabled = false;
OnAnalyzeAvailabilityChanged?.Invoke(this, new EventArgs<bool>(false));
}
private void PanelLocationSelection_OnSelectionCompleted(object sender, EventArgs e)
{
buttonPanelSelection.Enabled = true;
buttonAnalyze.Enabled = PanelManager.CurrentPanelProfile.PanelSourceCoordinates.Count > 0;
OnAnalyzeAvailabilityChanged?.Invoke(this, new EventArgs<bool>(true));
buttonAnalyze.Focus();
}
private void PanelLocationSelection_OnLocationListChanged(object sender, EventArgs e)
{
var sb = new StringBuilder();
if (PanelManager.CurrentPanelProfile == null || PanelManager.CurrentPanelProfile.PanelSourceCoordinates.Count == 0)
using(var form = new AddProfileForm { StartPosition = FormStartPosition.CenterParent })
{
textBoxPanelLocations.Text = null;
}
else
{
foreach (var coor in PanelManager.CurrentPanelProfile.PanelSourceCoordinates)
var dialogResult = form.ShowDialog();
if(dialogResult == DialogResult.OK)
{
sb.Append($"Panel: {coor.PanelIndex,-5} X-Pos: {coor.X,-8} Y-Pos: {coor.Y,-8}");
sb.Append(Environment.NewLine);
_controller.AddUserProfile(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 })
{
var dialogResult = form.ShowDialog();
if (dialogResult == DialogResult.Yes)
{
_controller.DeleteProfile();
}
}
}
private void HandleProfileChanged(object sender, EventArgs e)
{
if(Convert.ToInt32(comboBoxProfile.SelectedValue) > 0)
_controller.ProfileChanged(Convert.ToInt32(comboBoxProfile.SelectedValue));
}
private void HandlePanelSelectionStarted(object sender, EventArgs e)
{
if (_controller.ActiveProfile != null)
{
if (_controller.ActiveProfile.PanelConfigs.Count > 0)
{
var title = "Confirm Overwrite";
var message = "Are you sure you want to overwrite existing saved panel locations and settings for this profile??";
using (var form = new ConfirmDialogForm(title, message) { StartPosition = FormStartPosition.CenterParent })
{
var dialogResult = form.ShowDialog();
if (dialogResult == DialogResult.No)
{
return;
}
}
}
textBoxPanelLocations.Text = sb.ToString();
_controller.StartPanelSelection(ParentForm);
}
}
public void SetProfileDropDown(int? defaultProfileId)
private void HandleOnUIStateChanged(object sender, EventArgs<PanelSelectionUIState> e)
{
try
switch (e.Value)
{
var allProfiles = FileManager.ReadAllPlaneProfileData();
comboBoxProfile.DisplayMember = "ProfileName";
comboBoxProfile.ValueMember = "ProfileId";
comboBoxProfile.DataSource = allProfiles.OrderBy(x => x.ProfileName).ToList();
if (allProfiles.Exists(x => x.ProfileId == defaultProfileId))
comboBoxProfile.SelectedValue = defaultProfileId;
else
comboBoxProfile.SelectedIndex = -1;
}
catch (Exception ex)
{
Logger.LogStatus(ex.Message);
}
}
private void buttonPanelSelection_Click(object sender, EventArgs e)
{
bool continued = true;
if (PanelManager.CurrentPanelProfile != null && PanelManager.CurrentPanelProfile.PanelSettings.PanelDestinationList.Count > 0)
{
var dialogResult = MessageBox.Show("Are you sure you want to overwrite existing saved panel locations and settings for this profile?", "Confirm Overwrite", MessageBoxButtons.YesNo);
continued = dialogResult == DialogResult.Yes;
}
if (continued)
{
PanelManager.PanelLocationSelection.Start();
checkBoxShowPanelLocation.Checked = true;
}
}
private void comboBoxProfile_SelectedIndexChanged(object sender, EventArgs e)
{
PanelManager.PlaneProfileChanged(Convert.ToInt32(comboBoxProfile.SelectedValue), checkBoxShowPanelLocation.Checked);
buttonPanelSelection.Enabled = comboBoxProfile.SelectedValue != null;
buttonSetDefault.Enabled = comboBoxProfile.SelectedValue != null;
buttonDeleteProfile.Enabled = comboBoxProfile.SelectedValue != null;
buttonAnalyze.Enabled = !String.IsNullOrEmpty(textBoxPanelLocations.Text);
}
private void checkBoxShowPanelLocation_CheckedChanged(object sender, EventArgs e)
{
PanelManager.PanelLocationSelection.ShowPanelLocationOverlay(checkBoxShowPanelLocation.Checked);
}
private void buttonAnalyze_Click(object sender, EventArgs e)
{
Logger.LogStatus("Panel analysis in progress. Please wait...");
panel1.Enabled = false;
panel2.Enabled = false;
panel3.Enabled = false;
PanelManager.Analyze();
panel1.Enabled = true;
panel2.Enabled = true;
panel3.Enabled = true;
}
private void buttonSetDefault_Click(object sender, EventArgs e)
{
if(comboBoxProfile.SelectedValue != null)
PanelManager.SetDefaultProfile();
}
private void buttonAddProfile_Click(object sender, EventArgs e)
{
AddProfileForm addProfileForm = new AddProfileForm(PanelManager);
addProfileForm.StartPosition = FormStartPosition.CenterParent;
addProfileForm.OnAddProfile += (soruce, e) => { SetProfileDropDown(e.Value); };
addProfileForm.ShowDialog();
}
private void buttonDeleteProfile_Click(object sender, EventArgs e)
{
var dialogResult = MessageBox.Show("Are you sure you want to delete the selected profile?", "Confirm Delete", MessageBoxButtons.YesNo);
if(dialogResult == DialogResult.Yes)
{
var selectedProfile = (PlaneProfile)comboBoxProfile.SelectedItem;
PanelManager.DeleteUserProfile(selectedProfile);
SetProfileDropDown(null);
PanelManager.PlaneProfileChanged(null, checkBoxShowPanelLocation.Checked);
buttonPanelSelection.Enabled = comboBoxProfile.SelectedValue != null;
buttonSetDefault.Enabled = comboBoxProfile.SelectedValue != null;
buttonDeleteProfile.Enabled = comboBoxProfile.SelectedValue != null;
buttonAnalyze.Enabled = !String.IsNullOrEmpty(textBoxPanelLocations.Text);
case PanelSelectionUIState.NoProfileSelected:
comboBoxProfile.Enabled = true;
buttonAddProfile.Enabled = true;
buttonDeleteProfile.Enabled = false;
buttonSetDefault.Enabled = false;
buttonPanelSelection.Enabled = false;
checkBoxShowPanelLocation.Enabled = false;
buttonStartPopOut.Enabled = false;
break;
case PanelSelectionUIState.ProfileSelected:
comboBoxProfile.Enabled = true;
buttonAddProfile.Enabled = true;
buttonDeleteProfile.Enabled = true;
buttonSetDefault.Enabled = true;
buttonPanelSelection.Enabled = true;
checkBoxShowPanelLocation.Enabled = true;
buttonStartPopOut.Enabled = false;
break;
case PanelSelectionUIState.PanelSelectionStarted:
comboBoxProfile.Enabled = true;
buttonAddProfile.Enabled = false;
buttonDeleteProfile.Enabled = false;
buttonSetDefault.Enabled = false;
buttonPanelSelection.Enabled = false;
checkBoxShowPanelLocation.Enabled = false;
buttonStartPopOut.Enabled = false;
break;
case PanelSelectionUIState.PanelSelectionCompletedValid:
comboBoxProfile.Enabled = true;
buttonAddProfile.Enabled = true;
buttonDeleteProfile.Enabled = true;
buttonSetDefault.Enabled = true;
buttonPanelSelection.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;
buttonPanelSelection.Enabled = true;
checkBoxShowPanelLocation.Enabled = true;
buttonStartPopOut.Enabled = false;
break;
case PanelSelectionUIState.PopoutStarted:
comboBoxProfile.Enabled = false;
buttonAddProfile.Enabled = false;
buttonDeleteProfile.Enabled = false;
buttonSetDefault.Enabled = false;
buttonPanelSelection.Enabled = false;
checkBoxShowPanelLocation.Enabled = false;
buttonStartPopOut.Enabled = false;
break;
}
}
}

View file

@ -57,4 +57,13 @@
<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

@ -0,0 +1,33 @@
using MSFSPopoutPanelManager.Shared;
using System;
using System.ComponentModel;
namespace MSFSPopoutPanelManager.UIController
{
public class BaseController : INotifyPropertyChanged
{
// Need this for PropertyChanged.Fody plugin to add dynamic notification for binding for default SET property in controllers
public event PropertyChangedEventHandler PropertyChanged;
public static UserProfileData ActiveUserPlaneProfile { get; set; }
#region Shared Events
public static event EventHandler OnPopOutCompleted;
public static event EventHandler OnRestart;
public void PopOutCompleted()
{
OnPopOutCompleted?.Invoke(this, null);
}
public void Restart()
{
ActiveUserPlaneProfile = null;
OnRestart?.Invoke(this, null);
}
#endregion
}
}

View file

@ -0,0 +1,172 @@
using MSFSPopoutPanelManager.Provider;
using MSFSPopoutPanelManager.Shared;
using System;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
namespace MSFSPopoutPanelManager.UIController
{
public class PanelConfigurationController : BaseController
{
private const int WINEVENT_OUTOFCONTEXT = 0;
private const uint EVENT_SYSTEM_MOVESIZEEND = 0x000B;
private static PInvoke.WinEventProc _winEvent; // keep this as static to prevent garbage collect or the app will crash
private IntPtr _winEventHook;
public PanelConfigurationController()
{
BaseController.OnPopOutCompleted += HandlePopOutCompleted;
PanelConfigs = new BindingList<PanelConfig>();
_winEvent = new PInvoke.WinEventProc(EventCallback);
}
public event EventHandler RefreshDataUI;
public event EventHandler<EventArgs<int>> HightlightSelectedPanel;
public BindingList<PanelConfig> PanelConfigs { get; set; }
public void SaveSettings()
{
var profile = BaseController.ActiveUserPlaneProfile;
profile.PanelConfigs = PanelConfigs.ToList();
var allProfiles = FileManager.ReadUserProfileData();
var index = allProfiles.FindIndex(x => x.ProfileId == profile.ProfileId);
allProfiles[index] = profile;
FileManager.WriteUserProfileData(allProfiles);
}
public void BackToPanelSelection()
{
// Unhook all Win API events
PInvoke.UnhookWinEvent(_winEventHook);
// Try to close all Cutome Panel window
PanelConfigs.ToList().FindAll(p => p.PanelType == PanelType.CustomPopout).ForEach(panel => WindowManager.CloseWindow(panel.PanelHandle));
PanelConfigs.Clear();
Restart();
}
public void CellValueChanged(int rowIndex, PanelConfigDataColumn column, object newCellValue)
{
if (rowIndex != -1)
{
switch (column)
{
case PanelConfigDataColumn.PanelName:
PanelConfigs[rowIndex].PanelName = Convert.ToString(newCellValue);
PInvoke.SetWindowText(PanelConfigs[rowIndex].PanelHandle, PanelConfigs[rowIndex].PanelName);
break;
case PanelConfigDataColumn.Left:
PanelConfigs[rowIndex].Left = Convert.ToInt32(newCellValue);
PInvoke.MoveWindow(PanelConfigs[rowIndex].PanelHandle, PanelConfigs[rowIndex].Left, PanelConfigs[rowIndex].Top, PanelConfigs[rowIndex].Width, PanelConfigs[rowIndex].Height, true);
break;
case PanelConfigDataColumn.Top:
PanelConfigs[rowIndex].Top = Convert.ToInt32(newCellValue);
PInvoke.MoveWindow(PanelConfigs[rowIndex].PanelHandle, PanelConfigs[rowIndex].Left, PanelConfigs[rowIndex].Top, PanelConfigs[rowIndex].Width, PanelConfigs[rowIndex].Height, true);
break;
case PanelConfigDataColumn.Width:
PanelConfigs[rowIndex].Width = Convert.ToInt32(newCellValue);
PInvoke.MoveWindow(PanelConfigs[rowIndex].PanelHandle, PanelConfigs[rowIndex].Left, PanelConfigs[rowIndex].Top, PanelConfigs[rowIndex].Width, PanelConfigs[rowIndex].Height, true);
break;
case PanelConfigDataColumn.Height:
PanelConfigs[rowIndex].Height = Convert.ToInt32(newCellValue);
PInvoke.MoveWindow(PanelConfigs[rowIndex].PanelHandle, PanelConfigs[rowIndex].Left, PanelConfigs[rowIndex].Top, PanelConfigs[rowIndex].Width, PanelConfigs[rowIndex].Height, true);
break;
case PanelConfigDataColumn.AlwaysOnTop:
PanelConfigs[rowIndex].AlwaysOnTop = Convert.ToBoolean(newCellValue);
WindowManager.ApplyAlwaysOnTop(PanelConfigs[rowIndex].PanelHandle, PanelConfigs[rowIndex].AlwaysOnTop, new Rectangle(PanelConfigs[rowIndex].Left, PanelConfigs[rowIndex].Top, PanelConfigs[rowIndex].Width, PanelConfigs[rowIndex].Height));
break;
case PanelConfigDataColumn.HideTitlebar:
PanelConfigs[rowIndex].HideTitlebar = Convert.ToBoolean(newCellValue);
WindowManager.ApplyHidePanelTitleBar(PanelConfigs[rowIndex].PanelHandle, PanelConfigs[rowIndex].HideTitlebar);
break;
default:
return;
}
}
}
public void CellValueIncrDecr(int rowIndex, PanelConfigDataColumn column, int changeAmount)
{
if (rowIndex != -1)
{
switch (column)
{
case PanelConfigDataColumn.Left:
PanelConfigs[rowIndex].Left += changeAmount;
break;
case PanelConfigDataColumn.Top:
PanelConfigs[rowIndex].Top += changeAmount;
break;
case PanelConfigDataColumn.Width:
PanelConfigs[rowIndex].Width += changeAmount;
break;
case PanelConfigDataColumn.Height:
PanelConfigs[rowIndex].Height += changeAmount;
break;
default:
return;
}
RefreshDataUI?.Invoke(this, null);
PInvoke.MoveWindow(PanelConfigs[rowIndex].PanelHandle, PanelConfigs[rowIndex].Left, PanelConfigs[rowIndex].Top, PanelConfigs[rowIndex].Width, PanelConfigs[rowIndex].Height, true);
}
}
private void HandlePopOutCompleted(object sender, EventArgs e)
{
// Populate panel data
BaseController.ActiveUserPlaneProfile.PanelConfigs.ForEach(p => PanelConfigs.Add(p));
// Setup panel config event hooks
_winEventHook = PInvoke.SetWinEventHook(EVENT_SYSTEM_MOVESIZEEND, EVENT_SYSTEM_MOVESIZEEND, IntPtr.Zero, _winEvent, 0, 0, WINEVENT_OUTOFCONTEXT);
}
private void EventCallback(IntPtr hWinEventHook, uint iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime)
{
var panelConfig = PanelConfigs.FirstOrDefault(panel => panel.PanelHandle == hWnd);
if (panelConfig != null)
{
var rowIndex = PanelConfigs.IndexOf(panelConfig);
HightlightSelectedPanel?.Invoke(this, new EventArgs<int>(rowIndex));
if (panelConfig != null)
{
switch (iEvent)
{
case EVENT_SYSTEM_MOVESIZEEND:
Rectangle winRectangle;
Rectangle clientRectangle;
PInvoke.GetWindowRect(panelConfig.PanelHandle, out winRectangle);
PInvoke.GetClientRect(panelConfig.PanelHandle, out clientRectangle);
panelConfig.Top = winRectangle.Top;
panelConfig.Left = winRectangle.Left;
panelConfig.Width = clientRectangle.Width + 16;
panelConfig.Height = clientRectangle.Height + 39;
break;
}
RefreshDataUI?.Invoke(this, null);
}
}
}
}
public enum PanelConfigDataColumn
{
PanelName,
Left,
Top,
Width,
Height,
AlwaysOnTop,
HideTitlebar
}
}

View file

@ -0,0 +1,289 @@
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 : BaseController
{
private PanelSelectionManager _panelSelectionManager;
private Form _parentForm;
public PanelSelectionController()
{
_panelSelectionManager = new PanelSelectionManager();
_panelSelectionManager.OnSelectionStarted += PanelSelectionManager_OnPanelSelectionStarted;
_panelSelectionManager.OnSelectionCompleted += PanelSelectionManager_OnPanelSelectionCompleted;
_panelSelectionManager.OnPanelAdded += PanelSelectionManager_OnPanelAdded;
_panelSelectionManager.OnPanelSubtracted += PanelSelectionManager_OnPanelSubtracted;
BaseController.OnRestart += HandleOnRestart;
PanelCoordinates = new BindingList<PanelSourceCoordinate>();
}
public event EventHandler<EventArgs<PanelSelectionUIState>> OnUIStateChanged;
public int SelectedProfileId { get; set; }
public UserProfileData ActiveProfile { get { return BaseController.ActiveUserPlaneProfile; } }
public BindingList<UserProfileData> PlaneProfileList { get; set; }
public BindingList<PanelSourceCoordinate> PanelCoordinates { get; set; }
public bool HasActiveProfile { get { return true; } }
public bool ShowPanelLocationOverlay { get; set; }
public void Initialize()
{
// Setup Defaults
LoadPlaneProfileList();
PanelCoordinates.Clear();
var defaultProfile = FileManager.ReadUserProfileData().Find(x => x.IsDefaultProfile);
SelectedProfileId = defaultProfile == null ? 0 : defaultProfile.ProfileId;
ActiveUserPlaneProfile = defaultProfile;
ShowPanelLocationOverlay = false;
if (SelectedProfileId < 1)
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.NoProfileSelected));
else
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.ProfileSelected));
}
public void AddUserProfile(string profileName)
{
var profileId = PlaneProfileList.Count > 0 ? PlaneProfileList.Max(x => x.ProfileId) + 1 : 1;
var newPlaneProfile = new UserProfileData() { ProfileId = profileId, ProfileName = profileName };
// Find insert index for new profile into list in ascending order
var list = PlaneProfileList.ToList();
list.Add(newPlaneProfile);
var index = list.OrderBy(x => x.ProfileName).ToList().FindIndex(x => x.ProfileId == profileId);
PlaneProfileList.Insert(index, newPlaneProfile);
FileManager.WriteUserProfileData(PlaneProfileList.ToList());
SelectedProfileId = profileId;
ActiveUserPlaneProfile = newPlaneProfile;
PanelCoordinates.Clear();
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.ProfileSelected));
Logger.Status($"Profile '{newPlaneProfile.ProfileName}' has been added successfully.", StatusMessageType.Info);
}
public void DeleteProfile()
{
if (SelectedProfileId != 0)
{
var profileToRemove = PlaneProfileList.First(x => x.ProfileId == SelectedProfileId);
PlaneProfileList.Remove(profileToRemove);
FileManager.WriteUserProfileData(PlaneProfileList.ToList());
PanelCoordinates.Clear();
_panelSelectionManager.Reset();
SelectedProfileId = -1;
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.NoProfileSelected));
Logger.Status($"Profile '{profileToRemove.ProfileName}' has been deleted successfully.", StatusMessageType.Info);
}
}
public void SetDefaultProfile()
{
if (SelectedProfileId < 1)
return;
var profile = PlaneProfileList.First(x => x.ProfileId == SelectedProfileId);
profile.IsDefaultProfile = true;
foreach (var p in PlaneProfileList)
{
if (p.ProfileId != SelectedProfileId)
p.IsDefaultProfile = false;
}
FileManager.WriteUserProfileData(PlaneProfileList.ToList());
Logger.Status($"Profile '{profile.ProfileName}' has been set as default.", StatusMessageType.Info);
}
public void ProfileChanged(int profileId)
{
Logger.ClearStatus();
PanelCoordinates.Clear();
SelectedProfileId = profileId;
if (SelectedProfileId < 1)
{
ActiveUserPlaneProfile = null;
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.NoProfileSelected));
}
else
{
ActiveUserPlaneProfile = PlaneProfileList.First(x => x.ProfileId == SelectedProfileId);
ActiveUserPlaneProfile.PanelSourceCoordinates.ForEach(c => PanelCoordinates.Add(c));
_panelSelectionManager.PanelCoordinates = PanelCoordinates.ToList();
_panelSelectionManager.DrawPanelLocationOverlay();
_panelSelectionManager.ShowPanelLocationOverlay(false);
ShowPanelLocationOverlay = false;
if (PanelCoordinates.Count == 0)
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.ProfileSelected));
else
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.PanelSelectionCompletedValid));
}
}
public void StartPanelSelection(Form appForm)
{
_parentForm = appForm;
if (WindowManager.GetApplicationProcess() == null)
return;
// Temporary make app go to background
WindowManager.ApplyAlwaysOnTop(_parentForm.Handle, false, _parentForm.Bounds);
ActiveUserPlaneProfile.PanelConfigs = new List<PanelConfig>();
_panelSelectionManager.AppForm = appForm;
_panelSelectionManager.Start();
}
public void ShowPanelLocationOverlayChanged(bool show)
{
ShowPanelLocationOverlay = show;
_panelSelectionManager.ShowPanelLocationOverlay(show);
}
public void StartPopOut(Form appForm)
{
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);
Thread.Sleep(1000); // allow time for the mouse to be stopped moving by the user
_panelSelectionManager.ShowPanelLocationOverlay(false);
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, ActiveUserPlaneProfile);
// Temporary make app go to background before pop out process
WindowManager.ApplyAlwaysOnTop(appForm.Handle, false, appForm.Bounds);
var result = popoutSeparationManager.StartPopout();
WindowManager.ApplyAlwaysOnTop(appForm.Handle, true, appForm.Bounds);
if (result)
{
PopOutCompleted();
PInvoke.SetForegroundWindow(appForm.Handle);
}
else
{
_panelSelectionManager.ShowPanelLocationOverlay(true);
ShowPanelLocationOverlay = true;
}
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.PanelSelectionCompletedValid));
}
private void LoadPlaneProfileList()
{
if (PlaneProfileList != null)
PlaneProfileList.Clear();
else
PlaneProfileList = new BindingList<UserProfileData>();
var dataList = FileManager.ReadUserProfileData().OrderBy(x => x.ProfileName);
foreach (var profile in dataList)
PlaneProfileList.Add(profile);
}
private void PanelSelectionManager_OnPanelSelectionStarted(object sender, EventArgs e)
{
PanelCoordinates.Clear();
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.PanelSelectionStarted));
if(_parentForm != null)
_parentForm.WindowState = FormWindowState.Minimized;
var simulatorProcess = WindowManager.GetSimulatorProcess();
if(simulatorProcess != null)
PInvoke.SetForegroundWindow(simulatorProcess.Handle);
}
private void PanelSelectionManager_OnPanelAdded(object sender, EventArgs<PanelSourceCoordinate> e)
{
PanelCoordinates.Add(e.Value);
}
private void PanelSelectionManager_OnPanelSubtracted(object sender, EventArgs e)
{
if (PanelCoordinates.Count > 0)
PanelCoordinates.RemoveAt(PanelCoordinates.Count - 1);
}
private void PanelSelectionManager_OnPanelSelectionCompleted(object sender, EventArgs e)
{
if (_parentForm != null)
_parentForm.WindowState = FormWindowState.Normal;
if (PanelCoordinates.Count > 0)
{
ActiveUserPlaneProfile.PanelSourceCoordinates = PanelCoordinates.ToList();
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.PanelSelectionCompletedValid));
}
else
OnUIStateChanged?.Invoke(this, new EventArgs<PanelSelectionUIState>(PanelSelectionUIState.PanelSelectionCompletedInvalid));
ShowPanelLocationOverlay = true;
var simulatorProcess = WindowManager.GetSimulatorProcess();
if (simulatorProcess != null)
PInvoke.SetForegroundWindow(simulatorProcess.Handle);
}
private void HandleOnRestart(object sender, EventArgs e)
{
Initialize();
ProfileChanged(SelectedProfileId);
}
}
public enum PanelSelectionUIState
{
NoProfileSelected,
ProfileSelected,
PanelSelectionStarted,
PanelSelectionCompletedValid,
PanelSelectionCompletedInvalid,
PopoutStarted,
}
}

View file

@ -0,0 +1,90 @@

using MSFSPopoutPanelManager.Provider;
using MSFSPopoutPanelManager.Shared;
using System;
using System.Windows.Forms;
namespace MSFSPopoutPanelManager.UIController
{
public class StartUpController : BaseController
{
private const int MSFS_CONNECTION_CHECK_INTERVAL = 3000;
private Form _appForm;
private System.Timers.Timer _timer;
public StartUpController(Form form)
{
_appForm = form;
BaseController.OnPopOutCompleted += (source, e) => OnPanelConfigurationActivated?.Invoke(this, null);
BaseController.OnRestart += (source, e) => OnPanelSelectionActivated?.Invoke(this, null);
}
public event EventHandler<EventArgs<bool>> OnSimConnectionChanged;
public event EventHandler OnPanelSelectionActivated;
public event EventHandler OnPanelConfigurationActivated;
public bool IsMinimizeToTray { get; set; }
public bool IsAlwaysOnTop { get; set; }
public bool IsAutoStart { get; set; }
public bool UseAutoPanning { get; set; }
public void Initialize()
{
var appSettings = FileManager.ReadAppSettingData();
IsMinimizeToTray = appSettings.MinimizeToTray;
IsAlwaysOnTop = appSettings.AlwaysOnTop;
UseAutoPanning = appSettings.UseAutoPanning;
IsAutoStart = Autostart.CheckIsAutoStart();
OnPanelSelectionActivated?.Invoke(this, null);
CheckSimulatorStarted();
}
public void SetAutoStart(bool isSet)
{
if (isSet)
Autostart.Activate();
else
Autostart.Deactivate();
}
public void SetMinimizeToTray(bool isSet)
{
var appSettingData = FileManager.ReadAppSettingData();
appSettingData.MinimizeToTray = isSet;
FileManager.WriteAppSettingData(appSettingData);
}
public void SetAutoPanning(bool isSet)
{
var appSettingData = FileManager.ReadAppSettingData();
appSettingData.UseAutoPanning = isSet;
FileManager.WriteAppSettingData(appSettingData);
}
public void SetAlwaysOnTop(bool isSet)
{
var appSettingData = FileManager.ReadAppSettingData();
appSettingData.AlwaysOnTop = isSet;
FileManager.WriteAppSettingData(appSettingData);
WindowManager.ApplyAlwaysOnTop(_appForm.Handle, isSet, _appForm.Bounds);
}
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));
};
}
}
}

View file

@ -1,6 +1,16 @@
# Version History
<hr/>
## Version 3.0.0.0
* Provided 2X pop out and panel separation performance.
* Better support for all screen resolutions.
* Added Cold Start feature. Panels can be popped out and recalled later even when they're not turned on.
* Added Auto Panning feature. Application remembers the cockpit camera angle when you first define the pop out panels. It will automatically move the cockpit view for you when popping out panel.
* Added fine-grain control in positioning panels down to pixel level.
* Added Always on Top feature for application.
* Added realtime readout during panel positioning.
* Added exception tracing to help troubleshoot application issue.
## Vesion 2.2.0.0
* Disabled ability to launch multiple instances of the application.
* Added autostart feature when MSFS starts. The application will create or modify exe.xml. A backup copy of exe.xml will be created.

View file

@ -1,174 +0,0 @@
[
{
"templateName": "Built-in G1000",
"IsUserTemplate": false,
"templates": [
{
"popoutId": 1,
"popoutName": "PFD",
"imagePaths": [ "g1000/g1000_pfd.png" ]
},
{
"popoutId": 2,
"popoutName": "MFD",
"imagePaths": [ "g1000/g1000_mfd.png", "g1000/g1000_mfd2.png" ]
}
]
},
{
"templateName": "Built-in WT-G1000NXi",
"IsUserTemplate": false,
"templates": [
{
"popoutId": 1,
"popoutName": "PFD",
"imagePaths": [ "g1000nxi/g1000nxi_pfd.png" ]
},
{
"popoutId": 2,
"popoutName": "MFD",
"imagePaths": [ "g1000nxi/g1000nxi_mfd.png", "g1000nxi/g1000nxi_mfd2.png" ]
}
]
},
{
"templateName": "Built-in G3000-KINGAIR",
"IsUserTemplate": false,
"templates": [
{
"popoutId": 1,
"popoutName": "PFD",
"imagePaths": [ "g3000-kingair/g3000kingair_pfd.png", "g3000-kingair/g3000kingair_pfd2.png" ]
},
{
"popoutId": 2,
"popoutName": "MFD",
"imagePaths": [ "g3000-kingair/g3000kingair_mfd.png", "g3000-kingair/g3000kingair_mfd2.png" ]
},
{
"popoutId": 3,
"popoutName": "Standby Altitude Module",
"imagePaths": [ "g3000-kingair/g3000kingair_standby_altitude_module.png" ]
}
]
},
{
"templateName": "Built-in G3000",
"IsUserTemplate": false,
"templates": [
{
"popoutId": 1,
"popoutName": "PFD",
"imagePaths": [ "g3000/g3000_pfd.png" ]
},
{
"popoutId": 2,
"popoutName": "MFD",
"imagePaths": [ "g3000/g3000_mfd.png", "g3000/g3000_mfd2.png" ]
},
{
"popoutId": 3,
"popoutName": "Multipurpose Control Display Unit",
"imagePaths": [ "g3000/g3000_multipurpose_control.png" ]
},
{
"popoutId": 4,
"popoutName": "Standby Altitude Module #1",
"imagePaths": [ "g3000/g3000_standby_altitude_module_1.png" ]
},
{
"popoutId": 5,
"popoutName": "Standby Altitude Module #2",
"imagePaths": [ "g3000/g3000_standby_altitude_module_2.png" ]
}
]
},
{
"templateName": "Built-in FBW-A32NX",
"IsUserTemplate": false,
"templates": [
{
"popoutId": 1,
"popoutName": "Message Panel",
"imagePaths": [ "a32nx/a32nx_message_panel.png" ]
},
{
"popoutId": 2,
"popoutName": "System Display",
"imagePaths": [ "a32nx/a32nx_system_display.png" ]
},
{
"popoutId": 3,
"popoutName": "Engine Display",
"imagePaths": [ "a32nx/a32nx_engine_display.png" ]
},
{
"popoutId": 4,
"popoutName": "Multipurpose Control Display Unit",
"imagePaths": [ "a32nx/a32nx_multipurpose_control.png" ]
},
{
"popoutId": 5,
"popoutName": "NAV Display",
"imagePaths": [ "a32nx/a32nx_nav_display.png" ]
},
{
"popoutId": 6,
"popoutName": "PFD",
"imagePaths": [ "a32nx/a32nx_pfd.png" ]
},
{
"popoutId": 7,
"popoutName": "Standby Altitude Indicator",
"imagePaths": [ "a32nx/a32nx_standby_altitude_indicator.png" ]
}
]
},
{
"templateName": "Built-in CJ4",
"IsUserTemplate": false,
"templates": [
{
"popoutId": 1,
"popoutName": "PFD",
"imagePaths": [ "cj4/cj4_pfd.png" ]
},
{
"popoutId": 2,
"popoutName": "MFD",
"imagePaths": [ "cj4/cj4_mfd.png" ]
},
{
"popoutId": 3,
"popoutName": "Standby Altitude Module",
"imagePaths": [ "cj4/cj4_standby_altitude_module.png" ]
},
{
"popoutId": 4,
"popoutName": "Multipurpose Control Display Unit",
"imagePaths": [ "cj4/cj4_multipurpose_control.png" ]
}
]
},
{
"templateName": "Built-in PMS50-GTN750",
"IsUserTemplate": false,
"templates": [
{
"popoutId": 1,
"popoutName": "PFD",
"imagePaths": [ "pms50-gtn750/pms50_gtn750_pfd.png" ]
},
{
"popoutId": 2,
"popoutName": "MFD",
"imagePaths": [ "pms50-gtn750/pms50_gtn750_mfd.png" ]
},
{
"popoutId": 3,
"popoutName": "MFD",
"imagePaths": [ "pms50-gtn750/pms50_gtn750_mfd2.png" ]
}
]
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

View file

@ -1,50 +0,0 @@
[
{
"profileId": 1,
"profileName": "Built-in - Cessna 172 Skyhawk (WT G1000 NXi)",
"IsUserProfile": false,
"analysisTemplateName": "Built-in WT-G1000NXi"
},
{
"profileId": 2,
"profileName": "Built-in - DA62 (WT G1000 NXi)",
"IsUserProfile": false,
"analysisTemplateName": "Built-in WT-G1000NXi"
},
{
"profileId": 3,
"profileName": "Built-in - A32NX (Flybywire)",
"IsUserProfile": false,
"analysisTemplateName": "Built-in FBW-A32NX"
},
{
"profileId": 4,
"profileName": "Built-in - Beechcraft King Air 350i (Original G3000)",
"IsUserProfile": false,
"analysisTemplateName": "Built-in G3000-KINGAIR"
},
{
"profileId": 5,
"profileName": "Built-in - TBM 930 (Original G3000)",
"IsUserProfile": false,
"analysisTemplateName": "Built-in G3000"
},
{
"profileId": 6,
"profileName": "Built-in - Cessna Citation CJ4",
"IsUserProfile": false,
"analysisTemplateName": "Built-in CJ4"
},
{
"profileId": 7,
"profileName": "Built-in - Asobo Planes with (Original G1000)",
"IsUserProfile": false,
"analysisTemplateName": "Built-in G1000"
},
{
"profileId": 8,
"profileName": "Built-in - DA40NG (PMS50 GTN750)",
"IsUserProfile": false,
"analysisTemplateName": "Built-in PMS50-GTN750"
}
]

BIN
images/doc/v3.0/4k.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 MiB

BIN
images/doc/v3.0/s1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 MiB

BIN
images/doc/v3.0/s2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

BIN
images/doc/v3.0/s3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
images/doc/v3.0/s4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 MiB

BIN
images/doc/v3.0/s5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

19
log4net.config Normal file
View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<log4net>
<root>
<level value="ALL" />
<appender-ref ref="console" />
<appender-ref ref="file" />
</root>
<appender name="file" type="log4net.Appender.RollingFileAppender">
<file value="error.log" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="5" />
<maximumFileSize value="25MB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %level %logger - %message%newline" />
</layout>
</appender>
</log4net>