port from perforce
This commit is contained in:
107
intromat/NodeNetworkToolkit/NodeList/NodeListView.xaml
Normal file
107
intromat/NodeNetworkToolkit/NodeList/NodeListView.xaml
Normal file
@@ -0,0 +1,107 @@
|
||||
<UserControl x:Class="NodeNetwork.Toolkit.NodeList.NodeListView"
|
||||
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:controls="clr-namespace:NodeNetwork.Views.Controls;assembly=NodeNetwork"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="500" d:DesignWidth="320"
|
||||
Background="{DynamicResource BackgroundColor}"
|
||||
x:Name="self">
|
||||
<UserControl.Resources>
|
||||
<DataTemplate x:Key="tilesTemplate">
|
||||
<Grid>
|
||||
<controls:ViewModelViewHostNoAnimations VerticalAlignment="Top" Margin="0, 5, 0, 5" ViewModel="{Binding}" KeyboardNavigation.TabNavigation="None">
|
||||
<controls:ViewModelViewHostNoAnimations.LayoutTransform>
|
||||
<ScaleTransform ScaleX="0.5" ScaleY="0.5"/>
|
||||
</controls:ViewModelViewHostNoAnimations.LayoutTransform>
|
||||
</controls:ViewModelViewHostNoAnimations>
|
||||
<Grid Background="#01000000" MouseMove="OnNodeMouseMove" Cursor="Hand"/>
|
||||
<!-- Overlay absorbs mouse events -->
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<ItemsPanelTemplate x:Key="tilesItemsPanelTemplate">
|
||||
<WrapPanel />
|
||||
</ItemsPanelTemplate>
|
||||
<ControlTemplate x:Key="tilesItemsControlTemplate">
|
||||
<ItemsPresenter HorizontalAlignment="Stretch"/>
|
||||
</ControlTemplate>
|
||||
|
||||
<DataTemplate x:Key="listTemplate">
|
||||
<Grid HorizontalAlignment="Stretch" Margin="0, 0, 0, 0" MouseMove="OnNodeMouseMove">
|
||||
<Grid.Style>
|
||||
<Style TargetType="Grid">
|
||||
<Setter Property="Background" Value="{Binding ListEntryBackgroundBrush, ElementName=self}"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="{Binding ListEntryBackgroundMouseOverBrush, ElementName=self}"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Grid.Style>
|
||||
<TextBlock Margin="10,5,5,5"><Run Text="{Binding Name}"/></TextBlock>
|
||||
<Viewbox Stretch="Uniform" Width="10" Height="20" HorizontalAlignment="Right" Margin="5" Cursor="SizeAll">
|
||||
<Canvas Width="12.5" Height="30" Background="#01ffffff">
|
||||
<Rectangle Canvas.Left="0" Canvas.Top="0" Width="4" Height="4" Fill="{Binding ListEntryHandleBrush, ElementName=self}" StrokeThickness="0" />
|
||||
<Rectangle Canvas.Left="7.5" Canvas.Top="0" Width="4" Height="4" Fill="{Binding ListEntryHandleBrush, ElementName=self}" StrokeThickness="0" />
|
||||
<Rectangle Canvas.Left="0" Canvas.Top="7.5" Width="4" Height="4" Fill="{Binding ListEntryHandleBrush, ElementName=self}" StrokeThickness="0" />
|
||||
<Rectangle Canvas.Left="7.5" Canvas.Top="7.5" Width="4" Height="4" Fill="{Binding ListEntryHandleBrush, ElementName=self}" StrokeThickness="0" />
|
||||
<Rectangle Canvas.Left="0" Canvas.Top="15" Width="4" Height="4" Fill="{Binding ListEntryHandleBrush, ElementName=self}" StrokeThickness="0" />
|
||||
<Rectangle Canvas.Left="7.5" Canvas.Top="15" Width="4" Height="4" Fill="{Binding ListEntryHandleBrush, ElementName=self}" StrokeThickness="0" />
|
||||
<Rectangle Canvas.Left="0" Canvas.Top="22.5" Width="4" Height="4" Fill="{Binding ListEntryHandleBrush, ElementName=self}" StrokeThickness="0" />
|
||||
<Rectangle Canvas.Left="7.5" Canvas.Top="22.5" Width="4" Height="4" Fill="{Binding ListEntryHandleBrush, ElementName=self}" StrokeThickness="0" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<ItemsPanelTemplate x:Key="listItemsPanelTemplate">
|
||||
<StackPanel HorizontalAlignment="Stretch"/>
|
||||
</ItemsPanelTemplate>
|
||||
<ControlTemplate x:Key="listItemsControlTemplate">
|
||||
<ItemsPresenter HorizontalAlignment="Stretch"/>
|
||||
</ControlTemplate>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Row="0">
|
||||
<TextBlock x:Name="titleLabel" Margin="10, 0, 0, 0" Padding="0, 10, 0, 10" VerticalAlignment="Center" HorizontalAlignment="Left" FontSize="18" FontFamily="Segoe UI Semilight" Text="Test"/>
|
||||
<ComboBox x:Name="viewComboBox" Margin="0,0,10,0" VerticalAlignment="Center" HorizontalAlignment="Right" Width="65" Height="20" />
|
||||
</Grid>
|
||||
|
||||
<Grid Row="1" x:Name="searchBoxGrid" Margin="25,7,25,0" MaxWidth="300" VerticalAlignment="Top">
|
||||
<TextBox x:Name="searchBox" TextWrapping="Wrap"/>
|
||||
<TextBlock Margin="5, 0, 0, 0" x:Name="emptySearchBoxMessage" Text="Search..." IsHitTestVisible="False" Foreground="LightGray"/>
|
||||
</Grid>
|
||||
|
||||
<Grid Row="2" Margin="10,10,10,0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
|
||||
<ScrollViewer>
|
||||
<ItemsControl x:Name="elementsList" IsTabStop="False">
|
||||
<ItemsControl.GroupStyle>
|
||||
<GroupStyle>
|
||||
<GroupStyle.ContainerStyle>
|
||||
<Style TargetType="{x:Type GroupItem}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate>
|
||||
<Expander Header="{Binding Name}" IsExpanded="True">
|
||||
<ItemsPresenter />
|
||||
</Expander>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</GroupStyle.ContainerStyle>
|
||||
</GroupStyle>
|
||||
</ItemsControl.GroupStyle>
|
||||
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
<TextBlock x:Name="emptyMessage" Text="No matching nodes found" HorizontalAlignment="Center" VerticalAlignment="Top"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
166
intromat/NodeNetworkToolkit/NodeList/NodeListView.xaml.cs
Normal file
166
intromat/NodeNetworkToolkit/NodeList/NodeListView.xaml.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using DynamicData;
|
||||
using NodeNetwork.Utilities;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace NodeNetwork.Toolkit.NodeList
|
||||
{
|
||||
public partial class NodeListView : IViewFor<NodeListViewModel>
|
||||
{
|
||||
#region ViewModel
|
||||
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register(nameof(ViewModel),
|
||||
typeof(NodeListViewModel), typeof(NodeListView), new PropertyMetadata(null));
|
||||
|
||||
public NodeListViewModel ViewModel
|
||||
{
|
||||
get => (NodeListViewModel)GetValue(ViewModelProperty);
|
||||
set => SetValue(ViewModelProperty, value);
|
||||
}
|
||||
|
||||
object IViewFor.ViewModel
|
||||
{
|
||||
get => ViewModel;
|
||||
set => ViewModel = (NodeListViewModel)value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Show/Hide properties
|
||||
public static readonly DependencyProperty ShowSearchProperty =
|
||||
DependencyProperty.Register(nameof(ShowSearch), typeof(bool), typeof(NodeListView), new PropertyMetadata(true));
|
||||
public static readonly DependencyProperty ShowDisplayModeSelectorProperty =
|
||||
DependencyProperty.Register(nameof(ShowDisplayModeSelector), typeof(bool), typeof(NodeListView), new PropertyMetadata(true));
|
||||
public static readonly DependencyProperty ShowTitleProperty =
|
||||
DependencyProperty.Register(nameof(ShowTitle), typeof(bool), typeof(NodeListView), new PropertyMetadata(true));
|
||||
|
||||
public bool ShowSearch
|
||||
{
|
||||
get { return (bool)GetValue(ShowSearchProperty); }
|
||||
set { SetValue(ShowSearchProperty, value); }
|
||||
}
|
||||
|
||||
public bool ShowDisplayModeSelector
|
||||
{
|
||||
get { return (bool)GetValue(ShowDisplayModeSelectorProperty); }
|
||||
set { SetValue(ShowDisplayModeSelectorProperty, value); }
|
||||
}
|
||||
|
||||
public bool ShowTitle
|
||||
{
|
||||
get { return (bool)GetValue(ShowTitleProperty); }
|
||||
set { SetValue(ShowTitleProperty, value); }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Colors
|
||||
public static readonly DependencyProperty ListEntryBackgroundBrushProperty =
|
||||
DependencyProperty.Register(nameof(ListEntryBackgroundBrush), typeof(Brush), typeof(NodeListView), new PropertyMetadata(new SolidColorBrush(Colors.White)));
|
||||
|
||||
public Brush ListEntryBackgroundBrush
|
||||
{
|
||||
get { return (Brush)GetValue(ListEntryBackgroundBrushProperty); }
|
||||
set { SetValue(ListEntryBackgroundBrushProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ListEntryBackgroundMouseOverBrushProperty =
|
||||
DependencyProperty.Register(nameof(ListEntryBackgroundMouseOverBrush), typeof(Brush), typeof(NodeListView), new PropertyMetadata(new SolidColorBrush(Color.FromRgb(0xf7, 0xf7, 0xf7))));
|
||||
|
||||
public Brush ListEntryBackgroundMouseOverBrush
|
||||
{
|
||||
get { return (Brush)GetValue(ListEntryBackgroundMouseOverBrushProperty); }
|
||||
set { SetValue(ListEntryBackgroundMouseOverBrushProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ListEntryHandleBrushProperty =
|
||||
DependencyProperty.Register(nameof(ListEntryHandleBrush), typeof(Brush), typeof(NodeListView), new PropertyMetadata(new SolidColorBrush(Color.FromRgb(0x99, 0x99, 0x99))));
|
||||
|
||||
public Brush ListEntryHandleBrush
|
||||
{
|
||||
get { return (Brush)GetValue(ListEntryHandleBrushProperty); }
|
||||
set { SetValue(ListEntryHandleBrushProperty, value); }
|
||||
}
|
||||
#endregion
|
||||
|
||||
public CollectionViewSource CVS { get; } = new CollectionViewSource();
|
||||
|
||||
public NodeListView()
|
||||
{
|
||||
InitializeComponent();
|
||||
if (DesignerProperties.GetIsInDesignMode(this)) { return; }
|
||||
|
||||
viewComboBox.ItemsSource = Enum.GetValues(typeof(NodeListViewModel.DisplayMode)).Cast<NodeListViewModel.DisplayMode>();
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
this.Bind(ViewModel, vm => vm.Display, v => v.viewComboBox.SelectedItem).DisposeWith(d);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.Display, v => v.elementsList.ItemTemplate,
|
||||
displayMode => displayMode == NodeListViewModel.DisplayMode.Tiles
|
||||
? Resources["tilesTemplate"]
|
||||
: Resources["listTemplate"])
|
||||
.DisposeWith(d);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.Display, v => v.elementsList.ItemsPanel,
|
||||
displayMode => displayMode == NodeListViewModel.DisplayMode.Tiles
|
||||
? Resources["tilesItemsPanelTemplate"]
|
||||
: Resources["listItemsPanelTemplate"])
|
||||
.DisposeWith(d);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.Display, v => v.elementsList.Template,
|
||||
displayMode => displayMode == NodeListViewModel.DisplayMode.Tiles
|
||||
? Resources["tilesItemsControlTemplate"]
|
||||
: Resources["listItemsControlTemplate"])
|
||||
.DisposeWith(d);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.SearchQuery, v => v.searchBox.Text).DisposeWith(d);
|
||||
|
||||
this.WhenAnyValue(v => v.ViewModel.VisibleNodes).Switch().Bind(out var bindableList).Subscribe().DisposeWith(d);
|
||||
CVS.Source = bindableList;
|
||||
elementsList.ItemsSource = CVS.View;
|
||||
|
||||
this.WhenAnyObservable(v => v.ViewModel.VisibleNodes.CountChanged)
|
||||
.Select(count => count == 0)
|
||||
.BindTo(this, v => v.emptyMessage.Visibility).DisposeWith(d);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.Title, v => v.titleLabel.Text).DisposeWith(d);
|
||||
this.OneWayBind(ViewModel, vm => vm.EmptyLabel, v => v.emptyMessage.Text).DisposeWith(d);
|
||||
|
||||
this.WhenAnyValue(v => v.searchBox.IsFocused, v => v.searchBox.Text)
|
||||
.Select(t => !t.Item1 && string.IsNullOrWhiteSpace(t.Item2))
|
||||
.BindTo(this, v => v.emptySearchBoxMessage.Visibility)
|
||||
.DisposeWith(d);
|
||||
|
||||
this.WhenAnyValue(v => v.ShowSearch)
|
||||
.BindTo(this, v => v.searchBoxGrid.Visibility).DisposeWith(d);
|
||||
this.WhenAnyValue(v => v.ShowDisplayModeSelector)
|
||||
.BindTo(this, v => v.viewComboBox.Visibility).DisposeWith(d);
|
||||
this.WhenAnyValue(v => v.ShowTitle)
|
||||
.BindTo(this, v => v.titleLabel.Visibility).DisposeWith(d);
|
||||
});
|
||||
}
|
||||
|
||||
private void OnNodeMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
NodeViewModel nodeVM = ((FrameworkElement)sender).DataContext as NodeViewModel;
|
||||
if (nodeVM == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var nodeFactory = ViewModel.NodeTemplates.Items.First(t => t.Instance == nodeVM).Factory;
|
||||
NodeViewModel newNodeVM = nodeFactory();
|
||||
|
||||
DragDrop.DoDragDrop(this, new DataObject("nodeVM", newNodeVM), DragDropEffects.Copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
130
intromat/NodeNetworkToolkit/NodeList/NodeListViewModel.cs
Normal file
130
intromat/NodeNetworkToolkit/NodeList/NodeListViewModel.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using DynamicData;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace NodeNetwork.Toolkit.NodeList
|
||||
{
|
||||
/// <summary>
|
||||
/// A viewmodel for a UI List component that contains NodeViewModels
|
||||
/// and can be used to let the user add new nodes to a network.
|
||||
/// </summary>
|
||||
public class NodeListViewModel : ReactiveObject
|
||||
{
|
||||
static NodeListViewModel()
|
||||
{
|
||||
NNViewRegistrar.AddRegistration(() => new NodeListView(), typeof(IViewFor<NodeListViewModel>));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The formatting mode of the list.
|
||||
/// </summary>
|
||||
public enum DisplayMode
|
||||
{
|
||||
/// <summary>
|
||||
/// The nodes are displayed graphically in a grid.
|
||||
/// </summary>
|
||||
Tiles,
|
||||
/// <summary>
|
||||
/// The node names are displayed as text in a list.
|
||||
/// </summary>
|
||||
List
|
||||
}
|
||||
|
||||
#region Title
|
||||
/// <summary>
|
||||
/// The string that is displayed at the top of the list
|
||||
/// </summary>
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
set => this.RaiseAndSetIfChanged(ref _title, value);
|
||||
}
|
||||
private string _title;
|
||||
#endregion
|
||||
|
||||
#region EmptyLabel
|
||||
/// <summary>
|
||||
/// The string that is displayed when VisibleNodes is empty.
|
||||
/// </summary>
|
||||
public string EmptyLabel
|
||||
{
|
||||
get => _emptyLabel;
|
||||
set => this.RaiseAndSetIfChanged(ref _emptyLabel, value);
|
||||
}
|
||||
private string _emptyLabel = "";
|
||||
#endregion
|
||||
|
||||
#region DisplayMode
|
||||
/// <summary>
|
||||
/// The way the list of available nodes is formatted.
|
||||
/// </summary>
|
||||
public DisplayMode Display
|
||||
{
|
||||
get => _display;
|
||||
set => this.RaiseAndSetIfChanged(ref _display, value);
|
||||
}
|
||||
private DisplayMode _display;
|
||||
#endregion
|
||||
|
||||
#region NodeTemplates
|
||||
/// <summary>
|
||||
/// List of all the available nodes in the list.
|
||||
/// </summary>
|
||||
public ISourceList<NodeTemplate> NodeTemplates { get; } = new SourceList<NodeTemplate>();
|
||||
#endregion
|
||||
|
||||
#region VisibleNodes
|
||||
/// <summary>
|
||||
/// List of nodes that are actually visible in the list.
|
||||
/// This list is based on Nodes and SearchQuery.
|
||||
/// </summary>
|
||||
public IObservableList<NodeViewModel> VisibleNodes { get; }
|
||||
#endregion
|
||||
|
||||
#region SearchQuery
|
||||
/// <summary>
|
||||
/// The current search string that is used to filter Nodes into VisibleNodes.
|
||||
/// </summary>
|
||||
public string SearchQuery
|
||||
{
|
||||
get => _searchQuery;
|
||||
set => this.RaiseAndSetIfChanged(ref _searchQuery, value);
|
||||
}
|
||||
private string _searchQuery = "";
|
||||
#endregion
|
||||
|
||||
public NodeListViewModel()
|
||||
{
|
||||
Title = "Add node";
|
||||
EmptyLabel = "No matching nodes found.";
|
||||
Display = DisplayMode.Tiles;
|
||||
|
||||
var onQueryChanged = this.WhenAnyValue(vm => vm.SearchQuery)
|
||||
.Throttle(TimeSpan.FromMilliseconds(200), RxApp.MainThreadScheduler)
|
||||
.Publish();
|
||||
onQueryChanged.Connect();
|
||||
VisibleNodes = NodeTemplates.Connect()
|
||||
.AutoRefreshOnObservable(_ => onQueryChanged)
|
||||
.Transform(t => t.Instance)
|
||||
.AutoRefresh(node => node.Name)
|
||||
.Filter(n => (n.Name ?? "").ToUpper().Contains(SearchQuery?.ToUpper() ?? ""))
|
||||
.AsObservableList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new node type to the list.
|
||||
/// Every time a node is added to a network from this list, the factory function will be called to create a new instance of the viewmodel type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The subtype of NodeViewModel to add to the list.</typeparam>
|
||||
/// <param name="factory">The factory function to create a new instance of T</param>
|
||||
public void AddNodeType<T>(Func<T> factory) where T : NodeViewModel
|
||||
{
|
||||
NodeTemplates.Add(new NodeTemplate(factory));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user