Refactored settings views

Improved global exception handling
Added plugins page
This commit is contained in:
Mark van Renswoude 2021-03-09 18:42:49 +01:00
parent b1b8498456
commit 4974e57221
51 changed files with 1077 additions and 488 deletions

View File

@ -1,8 +1,11 @@
using System.Diagnostics; using System;
using System.Diagnostics;
using System.Windows; using System.Windows;
using Hardcodet.Wpf.TaskbarNotification; using Hardcodet.Wpf.TaskbarNotification;
using MassiveKnob.View; using MassiveKnob.View;
using Serilog;
using SimpleInjector; using SimpleInjector;
using WpfBindingErrors;
namespace MassiveKnob namespace MassiveKnob
{ {
@ -28,6 +31,9 @@ namespace MassiveKnob
{ {
base.OnStartup(e); base.OnStartup(e);
// Do not let WPF swallow exceptions in bindings
BindingExceptionThrower.Attach();
notifyIcon = (TaskbarIcon)FindResource("NotifyIcon"); notifyIcon = (TaskbarIcon)FindResource("NotifyIcon");
Debug.Assert(notifyIcon != null, nameof(notifyIcon) + " != null"); Debug.Assert(notifyIcon != null, nameof(notifyIcon) + " != null");
} }

View File

@ -0,0 +1,48 @@
using MassiveKnob.View;
using MassiveKnob.View.Settings;
using MassiveKnob.ViewModel;
using MassiveKnob.ViewModel.Settings;
using SimpleInjector;
namespace MassiveKnob
{
public static class ContainerBuilder
{
public static Container Create()
{
var container = new Container();
container.Options.EnableAutoVerification = false;
container.Register<App>();
container.Register<SettingsWindow>();
container.Register<SettingsViewModel>();
container.Register<SettingsDeviceView>();
container.Register<SettingsDeviceViewModel>();
container.Register<SettingsAnalogInputsView>();
container.Register<SettingsAnalogInputsViewModel>();
container.Register<SettingsDigitalInputsView>();
container.Register<SettingsDigitalInputsViewModel>();
container.Register<SettingsAnalogOutputsView>();
container.Register<SettingsAnalogOutputsViewModel>();
container.Register<SettingsDigitalOutputsView>();
container.Register<SettingsDigitalOutputsViewModel>();
container.Register<SettingsLoggingView>();
container.Register<SettingsLoggingViewModel>();
container.Register<SettingsStartupView>();
container.Register<SettingsStartupViewModel>();
container.Register<SettingsPluginsView>();
container.Register<SettingsPluginsViewModel>();
return container;
}
}
}

View File

@ -5,7 +5,16 @@ namespace MassiveKnob.Core
{ {
public interface IPluginManager public interface IPluginManager
{ {
IEnumerable<IMassiveKnobPluginInfo> GetPlugins();
IEnumerable<IMassiveKnobDevicePlugin> GetDevicePlugins(); IEnumerable<IMassiveKnobDevicePlugin> GetDevicePlugins();
IEnumerable<IMassiveKnobActionPlugin> GetActionPlugins(); IEnumerable<IMassiveKnobActionPlugin> GetActionPlugins();
} }
public interface IMassiveKnobPluginInfo
{
string Filename { get; }
IMassiveKnobPlugin Plugin { get; }
}
} }

View File

@ -34,23 +34,28 @@ namespace MassiveKnob.Core
public class PluginManager : IPluginManager public class PluginManager : IPluginManager
{ {
private readonly ILogger logger; private readonly ILogger logger;
private readonly List<IMassiveKnobPlugin> plugins = new List<IMassiveKnobPlugin>(); private readonly List<IMassiveKnobPluginInfo> plugins = new List<IMassiveKnobPluginInfo>();
public PluginManager(ILogger logger) public PluginManager(ILogger logger)
{ {
this.logger = logger; this.logger = logger;
} }
public IEnumerable<IMassiveKnobPluginInfo> GetPlugins()
{
return plugins;
}
public IEnumerable<IMassiveKnobDevicePlugin> GetDevicePlugins() public IEnumerable<IMassiveKnobDevicePlugin> GetDevicePlugins()
{ {
return plugins.Where(p => p is IMassiveKnobDevicePlugin).Cast<IMassiveKnobDevicePlugin>(); return plugins.Where(p => p.Plugin is IMassiveKnobDevicePlugin).Select(p => (IMassiveKnobDevicePlugin)p.Plugin);
} }
public IEnumerable<IMassiveKnobActionPlugin> GetActionPlugins() public IEnumerable<IMassiveKnobActionPlugin> GetActionPlugins()
{ {
return plugins.Where(p => p is IMassiveKnobActionPlugin).Cast<IMassiveKnobActionPlugin>(); return plugins.Where(p => p.Plugin is IMassiveKnobActionPlugin).Select(p => (IMassiveKnobActionPlugin)p.Plugin);
} }
@ -190,7 +195,7 @@ namespace MassiveKnob.Core
logger.Information("Found plugin with Id {pluginId}: {name}", plugin.PluginId, plugin.Name); logger.Information("Found plugin with Id {pluginId}: {name}", plugin.PluginId, plugin.Name);
ValidateRegistration(filename, plugin, registeredIds); ValidateRegistration(filename, plugin, registeredIds);
plugins.Add((IMassiveKnobPlugin)pluginInstance); plugins.Add(new PluginInfo(filename, (IMassiveKnobPlugin)pluginInstance));
} }
} }
@ -280,5 +285,19 @@ namespace MassiveKnob.Core
// ReSharper disable once UnusedAutoPropertyAccessor.Local - for JSON deserialization // ReSharper disable once UnusedAutoPropertyAccessor.Local - for JSON deserialization
public string EntryAssembly { get; set; } public string EntryAssembly { get; set; }
} }
private class PluginInfo : IMassiveKnobPluginInfo
{
public string Filename { get; }
public IMassiveKnobPlugin Plugin { get; }
public PluginInfo(string filename, IMassiveKnobPlugin plugin)
{
Filename = filename;
Plugin = plugin;
}
}
} }
} }

View File

