implemented caching behaviour for tab, disables virtualization on tab controls,

cleaned up format of MultiFormatTextBox
master
Jonas Arnold 3 years ago
parent c6d20bcc6a
commit d8191bc80b
  1. 281
      MultiTerm.Wpf.CustomControl/Behaviours/TabContent.cs
  2. 8
      MultiTerm.Wpf.CustomControl/MultiFormatTextBox/Format.cs
  3. 38
      MultiTerm.Wpf.CustomControl/MultiFormatTextBox/MultiFormatTextBox.cs
  4. 4
      MultiTerm.Wpf.CustomControl/MultiTerm.Wpf.CustomControl.csproj
  5. 13
      MultiTerm.Wpf/View/ShellView.xaml

@ -0,0 +1,281 @@
// TabContent.cs, version 1.2
// The code in this file is Copyright (c) Ivan Krivyakov
// See http://www.ikriv.com/legal.php for more information
//
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
namespace MultiTerm.Wpf.CustomControl;
/// <summary>
/// Attached properties for persistent tab control
/// </summary>
/// <remarks>By default WPF TabControl bound to an ItemsSource destroys visual state of invisible tabs.
/// Set ikriv:TabContent.IsCached="True" to preserve visual state of each tab.
/// </remarks>
public static class TabContent
{
public static bool GetIsCached(DependencyObject obj)
{
return (bool)obj.GetValue(IsCachedProperty);
}
public static void SetIsCached(DependencyObject obj, bool value)
{
obj.SetValue(IsCachedProperty, value);
}
/// <summary>
/// Controls whether tab content is cached or not
/// </summary>
/// <remarks>When TabContent.IsCached is true, visual state of each tab is preserved (cached), even when the tab is hidden</remarks>
public static readonly DependencyProperty IsCachedProperty =
DependencyProperty.RegisterAttached("IsCached", typeof(bool), typeof(TabContent), new UIPropertyMetadata(false, OnIsCachedChanged));
public static DataTemplate GetTemplate(DependencyObject obj)
{
return (DataTemplate)obj.GetValue(TemplateProperty);
}
public static void SetTemplate(DependencyObject obj, DataTemplate value)
{
obj.SetValue(TemplateProperty, value);
}
/// <summary>
/// Used instead of TabControl.ContentTemplate for cached tabs
/// </summary>
public static readonly DependencyProperty TemplateProperty =
DependencyProperty.RegisterAttached("Template", typeof(DataTemplate), typeof(TabContent), new UIPropertyMetadata(null));
public static DataTemplateSelector GetTemplateSelector(DependencyObject obj)
{
return (DataTemplateSelector)obj.GetValue(TemplateSelectorProperty);
}
public static void SetTemplateSelector(DependencyObject obj, DataTemplateSelector value)
{
obj.SetValue(TemplateSelectorProperty, value);
}
/// <summary>
/// Used instead of TabControl.ContentTemplateSelector for cached tabs
/// </summary>
public static readonly DependencyProperty TemplateSelectorProperty =
DependencyProperty.RegisterAttached("TemplateSelector", typeof(DataTemplateSelector), typeof(TabContent), new UIPropertyMetadata(null));
[EditorBrowsable(EditorBrowsableState.Never)]
public static TabControl GetInternalTabControl(DependencyObject obj)
{
return (TabControl)obj.GetValue(InternalTabControlProperty);
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static void SetInternalTabControl(DependencyObject obj, TabControl value)
{
obj.SetValue(InternalTabControlProperty, value);
}
// Using a DependencyProperty as the backing store for InternalTabControl. This enables animation, styling, binding, etc...
[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly DependencyProperty InternalTabControlProperty =
DependencyProperty.RegisterAttached("InternalTabControl", typeof(TabControl), typeof(TabContent), new UIPropertyMetadata(null, OnInternalTabControlChanged));
[EditorBrowsable(EditorBrowsableState.Never)]
public static ContentControl GetInternalCachedContent(DependencyObject obj)
{
return (ContentControl)obj.GetValue(InternalCachedContentProperty);
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static void SetInternalCachedContent(DependencyObject obj, ContentControl value)
{
obj.SetValue(InternalCachedContentProperty, value);
}
// Using a DependencyProperty as the backing store for InternalCachedContent. This enables animation, styling, binding, etc...
[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly DependencyProperty InternalCachedContentProperty =
DependencyProperty.RegisterAttached("InternalCachedContent", typeof(ContentControl), typeof(TabContent), new UIPropertyMetadata(null));
[EditorBrowsable(EditorBrowsableState.Never)]
public static object GetInternalContentManager(DependencyObject obj)
{
return (object)obj.GetValue(InternalContentManagerProperty);
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static void SetInternalContentManager(DependencyObject obj, object value)
{
obj.SetValue(InternalContentManagerProperty, value);
}
// Using a DependencyProperty as the backing store for InternalContentManager. This enables animation, styling, binding, etc...
public static readonly DependencyProperty InternalContentManagerProperty =
DependencyProperty.RegisterAttached("InternalContentManager", typeof(object), typeof(TabContent), new UIPropertyMetadata(null));
private static void OnIsCachedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj == null) return;
var tabControl = obj as TabControl;
if (tabControl == null)
{
throw new InvalidOperationException("Cannot set TabContent.IsCached on object of type " + args.NewValue.GetType().Name +
". Only objects of type TabControl can have TabContent.IsCached property.");
}
bool newValue = (bool)args.NewValue;
if (!newValue)
{
if (args.OldValue != null && ((bool)args.OldValue))
{
throw new NotImplementedException("Cannot change TabContent.IsCached from True to False. Turning tab caching off is not implemented");
}
return;
}
EnsureContentTemplateIsNull(tabControl);
tabControl.ContentTemplate = CreateContentTemplate();
EnsureContentTemplateIsNotModified(tabControl);
}
private static DataTemplate CreateContentTemplate()
{
const string xaml =
"<DataTemplate><Border b:TabContent.InternalTabControl=\"{Binding RelativeSource={RelativeSource AncestorType=TabControl}}\" /></DataTemplate>";
var context = new ParserContext();
context.XamlTypeMapper = new XamlTypeMapper(new string[0]);
context.XamlTypeMapper.AddMappingProcessingInstruction("b", typeof(TabContent).Namespace, typeof(TabContent).Assembly.FullName);
context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
context.XmlnsDictionary.Add("b", "b");
var template = (DataTemplate)XamlReader.Parse(xaml, context);
return template;
}
private static void OnInternalTabControlChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj == null) return;
var container = obj as Decorator;
if (container == null)
{
var message = "Cannot set TabContent.InternalTabControl on object of type " + obj.GetType().Name +
". Only controls that derive from Decorator, such as Border can have a TabContent.InternalTabControl.";
throw new InvalidOperationException(message);
}
if (args.NewValue == null) return;
if (!(args.NewValue is TabControl))
{
throw new InvalidOperationException("Value of TabContent.InternalTabControl cannot be of type " + args.NewValue.GetType().Name +", it must be of type TabControl");
}
var tabControl = (TabControl)args.NewValue;
var contentManager = GetContentManager(tabControl, container);
contentManager.UpdateSelectedTab();
}
private static ContentManager GetContentManager(TabControl tabControl, Decorator container)
{
var contentManager = (ContentManager)GetInternalContentManager(tabControl);
if (contentManager != null)
{
/*
* Content manager already exists for the tab control. This means that tab content template is applied
* again, and new instance of the Border control (container) has been created. The old container
* referenced by the content manager is no longer visible and needs to be replaced
*/
contentManager.ReplaceContainer(container);
}
else
{
// create content manager for the first time
contentManager = new ContentManager(tabControl, container);
SetInternalContentManager(tabControl, contentManager);
}
return contentManager;
}
private static void EnsureContentTemplateIsNull(TabControl tabControl)
{
if (tabControl.ContentTemplate != null)
{
throw new InvalidOperationException("TabControl.ContentTemplate value is not null. If TabContent.IsCached is True, use TabContent.Template instead of ContentTemplate");
}
}
private static void EnsureContentTemplateIsNotModified(TabControl tabControl)
{
var descriptor = DependencyPropertyDescriptor.FromProperty(TabControl.ContentTemplateProperty, typeof(TabControl));
descriptor.AddValueChanged(tabControl, (sender, args) =>
{
throw new InvalidOperationException("Cannot assign to TabControl.ContentTemplate when TabContent.IsCached is True. Use TabContent.Template instead");
});
}
public class ContentManager
{
TabControl _tabControl;
Decorator _border;
public ContentManager(TabControl tabControl, Decorator border)
{
_tabControl = tabControl;
_border = border;
_tabControl.SelectionChanged += (sender, args) => { UpdateSelectedTab(); };
}
public void ReplaceContainer(Decorator newBorder)
{
if (Object.ReferenceEquals(_border, newBorder)) return;
_border.Child = null; // detach any tab content that old border may hold
_border = newBorder;
}
public void UpdateSelectedTab()
{
_border.Child = GetCurrentContent();
}
private ContentControl GetCurrentContent()
{
var item = _tabControl.SelectedItem;
if (item == null) return null;
var tabItem = _tabControl.ItemContainerGenerator.ContainerFromItem(item);
if (tabItem == null) return null;
var cachedContent = TabContent.GetInternalCachedContent(tabItem);
if (cachedContent == null)
{
cachedContent = new ContentControl
{
DataContext = item,
ContentTemplate = TabContent.GetTemplate(_tabControl),
ContentTemplateSelector = TabContent.GetTemplateSelector(_tabControl)
};
cachedContent.SetBinding(ContentControl.ContentProperty, new Binding());
TabContent.SetInternalCachedContent(tabItem, cachedContent);
}
return cachedContent;
}
}
}

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using System.Windows; using System.Windows;
using MultiTerm.Core.Types; using MultiTerm.Core.Types;
@ -15,21 +14,18 @@ internal class Format
public string Name { get; set; } public string Name { get; set; }
public Brush BackgroundBrush { get; set; } public Brush BackgroundBrush { get; set; }
public FormatType AssociatedFormatType { get; set; } public FormatType AssociatedFormatType { get; set; }
public Predicate<Key> IsKeyValid { get; set; } public Format(string name, Brush backgroundBrush, FormatType associatedFormatType)
public Format(string name, Brush backgroundBrush, FormatType associatedFormatType, Predicate<Key> keyValidator)
{ {
this.Name = name; this.Name = name;
this.BackgroundBrush = backgroundBrush; this.BackgroundBrush = backgroundBrush;
this.AssociatedFormatType = associatedFormatType; this.AssociatedFormatType = associatedFormatType;
this.IsKeyValid = keyValidator;
} }
public Format(string name, string backgroundColorResourceName, FormatType associatedFormatType, Predicate<Key> keyValidator) public Format(string name, string backgroundColorResourceName, FormatType associatedFormatType)
{ {
this.Name = name; this.Name = name;
this.backgroundColorResourceName = backgroundColorResourceName; this.backgroundColorResourceName = backgroundColorResourceName;
this.BackgroundBrush = Brushes.White; // set background brush to white this.BackgroundBrush = Brushes.White; // set background brush to white
this.AssociatedFormatType = associatedFormatType; this.AssociatedFormatType = associatedFormatType;
this.IsKeyValid = keyValidator;
} }
public static List<string> GetListOfNames(IEnumerable<Format> formats) public static List<string> GetListOfNames(IEnumerable<Format> formats)
{ {

@ -19,15 +19,12 @@ public class MultiFormatTextBox : Control
private static readonly Brush defaultBackgroundBrush = Brushes.White; private static readonly Brush defaultBackgroundBrush = Brushes.White;
private static readonly List<Format> formats = new() private static readonly List<Format> formats = new()
{ {
// character input, accepts all keys // character input
new Format("CHAR", "MultiFormatTextBox.CHAR.Background", FormatType.Character, new Format("CHAR", "MultiFormatTextBox.CHAR.Background", FormatType.Character),
delegate(Key k) { return true; }), // hex input
// hex input, ignores all keys that are not inbetween 0 and F new Format("HEX", "MultiFormatTextBox.HEX.Background", FormatType.Hexadecimal),
new Format("HEX", "MultiFormatTextBox.HEX.Background", FormatType.Hexadecimal, // binary input
delegate(Key k) { return (k >= Key.D0 && k <= Key.F); }), new Format("BIN", "MultiFormatTextBox.BIN.Background", FormatType.Binary)
// binary input, ignores all keys except 0 and 1
new Format("BIN", "MultiFormatTextBox.BIN.Background", FormatType.Binary,
delegate(Key k) { return (k == Key.D0 || k == Key.D1); })
}; };
#endregion #endregion
@ -46,9 +43,11 @@ public class MultiFormatTextBox : Control
#region Dependency Properties #region Dependency Properties
public static readonly DependencyProperty CurrentMultiFormatStringProperty = public static readonly DependencyProperty CurrentMultiFormatStringProperty =
DependencyProperty.Register("CurrentMultiFormatString", DependencyProperty.Register(
typeof(MultiFormatString), typeof(MultiFormatTextBox), name: "CurrentMultiFormatString",
new PropertyMetadata(null, OnCurrentMultiFormatStringChanged)); propertyType: typeof(MultiFormatString),
ownerType: typeof(MultiFormatTextBox),
typeMetadata: new FrameworkPropertyMetadata(null, OnCurrentMultiFormatStringChanged));
public static readonly RoutedEvent EnterPressedEvent; public static readonly RoutedEvent EnterPressedEvent;
@ -79,7 +78,13 @@ public class MultiFormatTextBox : Control
EnterPressedEvent = EventManager.RegisterRoutedEvent("EnterPressed", EnterPressedEvent = EventManager.RegisterRoutedEvent("EnterPressed",
RoutingStrategy.Bubble, typeof(RoutedEventArgs), RoutingStrategy.Bubble, typeof(RoutedEventArgs),
typeof(MultiFormatTextBox)); typeof(MultiFormatTextBox));
}
~MultiFormatTextBox()
{
// unregister events
var incc = this.CurrentMultiFormatString as INotifyCollectionChanged;
incc.CollectionChanged -= this.MultiFormatString_CollectionChanged;
} }
public override void OnApplyTemplate() public override void OnApplyTemplate()
@ -126,12 +131,17 @@ public class MultiFormatTextBox : Control
{ {
// extract instance and guard null // extract instance and guard null
if (d is not MultiFormatTextBox mftb) { return; } if (d is not MultiFormatTextBox mftb) { return; }
if (e.NewValue is not MultiFormatString newString) { return; }
// register to collection changed event // register to collection changed event
if (mftb.CurrentMultiFormatString is INotifyCollectionChanged incc) if (mftb.CurrentMultiFormatString is INotifyCollectionChanged incc)
{ {
incc.CollectionChanged += mftb.MultiFormatString_CollectionChanged; // remove handler
incc.CollectionChanged -= mftb.MultiFormatString_CollectionChanged;
// add new handler if not null
if(e.NewValue != null)
{
incc.CollectionChanged += mftb.MultiFormatString_CollectionChanged;
}
} }
} }

@ -14,4 +14,8 @@
<ProjectReference Include="..\MultiTerm.Core\MultiTerm.Core.csproj" /> <ProjectReference Include="..\MultiTerm.Core\MultiTerm.Core.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Behaviours\" />
</ItemGroup>
</Project> </Project>

@ -131,18 +131,27 @@
<custom_controls:ExtendedTabControl Grid.Row="1" Grid.Column="1" <custom_controls:ExtendedTabControl Grid.Row="1" Grid.Column="1"
x:Name="terminalTabControl" x:Name="terminalTabControl"
IsSynchronizedWithCurrentItem="True"
custom_controls:TabContent.IsCached="True"
ItemsSource="{Binding TerminalViewModels}" ItemsSource="{Binding TerminalViewModels}"
SelectedItem="{Binding SelectedTerminalViewModel}"> SelectedItem="{Binding SelectedTerminalViewModel}">
<!-- Register additional Tab ViewModels here --> <!-- View Model Types -->
<custom_controls:ExtendedTabControl.Resources> <custom_controls:ExtendedTabControl.Resources>
<DataTemplate DataType="{x:Type vm:SendReceiveViewModel}"> <DataTemplate DataType="{x:Type vm:SendReceiveViewModel}">
<v:SendReceiveView/> <v:SendReceiveView/>
</DataTemplate> </DataTemplate>
</custom_controls:ExtendedTabControl.Resources> </custom_controls:ExtendedTabControl.Resources>
<!-- Content of tab with special caching behaviour -->
<custom_controls:TabContent.Template>
<DataTemplate>
<ContentControl Content="{Binding}"/>
</DataTemplate>
</custom_controls:TabContent.Template>
<!-- Tab Header Template -->
<custom_controls:ExtendedTabControl.ItemTemplate> <custom_controls:ExtendedTabControl.ItemTemplate>
<!-- Tab Template -->
<DataTemplate> <DataTemplate>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<Ellipse Width="16" Height="16" Margin="0 0 8 0" <Ellipse Width="16" Height="16" Margin="0 0 8 0"

Loading…
Cancel
Save