//D:\C\WpfApp11\WpfApp11\Themes\Generic.xaml <ResourceDictionaryxmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="clr-namespace:WpfApp11"><Style TargetType="TextBlock" x:Key="TbkStyle"><Setter Property="FontSize" Value="30"/><Setter Property="FontWeight" Value="Normal"/><Setter Property="HorizontalAlignment" Value="Center"/><Style.Triggers><Trigger Property="IsMouseOver" Value="True"><Setter Property="Foreground" Value="Red"/></Trigger></Style.Triggers></Style><DataTemplate DataType="{x:Type local:Book}" x:Key="BookRowDataTemplate"><Border BorderBrush="Gray"BorderThickness="1"Margin="5"><Grid><Grid.Resources><Style TargetType="TextBlock" BasedOn="{StaticResource TbkStyle}"><Setter Property="ToolTip"><Setter.Value><StackPanel Orientation="Horizontal"><TextBlock Text="GroupName:" FontSize="50"/><TextBlock Text="{Binding CategoryName}" FontSize="50"/></StackPanel></Setter.Value> </Setter></Style></Grid.Resources><Grid.RowDefinitions><RowDefinition/><RowDefinition/></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="*"/><ColumnDefinition Width="4*"/></Grid.ColumnDefinitions><TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Id}"/><TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Name}"/><TextBlock Grid.Row="0" Grid.Column="2" Text="{Binding ISBN}"/><TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding Summary}"/><TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Title}"/><TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding Topic}"/></Grid></Border></DataTemplate><Style TargetType="{x:Type local:BookCategoryControl1}"><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="{x:Type local:BookCategoryControl1}"><Border Background="{TemplateBinding Background}"BorderBrush="{TemplateBinding BorderBrush}"BorderThickness="{TemplateBinding BorderThickness}"><Grid><Grid.RowDefinitions><RowDefinition/><RowDefinition Height="10*"/></Grid.RowDefinitions><ComboBox ItemsSource="{Binding BookCategoryNames, RelativeSource={RelativeSource TemplatedParent}}"SelectedItem="{Binding SelectedBookCategoryName, RelativeSource={RelativeSource TemplatedParent}}"Grid.Row="0"FontSize="30" Margin="10"HorizontalContentAlignment="Center"VerticalContentAlignment="Center"/><ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto"><GroupBox BorderBrush="Cyan" BorderThickness="2"Header="{Binding SelectedBookCategoryName,RelativeSource={RelativeSource TemplatedParent}}"FontSize="50"FontWeight="ExtraBold"Margin="5"><ItemsControl ItemsSource="{Binding FilteredBksList,RelativeSource={RelativeSource TemplatedParent}}"ItemTemplate="{StaticResource BookRowDataTemplate}"/></GroupBox></ScrollViewer></Grid></Border></ControlTemplate></Setter.Value></Setter></Style> </ResourceDictionary>//D:\C\WpfApp11\WpfApp11\BookCategoryControl1.cs using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes;namespace WpfApp11 {/// <summary>/// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.////// Step 1a) Using this custom control in a XAML file that exists in the current project./// Add this XmlNamespace attribute to the root element of the markup file where it is /// to be used:////// xmlns:MyNamespace="clr-namespace:WpfApp11"///////// Step 1b) Using this custom control in a XAML file that exists in a different project./// Add this XmlNamespace attribute to the root element of the markup file where it is /// to be used:////// xmlns:MyNamespace="clr-namespace:WpfApp11;assembly=WpfApp11"////// You will also need to add a project reference from the project where the XAML file lives/// to this project and Rebuild to avoid compilation errors:////// Right click on the target project in the Solution Explorer and/// "Add Reference"->"Projects"->[Browse to and select this project]///////// Step 2)/// Go ahead and use your control in the XAML file.////// <MyNamespace:BookCategoryControl1/>////// </summary>public class BookCategoryControl1 : Control{static BookCategoryControl1(){DefaultStyleKeyProperty.OverrideMetadata(typeof(BookCategoryControl1), new FrameworkPropertyMetadata(typeof(BookCategoryControl1)));}#region DependencyPropertiespublic ObservableCollection<Book> AllBooks{get { return (ObservableCollection<Book>)GetValue(AllBooksProperty); }set { SetValue(AllBooksProperty, value); }}// Using a DependencyProperty as the backing store for AllBooks. This enables animation, styling, binding, etc...public static readonly DependencyProperty AllBooksProperty =DependencyProperty.Register(nameof(AllBooks),typeof(ObservableCollection<Book>),typeof(BookCategoryControl1),new PropertyMetadata(null, OnAllBooksChanged));private static void OnAllBooksChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){var categoryControl = d as BookCategoryControl1;if (categoryControl != null){if (categoryControl.AllBooks != null && categoryControl.AllBooks.Any()){var tempGroups = categoryControl.AllBooks.GroupBy(x => x.CategoryName);if (tempGroups != null && tempGroups.Any()){categoryControl.BookCategoryNames = tempGroups.Select(x => x.Key).ToList();categoryControl.SelectedBookCategoryName = categoryControl.BookCategoryNames.FirstOrDefault();}}}}public List<string> BookCategoryNames{get { return (List<string>)GetValue(BookCategoryNamesProperty); }set { SetValue(BookCategoryNamesProperty, value); }}// Using a DependencyProperty as the backing store for BookCategoryNames. This enables animation, styling, binding, etc...public static readonly DependencyProperty BookCategoryNamesProperty =DependencyProperty.Register(nameof(BookCategoryNames),typeof(List<string>),typeof(BookCategoryControl1), new PropertyMetadata(null));public string SelectedBookCategoryName{get { return (string)GetValue(SelectedBookCategoryNameProperty); }set { SetValue(SelectedBookCategoryNameProperty, value); }}// Using a DependencyProperty as the backing store for SelectedBookCategoryName. This enables animation, styling, binding, etc...public static readonly DependencyProperty SelectedBookCategoryNameProperty =DependencyProperty.Register(nameof(SelectedBookCategoryName),typeof(string),typeof(BookCategoryControl1),new PropertyMetadata(null, OnSelectedBookCategoryNameChanged));private static void OnSelectedBookCategoryNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){var categoryControl = d as BookCategoryControl1;if (categoryControl != null&& !string.IsNullOrWhiteSpace(categoryControl.SelectedBookCategoryName)&& categoryControl.AllBooks != null&& categoryControl.AllBooks.Any()){categoryControl.FilteredBksList = categoryControl.AllBooks.Where(x => x.CategoryName == categoryControl.SelectedBookCategoryName).ToList().Select(x => new Book(){Id = x.Id,Name = x.Name,CategoryName = x.CategoryName,ISBN = x.ISBN,Summary = x.Summary,Title = x.Title,Topic = x.Topic}).ToList();}}public List<Book> FilteredBksList{get { return (List<Book>)GetValue(FilteredBksListProperty); }set { SetValue(FilteredBksListProperty, value); }}// Using a DependencyProperty as the backing store for FilteredBksList. This enables animation, styling, binding, etc...public static readonly DependencyProperty FilteredBksListProperty =DependencyProperty.Register(nameof(FilteredBksList),typeof(List<Book>),typeof(BookCategoryControl1), new PropertyMetadata(null));#endregion} }//D:\C\WpfApp11\WpfApp11\MainWindow.xaml <Window x:Class="WpfApp11.MainWindow"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"xmlns:local="clr-namespace:WpfApp11"mc:Ignorable="d"Title="MainWindow" WindowState="Maximized"><Window.DataContext><local:MainVM/></Window.DataContext><Grid><local:BookCategoryControl1 AllBooks="{Binding Books}"/><Grid.ContextMenu><ContextMenu><MenuItem Header="Load Data"FontSize="30"Width="300"Command="{Binding LoadDataCommand}"/></ContextMenu></Grid.ContextMenu></Grid> </Window>//D:\C\WpfApp11\WpfApp11\MainWindow.xaml.cs using Newtonsoft.Json; using System.Collections.ObjectModel; using System.ComponentModel; using System.Runtime.CompilerServices; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes;namespace WpfApp11 {/// <summary>/// Interaction logic for MainWindow.xaml/// </summary>public partial class MainWindow : Window{public MainWindow(){InitializeComponent();}}public class MainVM : INotifyPropertyChanged{WcfService5.BookService bkService;private bool isLoading = false;public MainVM(){if (!DesignerProperties.GetIsInDesignMode(new DependencyObject())){bkService = new WcfService5.BookService();InitBooks(100);}}private void InitBooks(int cnt = 100){if (isLoading){return;}isLoading = true;try{var bks = bkService.GetBooks(cnt);if (bks != null && bks.Any()){List<Book> tempBks = new List<Book>();tempBks = bks.Select(x => new Book(){Id = x.Id,Name = x.Name,CategoryName = x.CategoryName,ISBN = x.ISBN,Summary = x.Summary,Title = x.Title,Topic = x.Topic}).ToList();Books = new ObservableCollection<Book>(tempBks);}}catch (Exception ex){MessageBox.Show(ex?.Message);}finally{isLoading = false;}}private ICommand loadDataCommand;public ICommand LoadDataCommand{get{if (loadDataCommand == null){loadDataCommand = new DelegateCommand(LoadDataCommandExecuted);}return loadDataCommand;}}private void LoadDataCommandExecuted(object? obj){InitBooks();}private ObservableCollection<Book> books;public ObservableCollection<Book> Books{get{return books;}set{if (value != books){books = value;OnPropertyChanged();}}}public event PropertyChangedEventHandler? PropertyChanged;private void OnPropertyChanged([CallerMemberName] string propName = ""){var handler = Volatile.Read(ref PropertyChanged);if (handler == null){return;}handler(this, new PropertyChangedEventArgs(propName));}}public class Book{public long Id { get; set; }public string Name { get; set; }public string ISBN { get; set; }public string CategoryName { get; set; }public string Summary { get; set; }public string Title { get; set; }public string Topic { get; set; }}public class DelegateCommand : ICommand{private Action<object?> execute;private Func<object?, bool>? canExecute;public DelegateCommand(Action<object?> executeValue, Func<object?, bool>? canExecuteValue = null){execute = executeValue ?? throw new ArgumentNullException(nameof(executeValue));canExecute = canExecuteValue;}public event EventHandler? CanExecuteChanged;public bool CanExecute(object? parameter){return canExecute == null ? true : canExecute(parameter);}public void Execute(object? parameter){execute(parameter);}public void RaiseCanExecuteChanged(){var handler = Volatile.Read(ref CanExecuteChanged);if (handler == null){return;}var dispatcher = Application.Current?.Dispatcher;if (dispatcher != null){if (dispatcher.CheckAccess()){handler(this, EventArgs.Empty);}else{dispatcher.Invoke(() =>{handler(this, EventArgs.Empty);}, System.Windows.Threading.DispatcherPriority.Background);}}}} }