@ -58,6 +58,7 @@
<Reference Include="WindowsBase" /> <Reference Include="WindowsBase" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="ContainerBuilder.cs" />
<Compile Include="Helpers\ComboBoxTemplateSelector.cs" /> <Compile Include="Helpers\ComboBoxTemplateSelector.cs" />
<Compile Include="Helpers\ComparisonConverter.cs" /> <Compile Include="Helpers\ComparisonConverter.cs" />
<Compile Include="Helpers\SerialQueue.cs" /> <Compile Include="Helpers\SerialQueue.cs" />
@ -72,10 +73,17 @@
<Compile Include="ViewModel\DeviceViewModel.cs" /> <Compile Include="ViewModel\DeviceViewModel.cs" />
<Compile Include="ViewModel\InputOutputViewModel.cs" /> <Compile Include="ViewModel\InputOutputViewModel.cs" />
<Compile Include="ViewModel\MenuItemProperties.cs" /> <Compile Include="ViewModel\MenuItemProperties.cs" />
<Compile Include="ViewModel\PluginViewModel.cs" />
<Compile Include="ViewModel\SettingsViewModel.cs" /> <Compile Include="ViewModel\SettingsViewModel.cs" />
<Compile Include="View\InputOutputView.xaml.cs"> <Compile Include="ViewModel\Settings\SettingsAnalogOutputsViewModel.cs" />
<DependentUpon>InputOutputView.xaml</DependentUpon> <Compile Include="ViewModel\Settings\SettingsDigitalOutputsViewModel.cs" />
</Compile> <Compile Include="ViewModel\Settings\SettingsDigitalInputsViewModel.cs" />
<Compile Include="ViewModel\Settings\SettingsAnalogInputsViewModel.cs" />
<Compile Include="ViewModel\Settings\SettingsDeviceViewModel.cs" />
<Compile Include="ViewModel\Settings\BaseSettingsInputOutputViewModel.cs" />
<Compile Include="ViewModel\Settings\SettingsLoggingViewModel.cs" />
<Compile Include="ViewModel\Settings\SettingsPluginsViewModel.cs" />
<Compile Include="ViewModel\Settings\SettingsStartupViewModel.cs" />
<Compile Include="View\SettingsWindow.xaml.cs"> <Compile Include="View\SettingsWindow.xaml.cs">
<DependentUpon>SettingsWindow.xaml</DependentUpon> <DependentUpon>SettingsWindow.xaml</DependentUpon>
</Compile> </Compile>
@ -88,26 +96,24 @@
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>
<DependentUpon>Strings.resx</DependentUpon> <DependentUpon>Strings.resx</DependentUpon>
</Compile> </Compile>
<Compile Include="View\Settings\AnalogInputsView.xaml.cs"> <Compile Include="View\Settings\BaseSettingsInputOutputView.xaml.cs">
<DependentUpon>AnalogInputsView.xaml</DependentUpon> <DependentUpon>BaseSettingsInputOutputView.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="View\Settings\AnalogOutputsView.xaml.cs"> <Compile Include="View\Settings\SettingsAnalogOutputsView.cs" />
<DependentUpon>AnalogOutputsView.xaml</DependentUpon> <Compile Include="View\Settings\SettingsDigitalOutputsView.cs" />
<Compile Include="View\Settings\SettingsDigitalInputsView.cs" />
<Compile Include="View\Settings\SettingsAnalogInputsView.cs" />
<Compile Include="View\Settings\SettingsPluginsView.xaml.cs">
<DependentUpon>SettingsPluginsView.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="View\Settings\StartupView.xaml.cs"> <Compile Include="View\Settings\SettingsStartupView.xaml.cs">
<DependentUpon>StartupView.xaml</DependentUpon> <DependentUpon>SettingsStartupView.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="View\Settings\LoggingView.xaml.cs"> <Compile Include="View\Settings\SettingsLoggingView.xaml.cs">
<DependentUpon>LoggingView.xaml</DependentUpon> <DependentUpon>SettingsLoggingView.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="View\Settings\DigitalInputsView.xaml.cs"> <Compile Include="View\Settings\SettingsDeviceView.xaml.cs">
<DependentUpon>DigitalInputsView.xaml</DependentUpon> <DependentUpon>SettingsDeviceView.xaml</DependentUpon>
</Compile>
<Compile Include="View\Settings\DigitalOutputsView.xaml.cs">
<DependentUpon>DigitalOutputsView.xaml</DependentUpon>
</Compile>
<Compile Include="View\Settings\DeviceView.xaml.cs">
<DependentUpon>DeviceView.xaml</DependentUpon>
</Compile> </Compile>
<EmbeddedResource Include="Strings.resx"> <EmbeddedResource Include="Strings.resx">
<Generator>PublicResXFileCodeGenerator</Generator> <Generator>PublicResXFileCodeGenerator</Generator>
@ -158,6 +164,9 @@
<PackageReference Include="System.Reactive"> <PackageReference Include="System.Reactive">
<Version>5.0.0</Version> <Version>5.0.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="WpfBindingErrors">
<Version>1.1.0</Version>
</PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Resource Include="Resources\MainIcon.ico" /> <Resource Include="Resources\MainIcon.ico" />
@ -192,35 +201,31 @@
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Page> </Page>
<Page Include="Resources\Plugins.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Resources\Startup.xaml"> <Page Include="Resources\Startup.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Page> </Page>
<Page Include="View\Settings\AnalogInputsView.xaml"> <Page Include="View\Settings\BaseSettingsInputOutputView.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="View\Settings\AnalogOutputsView.xaml"> <Page Include="View\Settings\SettingsPluginsView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="View\Settings\StartupView.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Page> </Page>
<Page Include="View\Settings\LoggingView.xaml"> <Page Include="View\Settings\SettingsStartupView.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Page> </Page>
<Page Include="View\Settings\DigitalInputsView.xaml"> <Page Include="View\Settings\SettingsLoggingView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="View\Settings\DigitalOutputsView.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="View\Settings\DeviceView.xaml"> <Page Include="View\Settings\SettingsDeviceView.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
@ -230,10 +235,6 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="View\InputOutputView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="View\SettingsWindow.xaml"> <Page Include="View\SettingsWindow.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>

View File

