WPF Custom control in cs and Generic.xaml

//D:\C\WpfApp11\WpfApp11\Themes\Generic.xaml
<ResourceDictionary
    xmlns="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 DependencyProperties


        public 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);
                }
            }
        }
    }
}

 

 

 

 

 

 

 

 

image

 

 

 

 

 

image

 

 

 

 

 

image

 

 

 

image

 

posted @ 2026-08-15 19:35  FredGrit  阅读(5)  评论(0)    收藏  举报