Skip to content

Commit

Permalink
Add AdvancedGrid control and searchable selection list
Browse files Browse the repository at this point in the history
Renamed `Grid` to `AdvancedGrid` and refactored it to utilize a `UserControl` with enhanced methods. Introduced a `SearchableSelectionList` control, which allows searching and selecting items. Updated `TextInput` to handle text change events more appropriately and added tests for the new components.
  • Loading branch information
frankhaugen committed Aug 12, 2024
1 parent 04d1b10 commit 405b8b5
Show file tree
Hide file tree
Showing 24 changed files with 524 additions and 96 deletions.
64 changes: 64 additions & 0 deletions Frank.Wpf.Controls.Grid/AdvancedGrid.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System.Windows;
using System.Windows.Controls;

namespace Frank.Wpf.Controls.Grid;

public class AdvancedGrid : UserControl
{
private readonly Cell[,] _cells;
private readonly System.Windows.Controls.Grid _grid = new();

public AdvancedGrid(int columns, int rows)
{
_cells = new Cell[columns, rows];

for (var i = 0; i < columns; i++) _grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
for (var i = 0; i < rows; i++) _grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });

for (var i = 0; i < columns; i++)
for (var j = 0; j < rows; j++)
_cells[i, j] = new Cell(new CellPosition(i, j));

foreach (var cell in _cells) _grid.Children.Add(cell);
}

/// <summary>
/// Clears the grid of all content.
/// </summary>
public void Clear() => _grid.Children.Clear();

/// <summary>
/// Gets the underlying grid's UI element collection.
/// </summary>
/// <returns></returns>
public UIElementCollection GetUIElementCollection() => _grid.Children;

/// <summary>
/// Gets the UI elements in the grid.
/// </summary>
/// <returns></returns>
public IEnumerable<UIElement> GetUIElements() => _grid.Children.Cast<UIElement>();

/// <summary>
/// Sets the content of the cell at the specified position.
/// </summary>
/// <param name="position"></param>
/// <param name="content"></param>
/// <typeparam name="T"></typeparam>
public void SetCellContent<T>(CellPosition position, T content) where T : UIElement => _cells[position.Column, position.Row].Content = content;

/// <summary>
/// Gets the content of the cell at the specified position.
/// </summary>
/// <param name="position"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T GetCellContent<T>(CellPosition position) where T : UIElement => (T)_cells[position.Column, position.Row].Content;

/// <summary>
/// Gets the content of the cell at the specified position without knowing the type.
/// </summary>
/// <param name="position"></param>
/// <returns></returns>
public object GetCellContent(CellPosition position) => _cells[position.Column, position.Row].Content;
}
1 change: 1 addition & 0 deletions Frank.Wpf.Controls.Grid/Cell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ public void SetPosition(CellPosition position)
{
_position = position;
this.SetGridPosition(_position.Column, _position.Row);
this.SetGridSpan(_position.ColumnSpan, _position.RowSpan);
}
}
9 changes: 8 additions & 1 deletion Frank.Wpf.Controls.Grid/CellPosition.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
namespace Frank.Wpf.Controls.Grid;

public readonly record struct CellPosition(int Column, int Row);
/// <summary>
/// Describes the position of a cell in a grid with optional spans.
/// </summary>
/// <param name="Column"></param>
/// <param name="Row"></param>
/// <param name="ColumnSpan"></param>
/// <param name="RowSpan"></param>
public readonly record struct CellPosition(int Column, int Row, int ColumnSpan = 1, int RowSpan = 1);
35 changes: 0 additions & 35 deletions Frank.Wpf.Controls.Grid/Grid.cs

This file was deleted.

2 changes: 1 addition & 1 deletion Frank.Wpf.Controls.JsonRenderer/JsonRendererTreeView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Frank.Wpf.Controls.JsonRenderer;