@ -4,10 +4,7 @@ using System.Text;
using System.Windows; using System.Windows;
using MassiveKnob.Core; using MassiveKnob.Core;
using MassiveKnob.Settings; using MassiveKnob.Settings;
using MassiveKnob.View;
using MassiveKnob.ViewModel;
using Serilog; using Serilog;
using SimpleInjector;
namespace MassiveKnob namespace MassiveKnob
{ {
@ -19,23 +16,40 @@ namespace MassiveKnob
{ {
var settings = MassiveKnobSettingsJsonSerializer.Deserialize(); var settings = MassiveKnobSettingsJsonSerializer.Deserialize();
var loggingSwitch = new LoggingSwitch(); var loggingSwitch = new LoggingSwitch();
loggingSwitch.SetLogging(settings.Log.Enabled, settings.Log.Level); loggingSwitch.SetLogging(settings.Log.Enabled, settings.Log.Level);
var logFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"MassiveKnob", @"Logs");
var logger = new LoggerConfiguration() var logger = new LoggerConfiguration()
.MinimumLevel.Verbose() .MinimumLevel.Verbose()
.Filter.ByIncludingOnly(loggingSwitch.IsIncluded) .Filter.ByIncludingOnly(loggingSwitch.IsIncluded)
.Enrich.FromLogContext() .Enrich.FromLogContext()
.WriteTo.File( .WriteTo.File(
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"MassiveKnob", @"Logs", @".log"), Path.Combine(logFilePath, @".log"),
rollingInterval: RollingInterval.Day, rollingInterval: RollingInterval.Day,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{@Context}{NewLine}{Exception}") outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{@Context}{NewLine}{Exception}")
.CreateLogger(); .CreateLogger();
logger.Information("MassiveKnob starting"); logger.Information("MassiveKnob starting");
var pluginManager = new PluginManager(logger);
AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
var e = (Exception)args.ExceptionObject;
logger.Error(e, "Unhandled exception: {message}", e.Message);
MessageBox.Show(
"Oops, something went very wrong. Please notify the developer and include this message, you can copy it using Ctrl-C. " +
"Preferably also include the log file which can be found at:" + Environment.NewLine + logFilePath +
Environment.NewLine + Environment.NewLine +
e.Message, "Massive Knob - Fatal error", MessageBoxButton.OK, MessageBoxImage.Error);
};
var pluginManager = new PluginManager(logger);
var messages = new StringBuilder(); var messages = new StringBuilder();
pluginManager.Load((exception, filename) => pluginManager.Load((exception, filename) =>
{ {
@ -48,23 +62,17 @@ namespace MassiveKnob
return 1; return 1;
} }
var orchestrator = new MassiveKnobOrchestrator(pluginManager, logger, settings); var orchestrator = new MassiveKnobOrchestrator(pluginManager, logger, settings);
orchestrator.Load(); orchestrator.Load();
var container = new Container(); var container = ContainerBuilder.Create();
container.Options.EnableAutoVerification = false; container.RegisterInstance<ILogger>(logger);
container.RegisterInstance(logger);
container.RegisterInstance<ILoggingSwitch>(loggingSwitch); container.RegisterInstance<ILoggingSwitch>(loggingSwitch);
container.RegisterInstance<IPluginManager>(pluginManager); container.RegisterInstance<IPluginManager>(pluginManager);
container.RegisterInstance<IMassiveKnobOrchestrator>(orchestrator); container.RegisterInstance<IMassiveKnobOrchestrator>(orchestrator);
container.Register<App>();
container.Register<SettingsWindow>();
container.Register<SettingsViewModel>();
var app = container.GetInstance<App>(); var app = container.GetInstance<App>();
app.Run(); app.Run();

View File

@ -0,0 +1,17 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Viewbox Stretch="Uniform" x:Key="Plugins" x:Shared="False">
<Canvas Width="24" Height="24">
<Canvas.Resources>
<ResourceDictionary Source="IconStyle.xaml" />
</Canvas.Resources>
<Polygon Points="12 2 2 7 12 12 22 7 12 2" FillRule="NonZero" Style="{StaticResource IconStroke}" />
<Polyline Points="2 17 12 22 22 17" FillRule="NonZero" Style="{StaticResource IconStroke}" />
<Polyline Points="2 12 12 17 22 12" FillRule="NonZero" Style="{StaticResource IconStroke}" />
</Canvas>
</Viewbox>
</ResourceDictionary>

View File

@ -15,7 +15,8 @@ namespace MassiveKnob.Settings
AnalogOutputs, AnalogOutputs,
DigitalOutputs, DigitalOutputs,
Logging, Logging,
Startup Startup,
Plugins
} }

View File

@ -303,6 +303,15 @@ namespace MassiveKnob {
} }
} }
/// <summary>
/// Looks up a localized string similar to Plugins.
/// </summary>
public static string MenuItemPlugins {
get {
return ResourceManager.GetString("MenuItemPlugins", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Startup. /// Looks up a localized string similar to Startup.
/// </summary> /// </summary>

View File

@ -198,6 +198,9 @@
<data name="MenuItemLogging" xml:space="preserve"> <data name="MenuItemLogging" xml:space="preserve">
<value>Logging</value> <value>Logging</value>
</data> </data>
<data name="MenuItemPlugins" xml:space="preserve">
<value>Plugins</value>
</data>
<data name="MenuItemStartup" xml:space="preserve"> <data name="MenuItemStartup" xml:space="preserve">
<value>Startup</value> <value>Startup</value>
</data> </data>

View File

@ -22,8 +22,8 @@
<Setter Property="Margin" Value="0,0,0,4" /> <Setter Property="Margin" Value="0,0,0,4" />
</Style> </Style>
<Style TargetType="StackPanel" x:Key="Content"> <Style x:Key="Content">
<Setter Property="Margin" Value="8" /> <Setter Property="Control.Margin" Value="8" />
</Style> </Style>
<Style TargetType="ContentControl" x:Key="SettingsControl"> <Style TargetType="ContentControl" x:Key="SettingsControl">
@ -41,4 +41,19 @@
<Style TargetType="TextBlock" x:Key="SubLabel"> <Style TargetType="TextBlock" x:Key="SubLabel">
<Setter Property="Foreground" Value="#808080" /> <Setter Property="Foreground" Value="#808080" />
</Style> </Style>
<Style TargetType="TextBlock" x:Key="PluginName">
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
</Style>
<Style TargetType="TextBlock" x:Key="PluginDescription">
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="Margin" Value="0,0,0,8" />
</Style>
<Style TargetType="TextBlock" x:Key="PluginFilename">
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="Foreground" Value="#808080" />
</Style>
</ResourceDictionary> </ResourceDictionary>

View File

@ -1,65 +0,0 @@
<UserControl x:Class="MassiveKnob.View.InputOutputView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="clr-namespace:MassiveKnob.Helpers"
xmlns:viewModel="clr-namespace:MassiveKnob.ViewModel"
xmlns:massiveKnob="clr-namespace:MassiveKnob"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="600"
d:DataContext="{d:DesignInstance viewModel:InputOutputViewModelDesignTime, IsDesignTimeCreatable=True}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Style.xaml"></ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
<DataTemplate x:Key="ActionDropdownItem">
<StackPanel Orientation="Vertical" d:DataContext="{d:DesignInstance Type=viewModel:ActionViewModel}">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Description}" Style="{StaticResource ComboBoxDescription}" Visibility="{Binding DescriptionVisibility}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ActionSelectedItem">
<TextBlock Text="{Binding Name}" d:DataContext="{d:DesignInstance Type=viewModel:ActionViewModel}" />
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<StackPanel Orientation="Vertical" Style="{StaticResource Content}">
<TextBlock Text="{Binding DisplayName}" Style="{StaticResource SubHeader}"></TextBlock>
<StackPanel Orientation="Vertical">
<ComboBox
ItemsSource="{Binding Actions}"
SelectedItem="{Binding SelectedAction}"
IsSynchronizedWithCurrentItem="False"
ItemTemplateSelector="{helpers:ComboBoxTemplateSelector
SelectedItemTemplate={StaticResource ActionSelectedItem},
DropdownItemsTemplate={StaticResource ActionDropdownItem}}" />
<ContentControl Focusable="False" Content="{Binding ActionSettingsControl}" Style="{StaticResource SettingsControl}" />
<Grid Margin="0,24,0,0" Visibility="{Binding DigitalToAnalogVisibility}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Text="{x:Static massiveKnob:Strings.DigitalToAnalogDescription}" TextWrapping="Wrap" />
<TextBlock Grid.Row="1" Grid.Column="0" Margin="0,8,8,8" VerticalAlignment="Center" Text="{x:Static massiveKnob:Strings.DigitalToAnalogOn}" />
<Slider Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Value="{Binding DigitalToAnalogOn}" Minimum="0" Maximum="100" />
<TextBlock Grid.Row="2" Grid.Column="0" Margin="0,8,8,8" VerticalAlignment="Center" Text="{x:Static massiveKnob:Strings.DigitalToAnalogOff}" />
<Slider Grid.Row="2" Grid.Column="1" VerticalAlignment="Center" Value="{Binding DigitalToAnalogOff}" Minimum="0" Maximum="100" />
</Grid>
</StackPanel>
</StackPanel>
</UserControl>

View File

@ -1,13 +0,0 @@
namespace MassiveKnob.View
{
/// <summary>
/// Interaction logic for InputOutputView.xaml
/// </summary>
public partial class InputOutputView
{
public InputOutputView()
{
InitializeComponent();
}
}
}

View File

@ -1,29 +0,0 @@
<UserControl x:Class="MassiveKnob.View.Settings.AnalogInputsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:viewModel="clr-namespace:MassiveKnob.ViewModel"
xmlns:view="clr-namespace:MassiveKnob.View"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="800"
d:DataContext="{d:DesignInstance Type=viewModel:SettingsViewModelDesignTime, IsDesignTimeCreatable=True}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../../Style.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<StackPanel Orientation="Vertical" SnapsToDevicePixels="True" UseLayoutRounding="True" TextOptions.TextFormattingMode="Display">
<ItemsControl ItemsSource="{Binding AnalogInputs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<view:InputOutputView />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</UserControl>

View File

@ -1,13 +0,0 @@
namespace MassiveKnob.View.Settings
{
/// <summary>
/// Interaction logic for AnalogInputsView.xaml
/// </summary>
public partial class AnalogInputsView
{
public AnalogInputsView()
{
InitializeComponent();
}
}
}

View File

@ -1,28 +0,0 @@
<UserControl x:Class="MassiveKnob.View.Settings.AnalogOutputsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:viewModel="clr-namespace:MassiveKnob.ViewModel"
xmlns:view="clr-namespace:MassiveKnob.View"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="800"
d:DataContext="{d:DesignInstance Type=viewModel:SettingsViewModelDesignTime, IsDesignTimeCreatable=True}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../../Style.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<StackPanel Orientation="Vertical" SnapsToDevicePixels="True" UseLayoutRounding="True" TextOptions.TextFormattingMode="Display">
<ItemsControl ItemsSource="{Binding AnalogOutputs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<view:InputOutputView />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</UserControl>

View File

@ -1,13 +0,0 @@
namespace MassiveKnob.View.Settings
{
/// <summary>
/// Interaction logic for AnalogOutputsView.xaml
/// </summary>
public partial class AnalogOutputsView
{
public AnalogOutputsView()
{
InitializeComponent();
}
}
}

View File

@ -0,0 +1,75 @@
<UserControl x:Class="MassiveKnob.View.Settings.BaseSettingsInputOutputView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:settings="clr-namespace:MassiveKnob.ViewModel.Settings"
xmlns:viewModel="clr-namespace:MassiveKnob.ViewModel"
xmlns:massiveKnob="clr-namespace:MassiveKnob"
xmlns:helpers="clr-namespace:MassiveKnob.Helpers"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="800"
d:DataContext="{d:DesignInstance Type=settings:BaseSettingsInputOutputViewModelDesignTime, IsDesignTimeCreatable=True}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../../Style.xaml" />
</ResourceDictionary.MergedDictionaries>
<DataTemplate x:Key="ActionDropdownItem">
<StackPanel Orientation="Vertical" d:DataContext="{d:DesignInstance Type=viewModel:ActionViewModel}">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Description}" Style="{StaticResource ComboBoxDescription}" Visibility="{Binding DescriptionVisibility}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ActionSelectedItem">
<TextBlock Text="{Binding Name}" d:DataContext="{d:DesignInstance Type=viewModel:ActionViewModel}" />
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<StackPanel Orientation="Vertical" SnapsToDevicePixels="True" UseLayoutRounding="True" TextOptions.TextFormattingMode="Display">
<ItemsControl ItemsSource="{Binding InputOutputs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Style="{StaticResource Content}">
<TextBlock Text="{Binding DisplayName}" Style="{StaticResource SubHeader}"></TextBlock>
<StackPanel Orientation="Vertical">
<ComboBox
ItemsSource="{Binding Actions}"
SelectedItem="{Binding SelectedAction}"
IsSynchronizedWithCurrentItem="False"
ItemTemplateSelector="{helpers:ComboBoxTemplateSelector
SelectedItemTemplate={StaticResource ActionSelectedItem},
DropdownItemsTemplate={StaticResource ActionDropdownItem}}" />
<ContentControl Focusable="False" Content="{Binding ActionSettingsControl}" Style="{StaticResource SettingsControl}" />
<Grid Margin="0,24,0,0" Visibility="{Binding DigitalToAnalogVisibility}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Text="{x:Static massiveKnob:Strings.DigitalToAnalogDescription}" TextWrapping="Wrap" />
<TextBlock Grid.Row="1" Grid.Column="0" Margin="0,8,8,8" VerticalAlignment="Center" Text="{x:Static massiveKnob:Strings.DigitalToAnalogOn}" />
<Slider Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Value="{Binding DigitalToAnalogOn}" Minimum="0" Maximum="100" />
<TextBlock Grid.Row="2" Grid.Column="0" Margin="0,8,8,8" VerticalAlignment="Center" Text="{x:Static massiveKnob:Strings.DigitalToAnalogOff}" />
<Slider Grid.Row="2" Grid.Column="1" VerticalAlignment="Center" Value="{Binding DigitalToAnalogOff}" Minimum="0" Maximum="100" />
</Grid>
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</UserControl>

