added enum for Protocol and TerminalView Types,

implemented selection of TerminalViewType in UI,
added BindingProxy to provide DataContext to elements that are not in the VisualTree of the UI
master
Jonas Arnold 3 years ago
parent 63946ad478
commit aec146cf88
  1. 30
      MultiTerm.Core/Common/ProtocolType.cs
  2. 18
      MultiTerm.Core/Common/TerminalViewType.cs
  3. 14
      MultiTerm.Core/ViewModel/ITerminalViewModel.cs
  4. 10
      MultiTerm.Core/ViewModel/SendReceiveViewModel.cs
  5. 15
      MultiTerm.Core/ViewModel/ShellViewModel.cs
  6. 8
      MultiTerm.Core/ViewModel/TerminalViewModel.cs
  7. 1
      MultiTerm.Wpf/App.xaml.cs
  8. 5
      MultiTerm.Wpf/Controls/SingleSelectSubMenu.cs
  9. 27
      MultiTerm.Wpf/Helpers/BindingProxy.cs
  10. 64
      MultiTerm.Wpf/View/ShellView.xaml

@ -0,0 +1,30 @@
using System.ComponentModel;
namespace MultiTerm.Core.Common;
public enum ProtocolType
{
/// <summary>
/// Serial Protocol
/// </summary>
[Description("Serial")]
Serial,
/// <summary>
/// USB HID Protocol
/// </summary>
[Description("USB HID")]
UsbHid,
/// <summary>
/// TCP Protocol
/// </summary>
[Description("TCP")]
Tcp,
/// <summary>
/// UDP Protocol
/// </summary>
[Description("UCP")]
Udp
}

@ -0,0 +1,18 @@
using System.ComponentModel;
namespace MultiTerm.Core.Common;
public enum TerminalViewType
{
/// <summary>
/// SendReceive View
/// </summary>
[Description("Send/Receive")]
SendReceive,
/// <summary>
/// Console View
/// </summary>
[Description("Console")]
Console
}