public class JsonRendererTreeView : TreeView
internal class JsonRendererTreeView : TreeView
{
private JsonElement? _selectedElement;
private JsonDocument? Document { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>

</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Frank.Wpf.Controls.SimpleInputs\Frank.Wpf.Controls.SimpleInputs.csproj" />
</ItemGroup>

</Project>
14 changes: 14 additions & 0 deletions Frank.Wpf.Controls.SearchableList/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# SearchableSelectionList<T>

A control that allows the user to search for an item in a list of items. The list of items is displayed in a dropdown list. The user can type in the search box to filter the list of items. The user can also use the up and down arrow keys to navigate through the list of items.

## Usage

```csharp

```


## License

![License](https://img.shields.io/github/license/frankhaugen/Frank.Wpf)
93 changes: 93 additions & 0 deletions Frank.Wpf.Controls.SearchableList/SearchableSelectionList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System.Windows;
using System.Windows.Controls;
using Frank.Wpf.Controls.SimpleInputs;

namespace Frank.Wpf.Controls.SearchableList;

/// <summary>
/// A list that allows searching and selecting items.
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class SearchableSelectionList<T> : UserControl
{
private readonly StackPanel _stackPanel = new();
private readonly SearchBox _searchBox;
private readonly GroupBox _groupBox = new() { Header = "Selected" };
private readonly ScrollViewer _scrollViewer = new();
private readonly CustomListBox<T> _listBox = new();

/// <summary>
/// Creates a new instance of <see cref="SearchableSelectionList{T}"/>.
/// </summary>
public SearchableSelectionList()
{
_searchBox = new SearchBox("Search", x =>
{
if (DisplayFunc != null)
{
_listBox.FilterFunc = item => DisplayFunc(item)
.Contains(x.SearchText ?? string.Empty, StringComparison.InvariantCultureIgnoreCase);
}
});

_scrollViewer.Content = _listBox;
_stackPanel.Children.Add(_searchBox);
_stackPanel.Children.Add(_scrollViewer);
_stackPanel.Children.Add(_groupBox);

MinWidth = 128;
Content = _stackPanel;
}

/// <summary>
/// Determines if the search box is visible. Default is true.
/// </summary>
public bool IsSearchBoxVisible
{
get => _searchBox.Visibility == Visibility.Visible;
set => _searchBox.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
}

/// <summary>
/// Determines if the selected item box is visible. Default is true.
/// </summary>
public bool IsSelectedBoxVisible
{
get => _groupBox.Visibility == Visibility.Visible;
set => _groupBox.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
}

/// <summary>
/// The items in the list.
/// </summary>
public required IEnumerable<T> Items
{
get => _listBox.Items;
set => _listBox.Items = value;
}

/// <summary>
/// The function that determines how to display an item.
/// </summary>
/// <remarks> Search functionality is based on this function. </remarks>
public required Func<T, string> DisplayFunc
{
get => _listBox.DisplayFunc;
init => _listBox.DisplayFunc = value;
}

/// <summary>
/// This action is called when an item is selected.
/// </summary>
public required Action<T> SelectionChangedAction
{
init
{
_listBox.SelectionChangedAction = item =>
{
_groupBox.Content = new TextBlock { Text = DisplayFunc(item) };
value?.Invoke(item);
};
}
}
}
112 changes: 112 additions & 0 deletions Frank.Wpf.Controls.SimpleInputs/CustomListBox.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System.Windows;
using System.Windows.Controls;

namespace Frank.Wpf.Controls.SimpleInputs;

public class CustomListBox<T> : UserControl
{
private readonly ListBox _listBox;

public CustomListBox()
{
_listBox = new ListBox();
Content = _listBox;

_listBox.SelectionChanged += ListBox_SelectionChanged;
}

public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register(
nameof(Items),
typeof(IEnumerable<T>),
typeof(CustomListBox<T>),
new PropertyMetadata(new List<T>(), OnItemsChanged));

public static readonly DependencyProperty DisplayFuncProperty =
DependencyProperty.Register(
nameof(DisplayFunc),
typeof(Func<T, string>),
typeof(CustomListBox<T>),
new PropertyMetadata(null, OnDisplayFuncChanged));

public static readonly DependencyProperty FilterFuncProperty =
DependencyProperty.Register(
nameof(FilterFunc),
typeof(Func<T, bool>),
typeof(CustomListBox<T>),
new PropertyMetadata(null, OnFilterFuncChanged));

public static readonly DependencyProperty SelectionChangedActionProperty =
DependencyProperty.Register(
nameof(SelectionChangedAction),
typeof(Action<T>),
typeof(CustomListBox<T>),
new PropertyMetadata(null));

public IEnumerable<T> Items
{
get => (IEnumerable<T>)GetValue(ItemsProperty);
set => SetValue(ItemsProperty, value);
}

public Func<T, string> DisplayFunc
{
get => (Func<T, string>)GetValue(DisplayFuncProperty);
set => SetValue(DisplayFuncProperty, value);
}

public Func<T, bool> FilterFunc
{
get => (Func<T, bool>)GetValue(FilterFuncProperty);
set => SetValue(FilterFuncProperty, value);
}

public Action<T> SelectionChangedAction
{
get => (Action<T>)GetValue(SelectionChangedActionProperty);
set => SetValue(SelectionChangedActionProperty, value);
}

private static void OnItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (CustomListBox<T>)d;
control.ApplyFilter();
}

private static void OnDisplayFuncChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (CustomListBox<T>)d;
control.ApplyFilter();
}

private static void OnFilterFuncChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (CustomListBox<T>)d;
control.ApplyFilter();
}

private void ApplyFilter()
{
_listBox.Items.Clear();

if (Items == null || DisplayFunc == null)
{
return;
}

var filteredItems = FilterFunc != null ? Items.Where(FilterFunc) : Items;

foreach (var item in filteredItems)
{
_listBox.Items.Add(new ListBoxItem { Content = DisplayFunc(item), Tag = item });
}
}

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_listBox.SelectedItem is ListBoxItem selectedItem && SelectionChangedAction != null)
{
SelectionChangedAction((T)selectedItem.Tag);
}
}
}
Loading

0 comments on commit 405b8b5

Please sign in to comment.