View File

@ -0,0 +1,16 @@
using MassiveKnob.ViewModel.Settings;
namespace MassiveKnob.View.Settings
{
/// <summary>
/// Interaction logic for BaseSettingsInputOutputView.xaml
/// </summary>
public partial class BaseSettingsInputOutputView
{
public BaseSettingsInputOutputView(BaseSettingsInputOutputViewModel viewModel)
{
DataContext = viewModel;
InitializeComponent();
}
}
}

View File

@ -1,13 +0,0 @@
namespace MassiveKnob.View.Settings
{
/// <summary>
/// Interaction logic for DeviceView.xaml
/// </summary>
public partial class DeviceView
{
public DeviceView()
{
InitializeComponent();
}
}
}

View File

@ -1,28 +0,0 @@
<UserControl x:Class="MassiveKnob.View.Settings.DigitalInputsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:viewModel="clr-namespace:MassiveKnob.ViewModel"
xmlns:view="clr-namespace:MassiveKnob.View"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="800"
d:DataContext="{d:DesignInstance Type=viewModel:SettingsViewModelDesignTime, IsDesignTimeCreatable=True}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../../Style.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<StackPanel Orientation="Vertical" SnapsToDevicePixels="True" UseLayoutRounding="True" TextOptions.TextFormattingMode="Display">
<ItemsControl ItemsSource="{Binding DigitalInputs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<view:InputOutputView />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</UserControl>

View File

@ -1,13 +0,0 @@
namespace MassiveKnob.View.Settings
{
/// <summary>
/// Interaction logic for DigitalInputsView.xaml
/// </summary>
public partial class DigitalInputsView
{
public DigitalInputsView()
{
InitializeComponent();
}
}
}

View File

@ -1,28 +0,0 @@
<UserControl x:Class="MassiveKnob.View.Settings.DigitalOutputsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:viewModel="clr-namespace:MassiveKnob.ViewModel"
xmlns:view="clr-namespace:MassiveKnob.View"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="800"
d:DataContext="{d:DesignInstance Type=viewModel:SettingsViewModelDesignTime, IsDesignTimeCreatable=True}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../../Style.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<StackPanel Orientation="Vertical" SnapsToDevicePixels="True" UseLayoutRounding="True" TextOptions.TextFormattingMode="Display">
<ItemsControl ItemsSource="{Binding DigitalOutputs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<view:InputOutputView />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</UserControl>

View File

@ -1,13 +0,0 @@
namespace MassiveKnob.View.Settings
{
/// <summary>
/// Interaction logic for DigitalOutputsView.xaml
/// </summary>
public partial class DigitalOutputsView
{
public DigitalOutputsView()
{
InitializeComponent();
}
}
}

View File

@ -1,13 +0,0 @@
namespace MassiveKnob.View.Settings
{
/// <summary>
/// Interaction logic for LoggingView.xaml
/// </summary>
public partial class LoggingView
{
public LoggingView()
{
InitializeComponent();
}
}
}

View File

@ -0,0 +1,12 @@
using MassiveKnob.ViewModel.Settings;
namespace MassiveKnob.View.Settings
{
public class SettingsAnalogInputsView : BaseSettingsInputOutputView
{
// ReSharper disable once SuggestBaseTypeForParameter - required for injection
public SettingsAnalogInputsView(SettingsAnalogInputsViewModel viewModel) : base(viewModel)
{
}
}
}

View File

@ -0,0 +1,12 @@
using MassiveKnob.ViewModel.Settings;
namespace MassiveKnob.View.Settings
{
public class SettingsAnalogOutputsView : BaseSettingsInputOutputView
{
// ReSharper disable once SuggestBaseTypeForParameter - required for injection
public SettingsAnalogOutputsView(SettingsAnalogOutputsViewModel viewModel) : base(viewModel)
{
}
}
}

View File

@ -1,13 +1,14 @@
<UserControl x:Class="MassiveKnob.View.Settings.DeviceView" <UserControl x:Class="MassiveKnob.View.Settings.SettingsDeviceView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:viewModel="clr-namespace:MassiveKnob.ViewModel" xmlns:viewModel="clr-namespace:MassiveKnob.ViewModel"
xmlns:helpers="clr-namespace:MassiveKnob.Helpers" xmlns:helpers="clr-namespace:MassiveKnob.Helpers"
xmlns:settings="clr-namespace:MassiveKnob.ViewModel.Settings"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="200" d:DesignWidth="800" d:DesignHeight="200" d:DesignWidth="800"
d:DataContext="{d:DesignInstance Type=viewModel:SettingsViewModelDesignTime, IsDesignTimeCreatable=True}"> d:DataContext="{d:DesignInstance Type=settings:SettingsDeviceViewModelDesignTime, IsDesignTimeCreatable=True}">
<UserControl.Resources> <UserControl.Resources>
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>

View File

@ -0,0 +1,16 @@
using MassiveKnob.ViewModel.Settings;
namespace MassiveKnob.View.Settings
{
/// <summary>
/// Interaction logic for SettingsDeviceView.xaml
/// </summary>
public partial class SettingsDeviceView
{
public SettingsDeviceView(SettingsDeviceViewModel viewModel)
{
DataContext = viewModel;
InitializeComponent();
}
}
}

View File

@ -0,0 +1,12 @@
using MassiveKnob.ViewModel.Settings;
namespace MassiveKnob.View.Settings
{
public class SettingsDigitalInputsView : BaseSettingsInputOutputView
{
// ReSharper disable once SuggestBaseTypeForParameter - required for injection
public SettingsDigitalInputsView(SettingsDigitalInputsViewModel viewModel) : base(viewModel)
{
}
}
}

View File

@ -0,0 +1,12 @@
using MassiveKnob.ViewModel.Settings;
namespace MassiveKnob.View.Settings
{
public class SettingsDigitalOutputsView : BaseSettingsInputOutputView
{
// ReSharper disable once SuggestBaseTypeForParameter - required for injection
public SettingsDigitalOutputsView(SettingsDigitalOutputsViewModel viewModel) : base(viewModel)
{
}
}
}

View File

@ -1,4 +1,4 @@
<UserControl x:Class="MassiveKnob.View.Settings.LoggingView" <UserControl x:Class="MassiveKnob.View.Settings.SettingsLoggingView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

View File

@ -0,0 +1,16 @@
using MassiveKnob.ViewModel.Settings;
namespace MassiveKnob.View.Settings
{
/// <summary>
/// Interaction logic for SettingsLoggingView.xaml
/// </summary>
public partial class SettingsLoggingView
{
public SettingsLoggingView(SettingsLoggingViewModel viewModel)
{
DataContext = viewModel;
InitializeComponent();
}
}
}

View File

@ -0,0 +1,29 @@
<UserControl x:Class="MassiveKnob.View.Settings.SettingsPluginsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:settings="clr-namespace:MassiveKnob.ViewModel.Settings"
mc:Ignorable="d"
d:DesignHeight="200" d:DesignWidth="800"
d:DataContext="{d:DesignInstance Type=settings:SettingsPluginsViewModelDesignTime, IsDesignTimeCreatable=True}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../../Style.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<ItemsControl ItemsSource="{Binding Plugins}" SnapsToDevicePixels="True" UseLayoutRounding="True" TextOptions.TextFormattingMode="Display" Style="{StaticResource Content}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Margin="0,0,0,24">
<TextBlock Text="{Binding Name}" Style="{StaticResource PluginName}" />
<TextBlock Text="{Binding Description}" Visibility="{Binding DescriptionVisibility}" Style="{StaticResource PluginDescription}" />
<TextBlock Text="{Binding Filename}" Style="{StaticResource PluginFilename}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>