@ -1,4 +1,6 @@
namespace MultiTerm.Core.ViewModel;
using MultiTerm.Core.Common;
namespace MultiTerm.Core.ViewModel;
public interface ITerminalViewModel
{
@ -7,6 +9,16 @@ public interface ITerminalViewModel
/// </summary>
string Title { get; }
/// <summary>
/// Type of view.
/// </summary>
TerminalViewType ViewType { get; }
/// <summary>
/// Type of Protocol.
/// </summary>
ProtocolType ProtocolType { get; }
/// <summary>
/// Request Closing of Terminal.
/// </summary>

@ -1,7 +1,15 @@
namespace MultiTerm.Core.ViewModel;
using MultiTerm.Core.Common;
namespace MultiTerm.Core.ViewModel;
public partial class SendReceiveViewModel : TerminalViewModel
{
public override string Title => "SendReceive Tab";
public override TerminalViewType ViewType => TerminalViewType.SendReceive;
public SendReceiveViewModel() : base(ProtocolType.Serial) // TODO implement Protocol initialization
{
}
}

@ -29,6 +29,12 @@ public partial class ShellViewModel : ObservableObject
private NewlineSeparatorType defaultSendNewlineSeparator = NewlineSeparatorType.None;
#endregion
#region New Terminal Context Menu
[ObservableProperty]
private TerminalViewType selectedTerminalViewType = TerminalViewType.SendReceive;
#endregion
public ShellViewModel(IAbstractFactory<SendReceiveViewModel> sendReceiveViewModelFactory)
{
this.sendReceiveViewModelFactory = sendReceiveViewModelFactory;
@ -36,7 +42,8 @@ public partial class ShellViewModel : ObservableObject
this.AppendTerminal(this.sendReceiveViewModelFactory.Create());
}
public void AppendTerminal(ITerminalViewModel newTerminal)
[RelayCommand]
private void AppendTerminal(ITerminalViewModel? newTerminal)
{
// guard null value
if(newTerminal == null) { return; }
@ -50,11 +57,11 @@ public partial class ShellViewModel : ObservableObject
private void Terminal_ClosingEvent(object? sender, EventArgs e)
{
if(sender is not ITerminalViewModel tvm) { throw new ArgumentException(nameof(sender)); }
if(sender is not ITerminalViewModel tvm) { throw new ArgumentException($"{nameof(Terminal_ClosingEvent)} failed to convert sender to {nameof(ITerminalViewModel)}"); }
this.RemoveTerminal(tvm);
}
public void RemoveTerminal(ITerminalViewModel terminalToRemove)
private void RemoveTerminal(ITerminalViewModel terminalToRemove)
{
// guard null value
if(terminalToRemove == null) { return; }
@ -73,7 +80,7 @@ public partial class ShellViewModel : ObservableObject
}
[RelayCommand]
public void TestButtonClicked()
private void TestButtonClicked()
{
this.DefaultReceiveNewlineSeparator = NewlineSeparatorType.CR_LF;
}

@ -1,14 +1,22 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MultiTerm.Core.Common;
namespace MultiTerm.Core.ViewModel;
public abstract partial class TerminalViewModel : ObservableObject, ITerminalViewModel
{
public abstract string Title { get; }
public abstract TerminalViewType ViewType { get; }
public ProtocolType ProtocolType { get; private set; }
public event EventHandler? ClosingEvent;
public TerminalViewModel(ProtocolType protocolType)
{
this.ProtocolType = protocolType;
}
/// <summary>
/// Method to override if any closing actions are required.
/// Closing can be cancelled using the return value.

@ -3,6 +3,7 @@ using Microsoft.Extensions.Hosting;
using MultiTerm.Core.ViewModel;
using Common.StartupHelpers;
using System.Windows;
using MultiTerm.Core.Common;
namespace MultiTerm.Wpf;

@ -105,7 +105,7 @@ public class SingleSelectSubMenu : MenuItem
/// <summary>
/// Options Source Changed Handler.
/// Builds list with options and adds them to parent MenuItem (must be Menu Item!).
/// Builds list with options and adds them to parent ItemsControl (must be subtype of ItemsControl).
/// Registers menu Items in locally stored list.
/// Cannot handle changing OptionsSources. Internal list will build up.
/// </summary>
@ -114,7 +114,7 @@ public class SingleSelectSubMenu : MenuItem
// extract instance and guard null
if (d is not SingleSelectSubMenu sssm) { return; }
// extract parent instance of SSSM and guard null
if (sssm.Parent is not MenuItem parent) { return; }
if (sssm.Parent is not ItemsControl parent) { throw new ArgumentException($"Wrong parent type."); }
// iterate through new OptionsSource Values and build up list of menuItems
foreach (var item in (IEnumerable)e.NewValue)
@ -123,6 +123,7 @@ public class SingleSelectSubMenu : MenuItem
var converter = new EnumDescriptionToMenuItemConverter();
var newMenuItem = (MenuItem)converter.Convert(item, typeof(MenuItem), new object(), CultureInfo.CurrentCulture);
newMenuItem.IsCheckable = true;
newMenuItem.StaysOpenOnClick = true;
// assign to event handler and register in dictionary
newMenuItem.Checked += OnAnyItemChecked;

@ -0,0 +1,27 @@
using System.Windows;
namespace MultiTerm.Wpf.Helpers;
/// <summary>
/// Serves as a Proxy to provide DataContext for Elements that are not part of the VisualTree.
/// From: https://thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
/// </summary>
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}

@ -9,6 +9,7 @@
xmlns:vm="clr-namespace:MultiTerm.Core.ViewModel;assembly=MultiTerm.Core"
xmlns:v="clr-namespace:MultiTerm.Wpf.View"
xmlns:core_common="clr-namespace:MultiTerm.Core.Common;assembly=MultiTerm.Core"
xmlns:helpers="clr-namespace:MultiTerm.Wpf.Helpers"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="1200">
<UserControl.Resources>
@ -23,6 +24,13 @@
<x:Type TypeName="core_common:NewlineSeparatorType" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<ObjectDataProvider x:Key="TerminalViewTypeValues"
ObjectType="{x:Type sys:Enum}"
MethodName="GetValues">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="core_common:TerminalViewType" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</UserControl.Resources>
@ -58,7 +66,61 @@
<MenuItem Header="_About"/>
</Menu>
<StackPanel Orientation="Vertical" DockPanel.Dock="Left" Width="150">
<Button Name="AddNewTabButton" Content="Add new Tab">
<Button.Resources>
<!-- Binding Proxy to provide DataContext to elements within ContextMenu -->
<helpers:BindingProxy x:Key="proxy" Data="{Binding}"/>
</Button.Resources>
<Button.ContextMenu>
<ContextMenu x:Name="MainContextMenu" PlacementRectangle="{Binding RelativeSource={RelativeSource Self}}">
<!-- View types -->
<controls:SingleSelectSubMenu Title="View type"
OptionsSource="{Binding Source={StaticResource TerminalViewTypeValues}}"
SelectedMenuItem="{Binding Data.SelectedTerminalViewType, Mode=TwoWay, Source={StaticResource proxy},
Converter={StaticResource EnumDescriptionConverter}}">
</controls:SingleSelectSubMenu>
<Separator/>
<MenuItem Header="Protocol" IsEnabled="False"/>
<!-- Protocol types -->
<MenuItem Header="Serial"
Command="{Binding Data.AppendTerminalCommand, Source={StaticResource proxy}}">
<!--TODO implement parameter CommandParameter="{Binding RelativeSource={RelativeSource Self}}">-->
</MenuItem>
<MenuItem Header="USB HID" />
<MenuItem Header="TCP" />
<MenuItem Header="UCP" />
</ContextMenu>
</Button.ContextMenu>
<Button.Triggers>
<EventTrigger SourceName="AddNewTabButton" RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MainContextMenu" Storyboard.TargetProperty="(ContextMenu.IsOpen)">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<sys:Boolean>True</sys:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
<!--<RibbonMenuButton x:Name="newTerminalMenu" Label="Add new Tab" Background="Gray">
<RibbonRadioButton Label="SendReceive">
</RibbonRadioButton>
<RibbonRadioButton Label="Console">
</RibbonRadioButton>
<RibbonSeparator/>
</RibbonMenuButton>-->
</StackPanel>
<TabControl DockPanel.Dock="Right"
x:Name="terminalTabControl"
@ -74,6 +136,8 @@
<Button.Style>
<Style TargetType="Button" x:Name="CloseButtonStyle">
<Setter Property="Visibility" Value="Visible"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
</Style>
</Button.Style>
</Button>

Loading…
Cancel
Save