View File

@ -0,0 +1,16 @@
using MassiveKnob.ViewModel.Settings;
namespace MassiveKnob.View.Settings
{
/// <summary>
/// Interaction logic for SettingsPluginsView.xaml
/// </summary>
public partial class SettingsPluginsView
{
public SettingsPluginsView(SettingsPluginsViewModel viewModel)
{
DataContext = viewModel;
InitializeComponent();
}
}
}

View File

@ -1,4 +1,4 @@
<UserControl x:Class="MassiveKnob.View.Settings.StartupView" <UserControl x:Class="MassiveKnob.View.Settings.SettingsStartupView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

View File

@ -0,0 +1,16 @@
using MassiveKnob.ViewModel.Settings;
namespace MassiveKnob.View.Settings
{
/// <summary>
/// Interaction logic for SettingsStartupView.xaml
/// </summary>
public partial class SettingsStartupView
{
public SettingsStartupView(SettingsStartupViewModel viewModel)
{
DataContext = viewModel;
InitializeComponent();
}
}
}

View File

@ -1,13 +0,0 @@
namespace MassiveKnob.View.Settings
{
/// <summary>
/// Interaction logic for StartupView.xaml
/// </summary>
public partial class StartupView
{
public StartupView()
{
InitializeComponent();
}
}
}

View File

@ -23,6 +23,7 @@
<ResourceDictionary Source="../Resources/Logging.xaml" /> <ResourceDictionary Source="../Resources/Logging.xaml" />
<ResourceDictionary Source="../Resources/Device.xaml" /> <ResourceDictionary Source="../Resources/Device.xaml" />
<ResourceDictionary Source="../Resources/Startup.xaml" /> <ResourceDictionary Source="../Resources/Startup.xaml" />
<ResourceDictionary Source="../Resources/Plugins.xaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<helpers:ComparisonConverter x:Key="ComparisonConverter" /> <helpers:ComparisonConverter x:Key="ComparisonConverter" />
@ -81,6 +82,7 @@
<TextBlock Style="{StaticResource MenuGroup}" Text="{x:Static massiveKnob:Strings.MenuGroupSettings}" /> <TextBlock Style="{StaticResource MenuGroup}" Text="{x:Static massiveKnob:Strings.MenuGroupSettings}" />
<RadioButton Style="{StaticResource MenuItem}" viewModel:MenuItemProperties.Text="{x:Static massiveKnob:Strings.MenuItemLogging}" viewModel:MenuItemProperties.Icon="{StaticResource Logging}" IsChecked="{Binding Path=SelectedMenuItem, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static settings:SettingsMenuItem.Logging}}"/> <RadioButton Style="{StaticResource MenuItem}" viewModel:MenuItemProperties.Text="{x:Static massiveKnob:Strings.MenuItemLogging}" viewModel:MenuItemProperties.Icon="{StaticResource Logging}" IsChecked="{Binding Path=SelectedMenuItem, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static settings:SettingsMenuItem.Logging}}"/>
<RadioButton Style="{StaticResource MenuItem}" viewModel:MenuItemProperties.Text="{x:Static massiveKnob:Strings.MenuItemStartup}" viewModel:MenuItemProperties.Icon="{StaticResource Startup}" IsChecked="{Binding Path=SelectedMenuItem, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static settings:SettingsMenuItem.Startup}}"/> <RadioButton Style="{StaticResource MenuItem}" viewModel:MenuItemProperties.Text="{x:Static massiveKnob:Strings.MenuItemStartup}" viewModel:MenuItemProperties.Icon="{StaticResource Startup}" IsChecked="{Binding Path=SelectedMenuItem, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static settings:SettingsMenuItem.Startup}}"/>
<RadioButton Style="{StaticResource MenuItem}" viewModel:MenuItemProperties.Text="{x:Static massiveKnob:Strings.MenuItemPlugins}" viewModel:MenuItemProperties.Icon="{StaticResource Plugins}" IsChecked="{Binding Path=SelectedMenuItem, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static settings:SettingsMenuItem.Plugins}}"/>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>

View File

@ -125,7 +125,7 @@ namespace MassiveKnob.ViewModel
// ReSharper restore UnusedMember.Global // ReSharper restore UnusedMember.Global
public InputOutputViewModel(SettingsViewModel settingsViewModel, IMassiveKnobOrchestrator orchestrator, public InputOutputViewModel(IEnumerable<ActionViewModel> allActions, IMassiveKnobOrchestrator orchestrator,
MassiveKnobActionType actionType, int index) MassiveKnobActionType actionType, int index)
{ {
this.orchestrator = orchestrator; this.orchestrator = orchestrator;
@ -155,7 +155,7 @@ namespace MassiveKnob.ViewModel
} }
Actions = settingsViewModel.Actions.Where(AllowAction).ToList(); Actions = allActions.Where(AllowAction).ToList();
var actionInfo = orchestrator.GetAction(actionType, index); var actionInfo = orchestrator.GetAction(actionType, index);

View File

@ -0,0 +1,23 @@
using System.Windows;
namespace MassiveKnob.ViewModel
{
public class PluginViewModel
{
// ReSharper disable UnusedMember.Global - used by WPF Binding
public string Name { get; }
public string Description { get; }
public string Filename { get; }
public Visibility DescriptionVisibility => string.IsNullOrEmpty(Description) ? Visibility.Collapsed : Visibility.Visible;
// ReSharper restore UnusedMember.Global
public PluginViewModel(string name, string description, string filename)
{
Name = name;
Description = description;
Filename = filename;
}
}
}

View File

@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
using MassiveKnob.Core;
using MassiveKnob.Plugin;
namespace MassiveKnob.ViewModel.Settings
{
public class BaseSettingsInputOutputViewModel : IDisposable, INotifyPropertyChanged
{
private readonly IMassiveKnobOrchestrator orchestrator;
private readonly MassiveKnobActionType inputOutputType;
private DeviceSpecs? specs;
private readonly IDisposable activeDeviceSubscription;
private IEnumerable<InputOutputViewModel> inputOutputs;
// ReSharper disable UnusedMember.Global - used by WPF Binding
public IList<ActionViewModel> Actions { get; }
public DeviceSpecs? Specs
{
get => specs;
set
{
specs = value;
OnPropertyChanged();
DisposeInputOutputViewModels();
int inputOutputCount;
switch (inputOutputType)
{
case MassiveKnobActionType.InputAnalog:
inputOutputCount = specs?.AnalogInputCount ?? 0;
break;
case MassiveKnobActionType.InputDigital:
inputOutputCount = specs?.DigitalInputCount ?? 0;
break;
case MassiveKnobActionType.OutputAnalog:
inputOutputCount = specs?.AnalogOutputCount ?? 0;
break;
case MassiveKnobActionType.OutputDigital:
inputOutputCount = specs?.DigitalOutputCount ?? 0;
break;
default:
throw new ArgumentOutOfRangeException();
}
InputOutputs = Enumerable
.Range(0, inputOutputCount)
.Select(i => new InputOutputViewModel(Actions, orchestrator, inputOutputType, i))
.ToList();
}
}
public IEnumerable<InputOutputViewModel> InputOutputs
{
get => inputOutputs;
set
{
inputOutputs = value;
OnPropertyChanged();
}
}
// ReSharper restore UnusedMember.Global
public BaseSettingsInputOutputViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator, MassiveKnobActionType inputOutputType)
{
this.orchestrator = orchestrator;
this.inputOutputType = inputOutputType;
// For design-time support
if (orchestrator == null)
return;
var allActions = new List<ActionViewModel>
{
new ActionViewModel(null, null)
};
allActions.AddRange(
pluginManager.GetActionPlugins()
.SelectMany(ap => ap.Actions.Select(a => new ActionViewModel(ap, a)))
.OrderBy(a => a.Name.ToLower()));
Actions = allActions;
activeDeviceSubscription = orchestrator.ActiveDeviceSubject.Subscribe(info =>
{
Application.Current?.Dispatcher.Invoke(() =>
{
Specs = info.Specs;
});
});
if (orchestrator.ActiveDevice != null)
Specs = orchestrator.ActiveDevice.Specs;
}
public void Dispose()
{
DisposeInputOutputViewModels();
activeDeviceSubscription?.Dispose();
}
private void DisposeInputOutputViewModels()
{
if (inputOutputs == null)
return;
foreach (var viewModel in inputOutputs)
viewModel.Dispose();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class BaseSettingsInputOutputViewModelDesignTime : BaseSettingsInputOutputViewModel
{
public BaseSettingsInputOutputViewModelDesignTime()
: base(null, null, MassiveKnobActionType.InputAnalog)
{
Specs = new DeviceSpecs(2, 2, 2, 2);
}
}
}

View File

@ -0,0 +1,13 @@
using MassiveKnob.Core;
using MassiveKnob.Plugin;
namespace MassiveKnob.ViewModel.Settings
{
public class SettingsAnalogInputsViewModel : BaseSettingsInputOutputViewModel
{
public SettingsAnalogInputsViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator)
: base(pluginManager, orchestrator, MassiveKnobActionType.InputAnalog)
{
}
}
}

View File

@ -0,0 +1,13 @@
using MassiveKnob.Core;
using MassiveKnob.Plugin;
namespace MassiveKnob.ViewModel.Settings
{
public class SettingsAnalogOutputsViewModel : BaseSettingsInputOutputViewModel
{
public SettingsAnalogOutputsViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator)
: base(pluginManager, orchestrator, MassiveKnobActionType.OutputAnalog)
{
}
}
}

View File

@ -0,0 +1,165 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows.Controls;
using System.Windows.Media;
using MassiveKnob.Core;
namespace MassiveKnob.ViewModel.Settings
{
public class SettingsDeviceViewModel : IDisposable, INotifyPropertyChanged
{
private readonly IMassiveKnobOrchestrator orchestrator;
private DeviceViewModel selectedDevice;
private UserControl settingsControl;
private readonly IDisposable deviceStatusSubscription;
// ReSharper disable UnusedMember.Global - used by WPF Binding
public IList<DeviceViewModel> Devices { get; }
public DeviceViewModel SelectedDevice
{
get => selectedDevice;
set
{
if (value == selectedDevice)
return;
selectedDevice = value;
var deviceInfo = orchestrator?.SetActiveDevice(value?.Device);
OnPropertyChanged();
SettingsControl = deviceInfo?.Instance.CreateSettingsControl();
}
}
public UserControl SettingsControl
{
get => settingsControl;
set
{
if (value == settingsControl)
return;
if (settingsControl is IDisposable disposable)
disposable.Dispose();
settingsControl = value;
OnPropertyChanged();
}
}
public string ConnectionStatusText
{
get
{
if (orchestrator == null)
return "Design-time";
switch (orchestrator.DeviceStatus)
{
case MassiveKnobDeviceStatus.Disconnected:
return Strings.DeviceStatusDisconnected;
case MassiveKnobDeviceStatus.Connecting:
return Strings.DeviceStatusConnecting;
case MassiveKnobDeviceStatus.Connected:
return Strings.DeviceStatusConnected;
default:
return null;
}
}
}
public Brush ConnectionStatusColor
{
get
{
if (orchestrator == null)
return Brushes.Fuchsia;
switch (orchestrator.DeviceStatus)
{
case MassiveKnobDeviceStatus.Disconnected:
return Brushes.DarkRed;
case MassiveKnobDeviceStatus.Connecting:
return Brushes.Orange;
case MassiveKnobDeviceStatus.Connected:
return Brushes.ForestGreen;
default:
return null;
}
}
}
// ReSharper restore UnusedMember.Global
public SettingsDeviceViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator)
{
this.orchestrator = orchestrator;
// For design-time support
if (orchestrator == null)
return;
deviceStatusSubscription = orchestrator.DeviceStatusSubject.Subscribe(status =>
{
OnDependantPropertyChanged(nameof(ConnectionStatusColor));
OnDependantPropertyChanged(nameof(ConnectionStatusText));
});
Devices = pluginManager.GetDevicePlugins()
.SelectMany(dp => dp.Devices.Select(d => new DeviceViewModel(dp, d)))
.OrderBy(d => d.Name.ToLower())
.ToList();
if (orchestrator.ActiveDevice == null)
return;
selectedDevice = Devices.Single(d => d.Device.DeviceId == orchestrator.ActiveDevice.Info.DeviceId);
SettingsControl = orchestrator.ActiveDevice.Instance.CreateSettingsControl();
}
public void Dispose()
{
if (SettingsControl is IDisposable disposable)
disposable.Dispose();
deviceStatusSubscription?.Dispose();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnDependantPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class SettingsDeviceViewModelDesignTime : SettingsDeviceViewModel
{
public SettingsDeviceViewModelDesignTime() : base(null, null)
{
}
}
}

View File

@ -0,0 +1,13 @@
using MassiveKnob.Core;
using MassiveKnob.Plugin;
namespace MassiveKnob.ViewModel.Settings
{
public class SettingsDigitalInputsViewModel : BaseSettingsInputOutputViewModel
{
public SettingsDigitalInputsViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator)
: base(pluginManager, orchestrator, MassiveKnobActionType.InputDigital)
{
}
}
}

View File

@ -0,0 +1,13 @@
using MassiveKnob.Core;
using MassiveKnob.Plugin;
namespace MassiveKnob.ViewModel.Settings
{
public class SettingsDigitalOutputsViewModel : BaseSettingsInputOutputViewModel
{
public SettingsDigitalOutputsViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator)
: base(pluginManager, orchestrator, MassiveKnobActionType.OutputDigital)
{
}
}
}

View File

@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using MassiveKnob.Core;
using MassiveKnob.Settings;
using Serilog.Events;
namespace MassiveKnob.ViewModel.Settings
{
public class SettingsLoggingViewModel : INotifyPropertyChanged
{
private readonly IMassiveKnobOrchestrator orchestrator;
private readonly ILoggingSwitch loggingSwitch;
// ReSharper disable UnusedMember.Global - used by WPF Binding
public IList<LoggingLevelViewModel> LoggingLevels { get; }
private LoggingLevelViewModel selectedLoggingLevel;
public LoggingLevelViewModel SelectedLoggingLevel
{
get => selectedLoggingLevel;
set
{
if (value == selectedLoggingLevel)
return;
selectedLoggingLevel = value;
OnPropertyChanged();
ApplyLoggingSettings();
}
}
private bool loggingEnabled;
public bool LoggingEnabled
{
get => loggingEnabled;
set
{
if (value == loggingEnabled)
return;
loggingEnabled = value;
OnPropertyChanged();
ApplyLoggingSettings();
}
}
// TODO (code quality) do not hardcode path here
public string LoggingOutputPath { get; } = string.Format(Strings.LoggingOutputPath,
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"MassiveKnob",
@"Logs"));
// ReSharper restore UnusedMember.Global
public SettingsLoggingViewModel(IMassiveKnobOrchestrator orchestrator, ILoggingSwitch loggingSwitch)
{
this.orchestrator = orchestrator;
this.loggingSwitch = loggingSwitch;
// For design-time support
if (orchestrator == null)
return;
var logSettings = orchestrator.GetSettings().Log;
LoggingLevels = new List<LoggingLevelViewModel>
{
new LoggingLevelViewModel(LogEventLevel.Error, Strings.LoggingLevelError, Strings.LoggingLevelErrorDescription),
new LoggingLevelViewModel(LogEventLevel.Warning, Strings.LoggingLevelWarning, Strings.LoggingLevelWarningDescription),
new LoggingLevelViewModel(LogEventLevel.Information, Strings.LoggingLevelInformation, Strings.LoggingLevelInformationDescription),
new LoggingLevelViewModel(LogEventLevel.Verbose, Strings.LoggingLevelVerbose, Strings.LoggingLevelVerboseDescription)
};
selectedLoggingLevel = LoggingLevels.SingleOrDefault(l => l.Level == logSettings.Level)
?? LoggingLevels.Single(l => l.Level == LogEventLevel.Information);
loggingEnabled = logSettings.Enabled;
}
private void ApplyLoggingSettings()
{
orchestrator?.UpdateSettings(settings =>
{
settings.Log.Enabled = LoggingEnabled;
settings.Log.Level = SelectedLoggingLevel.Level;
});
loggingSwitch?.SetLogging(LoggingEnabled, selectedLoggingLevel.Level);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MassiveKnob.Core;
namespace MassiveKnob.ViewModel.Settings
{
public class SettingsPluginsViewModel
{
// ReSharper disable UnusedMember.Global - used by WPF Binding
public IEnumerable<PluginViewModel> Plugins { get; protected set; }
// ReSharper restore UnusedMember.Global
public SettingsPluginsViewModel(IPluginManager pluginManager)
{
// Design-time support
if (pluginManager == null)
return;
Plugins = pluginManager.GetPlugins()
.Select(p => new PluginViewModel(p.Plugin.Name, p.Plugin.Description, p.Filename))
.OrderBy(p => p.Name, StringComparer.CurrentCultureIgnoreCase)
.ToList();
}
}
public class SettingsPluginsViewModelDesignTime : SettingsPluginsViewModel
{
public SettingsPluginsViewModelDesignTime()
: base(null)
{
Plugins = new[]
{
new PluginViewModel("Plugin without description", null, "D:\\Does\\Not\\Exist.dll"),
new PluginViewModel("Design-time plugin", "Fake plugin only visible at design-time.", "C:\\Does\\Not\\Exist.dll")
};
}
}
}

View File

@ -0,0 +1,67 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using Microsoft.Win32;
namespace MassiveKnob.ViewModel.Settings
{
public class SettingsStartupViewModel : INotifyPropertyChanged
{
public const string RunKey = @"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";
public const string RunValue = @"MassiveKnob";
// ReSharper disable UnusedMember.Global - used by WPF Binding
private bool runAtStartup;
public bool RunAtStartup
{
get => runAtStartup;
set
{
if (value == runAtStartup)
return;
runAtStartup = value;
OnPropertyChanged();
ApplyRunAtStartup();
}
}
// ReSharper restore UnusedMember.Global
public SettingsStartupViewModel()
{
var runKey = Registry.CurrentUser.OpenSubKey(RunKey, false);
runAtStartup = runKey?.GetValue(RunValue) != null;
}
private void ApplyRunAtStartup()
{
var runKey = Registry.CurrentUser.OpenSubKey(RunKey, true);
Debug.Assert(runKey != null, nameof(runKey) + " != null");
if (RunAtStartup)
{
var entryAssembly = Assembly.GetEntryAssembly();
Debug.Assert(entryAssembly != null, nameof(entryAssembly) + " != null");
runKey.SetValue(RunValue, new Uri(entryAssembly.CodeBase).LocalPath);
}
else
{
runKey.DeleteValue(RunValue, false);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@ -1,55 +1,39 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media;
using MassiveKnob.Core; using MassiveKnob.Core;
using MassiveKnob.Plugin; using MassiveKnob.Plugin;
using MassiveKnob.Settings; using MassiveKnob.Settings;
using MassiveKnob.View.Settings; using MassiveKnob.View.Settings;
using Microsoft.Win32;
using Serilog.Events;
namespace MassiveKnob.ViewModel namespace MassiveKnob.ViewModel
{ {
// TODO (code quality) split ViewModel for individual views, create viewmodel using container
// TODO (nice to have) installed plugins list
public class SettingsViewModel : IDisposable, INotifyPropertyChanged public class SettingsViewModel : IDisposable, INotifyPropertyChanged
{ {
private readonly Dictionary<SettingsMenuItem, Type> menuItemControls = new Dictionary<SettingsMenuItem, Type> private readonly Dictionary<SettingsMenuItem, Type> menuItemControls = new Dictionary<SettingsMenuItem, Type>
{ {
{ SettingsMenuItem.Device, typeof(DeviceView) }, { SettingsMenuItem.Device, typeof(SettingsDeviceView) },
{ SettingsMenuItem.AnalogInputs, typeof(AnalogInputsView) }, { SettingsMenuItem.AnalogInputs, typeof(SettingsAnalogInputsView) },
{ SettingsMenuItem.DigitalInputs, typeof(DigitalInputsView) }, { SettingsMenuItem.DigitalInputs, typeof(SettingsDigitalInputsView) },
{ SettingsMenuItem.AnalogOutputs, typeof(AnalogOutputsView) }, { SettingsMenuItem.AnalogOutputs, typeof(SettingsAnalogOutputsView) },
{ SettingsMenuItem.DigitalOutputs, typeof(DigitalOutputsView) }, { SettingsMenuItem.DigitalOutputs, typeof(SettingsDigitalOutputsView) },
{ SettingsMenuItem.Logging, typeof(LoggingView) }, { SettingsMenuItem.Logging, typeof(SettingsLoggingView) },
{ SettingsMenuItem.Startup, typeof(StartupView) } { SettingsMenuItem.Startup, typeof(SettingsStartupView) },
{ SettingsMenuItem.Plugins, typeof(SettingsPluginsView) }
}; };
private readonly SimpleInjector.Container container;
private readonly IMassiveKnobOrchestrator orchestrator; private readonly IMassiveKnobOrchestrator orchestrator;
private readonly ILoggingSwitch loggingSwitch;
private DeviceViewModel selectedDevice;
private UserControl selectedView; private UserControl selectedView;
private SettingsMenuItem selectedMenuItem; private SettingsMenuItem selectedMenuItem;
private UserControl settingsControl;
private DeviceSpecs? specs; private DeviceSpecs? specs;
private IEnumerable<InputOutputViewModel> analogInputs;
private IEnumerable<InputOutputViewModel> digitalInputs;
private IEnumerable<InputOutputViewModel> analogOutputs;
private IEnumerable<InputOutputViewModel> digitalOutputs;
private readonly IDisposable activeDeviceSubscription; private readonly IDisposable activeDeviceSubscription;
private readonly IDisposable deviceStatusSubscription;
// ReSharper disable UnusedMember.Global - used by WPF Binding // ReSharper disable UnusedMember.Global - used by WPF Binding
public SettingsMenuItem SelectedMenuItem public SettingsMenuItem SelectedMenuItem
@ -64,8 +48,8 @@ namespace MassiveKnob.ViewModel
OnPropertyChanged(); OnPropertyChanged();
if (menuItemControls.TryGetValue(selectedMenuItem, out var viewType)) if (menuItemControls.TryGetValue(selectedMenuItem, out var viewType))
SelectedView = (UserControl) Activator.CreateInstance(viewType); SelectedView = (UserControl)container?.GetInstance(viewType);
orchestrator?.UpdateSettings(settings => orchestrator?.UpdateSettings(settings =>
{ {
settings.UI.ActiveMenuItem = selectedMenuItem; settings.UI.ActiveMenuItem = selectedMenuItem;
@ -88,42 +72,7 @@ namespace MassiveKnob.ViewModel
public IList<DeviceViewModel> Devices { get; } //public IList<ActionViewModel> Actions { get; }
public IList<ActionViewModel> Actions { get; }
public DeviceViewModel SelectedDevice
{
get => selectedDevice;
set
{
if (value == selectedDevice)
return;
selectedDevice = value;
var deviceInfo = orchestrator?.SetActiveDevice(value?.Device);
OnPropertyChanged();
SettingsControl = deviceInfo?.Instance.CreateSettingsControl();
}
}
public UserControl SettingsControl
{
get => settingsControl;
set
{
if (value == settingsControl)
return;
if (settingsControl is IDisposable disposable)
disposable.Dispose();
settingsControl = value;
OnPropertyChanged();
}
}
public DeviceSpecs? Specs public DeviceSpecs? Specs
{ {
@ -136,7 +85,7 @@ namespace MassiveKnob.ViewModel
OnDependantPropertyChanged("DigitalInputVisibility"); OnDependantPropertyChanged("DigitalInputVisibility");
OnDependantPropertyChanged("AnalogOutputVisibility"); OnDependantPropertyChanged("AnalogOutputVisibility");
OnDependantPropertyChanged("DigitalOutputVisibility"); OnDependantPropertyChanged("DigitalOutputVisibility");
/*
DisposeInputOutputViewModels(AnalogInputs); DisposeInputOutputViewModels(AnalogInputs);
DisposeInputOutputViewModels(DigitalInputs); DisposeInputOutputViewModels(DigitalInputs);
DisposeInputOutputViewModels(AnalogOutputs); DisposeInputOutputViewModels(AnalogOutputs);
@ -161,6 +110,7 @@ namespace MassiveKnob.ViewModel
.Range(0, specs?.DigitalOutputCount ?? 0) .Range(0, specs?.DigitalOutputCount ?? 0)
.Select(i => new InputOutputViewModel(this, orchestrator, MassiveKnobActionType.OutputDigital, i)) .Select(i => new InputOutputViewModel(this, orchestrator, MassiveKnobActionType.OutputDigital, i))
.ToList(); .ToList();
*/
} }
} }
@ -169,6 +119,7 @@ namespace MassiveKnob.ViewModel
? Visibility.Visible ? Visibility.Visible
: Visibility.Collapsed; : Visibility.Collapsed;
/*
public IEnumerable<InputOutputViewModel> AnalogInputs public IEnumerable<InputOutputViewModel> AnalogInputs
{ {
get => analogInputs; get => analogInputs;
@ -178,11 +129,13 @@ namespace MassiveKnob.ViewModel
OnPropertyChanged(); OnPropertyChanged();
} }
} }
*/
public Visibility DigitalInputVisibility => specs.HasValue && specs.Value.DigitalInputCount > 0 public Visibility DigitalInputVisibility => specs.HasValue && specs.Value.DigitalInputCount > 0
? Visibility.Visible ? Visibility.Visible
: Visibility.Collapsed; : Visibility.Collapsed;
/*
public IEnumerable<InputOutputViewModel> DigitalInputs public IEnumerable<InputOutputViewModel> DigitalInputs
{ {
get => digitalInputs; get => digitalInputs;
@ -192,11 +145,13 @@ namespace MassiveKnob.ViewModel
OnPropertyChanged(); OnPropertyChanged();
} }
} }
*/
public Visibility AnalogOutputVisibility => specs.HasValue && specs.Value.AnalogOutputCount > 0 public Visibility AnalogOutputVisibility => specs.HasValue && specs.Value.AnalogOutputCount > 0
? Visibility.Visible ? Visibility.Visible
: Visibility.Collapsed; : Visibility.Collapsed;
/*
public IEnumerable<InputOutputViewModel> AnalogOutputs public IEnumerable<InputOutputViewModel> AnalogOutputs
{ {
get => analogOutputs; get => analogOutputs;
@ -206,11 +161,13 @@ namespace MassiveKnob.ViewModel
OnPropertyChanged(); OnPropertyChanged();
} }
} }
*/
public Visibility DigitalOutputVisibility => specs.HasValue && specs.Value.DigitalOutputCount > 0 public Visibility DigitalOutputVisibility => specs.HasValue && specs.Value.DigitalOutputCount > 0
? Visibility.Visible ? Visibility.Visible
: Visibility.Collapsed; : Visibility.Collapsed;
/*
public IEnumerable<InputOutputViewModel> DigitalOutputs public IEnumerable<InputOutputViewModel> DigitalOutputs
{ {
get => digitalOutputs; get => digitalOutputs;
@ -221,7 +178,6 @@ namespace MassiveKnob.ViewModel
} }
} }
public IList<LoggingLevelViewModel> LoggingLevels { get; } public IList<LoggingLevelViewModel> LoggingLevels { get; }
private LoggingLevelViewModel selectedLoggingLevel; private LoggingLevelViewModel selectedLoggingLevel;
@ -281,62 +237,15 @@ namespace MassiveKnob.ViewModel
ApplyRunAtStartup(); ApplyRunAtStartup();
} }
} }
*/
public string ConnectionStatusText
{
get
{
if (orchestrator == null)
return "Design-time";
switch (orchestrator.DeviceStatus)
{
case MassiveKnobDeviceStatus.Disconnected:
return Strings.DeviceStatusDisconnected;
case MassiveKnobDeviceStatus.Connecting:
return Strings.DeviceStatusConnecting;
case MassiveKnobDeviceStatus.Connected:
return Strings.DeviceStatusConnected;
default:
return null;
}
}
}
public Brush ConnectionStatusColor
{
get
{
if (orchestrator == null)
return Brushes.Fuchsia;
switch (orchestrator.DeviceStatus)
{
case MassiveKnobDeviceStatus.Disconnected:
return Brushes.DarkRed;
case MassiveKnobDeviceStatus.Connecting:
return Brushes.Orange;
case MassiveKnobDeviceStatus.Connected:
return Brushes.ForestGreen;
default:
return null;
}
}
}
// ReSharper restore UnusedMember.Global // ReSharper restore UnusedMember.Global
public SettingsViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator, ILoggingSwitch loggingSwitch) public SettingsViewModel(SimpleInjector.Container container, /*IPluginManager pluginManager, */IMassiveKnobOrchestrator orchestrator/*, ILoggingSwitch loggingSwitch*/)
{ {
this.container = container;
this.orchestrator = orchestrator; this.orchestrator = orchestrator;
this.loggingSwitch = loggingSwitch; //this.loggingSwitch = loggingSwitch;
// For design-time support // For design-time support
if (orchestrator == null) if (orchestrator == null)
@ -348,6 +257,7 @@ namespace MassiveKnob.ViewModel
SelectedMenuItem = activeMenuItem; SelectedMenuItem = activeMenuItem;
activeDeviceSubscription = orchestrator.ActiveDeviceSubject.Subscribe(info => activeDeviceSubscription = orchestrator.ActiveDeviceSubject.Subscribe(info =>
{ {
Application.Current?.Dispatcher.Invoke(() => Application.Current?.Dispatcher.Invoke(() =>
@ -355,18 +265,12 @@ namespace MassiveKnob.ViewModel
Specs = info.Specs; Specs = info.Specs;
}); });
}); });
deviceStatusSubscription = orchestrator.DeviceStatusSubject.Subscribe(status =>
{ if (orchestrator.ActiveDevice != null)
OnDependantPropertyChanged(nameof(ConnectionStatusColor)); Specs = orchestrator.ActiveDevice.Specs;
OnDependantPropertyChanged(nameof(ConnectionStatusText));
});
Devices = pluginManager.GetDevicePlugins() /*
.SelectMany(dp => dp.Devices.Select(d => new DeviceViewModel(dp, d)))
.OrderBy(d => d.Name.ToLower())
.ToList();
var allActions = new List<ActionViewModel> var allActions = new List<ActionViewModel>
{ {
new ActionViewModel(null, null) new ActionViewModel(null, null)
@ -379,12 +283,6 @@ namespace MassiveKnob.ViewModel
Actions = allActions; Actions = allActions;
if (orchestrator.ActiveDevice != null)
{
selectedDevice = Devices.Single(d => d.Device.DeviceId == orchestrator.ActiveDevice.Info.DeviceId);
SettingsControl = orchestrator.ActiveDevice.Instance.CreateSettingsControl();
Specs = orchestrator.ActiveDevice.Specs;
}
var logSettings = orchestrator.GetSettings().Log; var logSettings = orchestrator.GetSettings().Log;
@ -403,24 +301,23 @@ namespace MassiveKnob.ViewModel
var runKey = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", false); var runKey = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", false);
runAtStartup = runKey?.GetValue("MassiveKnob") != null; runAtStartup = runKey?.GetValue("MassiveKnob") != null;
*/
} }
public void Dispose() public void Dispose()
{ {
if (SettingsControl is IDisposable disposable) /*
disposable.Dispose();
DisposeInputOutputViewModels(AnalogInputs); DisposeInputOutputViewModels(AnalogInputs);
DisposeInputOutputViewModels(DigitalInputs); DisposeInputOutputViewModels(DigitalInputs);
DisposeInputOutputViewModels(AnalogOutputs); DisposeInputOutputViewModels(AnalogOutputs);
DisposeInputOutputViewModels(DigitalOutputs); DisposeInputOutputViewModels(DigitalOutputs);
*/
activeDeviceSubscription?.Dispose(); activeDeviceSubscription?.Dispose();
deviceStatusSubscription?.Dispose();
} }
/*
private void ApplyLoggingSettings() private void ApplyLoggingSettings()
{ {
orchestrator?.UpdateSettings(settings => orchestrator?.UpdateSettings(settings =>
@ -460,6 +357,7 @@ namespace MassiveKnob.ViewModel
foreach (var viewModel in viewModels) foreach (var viewModel in viewModels)
viewModel.Dispose(); viewModel.Dispose();
} }
*/
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
@ -478,7 +376,7 @@ namespace MassiveKnob.ViewModel
public class SettingsViewModelDesignTime : SettingsViewModel public class SettingsViewModelDesignTime : SettingsViewModel
{ {
public SettingsViewModelDesignTime() : base(null, null, null) public SettingsViewModelDesignTime() : base(null, null)
{ {
Specs = new DeviceSpecs(2, 2, 2, 2); Specs = new DeviceSpecs(2, 2, 2, 2);
} }