WPF CustomControl customized listbox with load ,clear and export command

Install-Package Newtonsoft.json

  

//D:\C\WpfApp17\Models\Book.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace WpfApp17.Models
{
    public class Book
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public string ISBN { get; set; }
        public string Author { get; set; }
        public string Comment { get; set; }
        public string Content { get; set; }
        public string CategoryName { get; set; }
        public string Summary { get; set; }
        public string Title { get; set; }
        public string Topic { get; set; }
    }
}


//D:\C\WpfApp17\Models\DelegateCommand.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Input;

namespace WpfApp17.Models
{
    public class DelegateCommand : ICommand
    {
        private readonly Action<object?> execute;
        private readonly Func<object?, bool>? canExecute;

        public DelegateCommand(Action<object?> executeValue, Func<object?, bool>? canExecuteValue = null)
        {
            execute = executeValue;
            canExecute = canExecuteValue;
        }


        public event EventHandler? CanExecuteChanged;

        public bool CanExecute(object? parameter)
        {
            return canExecute == null ? true : canExecute(parameter);
        }

        public void Execute(object? parameter)
        {
            execute?.Invoke(parameter);
        }

        public void RaiseCanExecuteChanged()
        {
            var handler = Volatile.Read(ref CanExecuteChanged);
            if (handler != null)
            {
                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);
                    }
                }

            }
        }
    }
}


//D:\C\WpfApp17\CustomLbxcs.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;
using WpfApp17.Models;

namespace WpfApp17
{
    /// <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:WpfApp17"
    ///
    ///
    /// 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:WpfApp17;assembly=WpfApp17"
    ///
    /// 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:CustomLbxcs/>
    ///
    /// </summary>
    public class CustomLbxcs : Control
    {
        static CustomLbxcs()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomLbxcs), new FrameworkPropertyMetadata(typeof(CustomLbxcs)));
        }



        public ICommand LoadDataCommand
        {
            get { return (ICommand)GetValue(LoadDataCommandProperty); }
            set { SetValue(LoadDataCommandProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LoadDataCommand.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LoadDataCommandProperty =
            DependencyProperty.Register(nameof(LoadDataCommand),
                typeof(ICommand),
                typeof(CustomLbxcs),
                new PropertyMetadata(null));




        public ICommand ClearDataCommand
        {
            get { return (ICommand)GetValue(ClearDataCommandProperty); }
            set { SetValue(ClearDataCommandProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ClearDataCommand.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ClearDataCommandProperty =
            DependencyProperty.Register(nameof(ClearDataCommand),
                typeof(ICommand),
                typeof(CustomLbxcs),
                new PropertyMetadata(null));






        public ICommand ExportDataCommand
        {
            get { return (ICommand)GetValue(ExportDataCommandProperty); }
            set { SetValue(ExportDataCommandProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ExportDataCommand.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ExportDataCommandProperty =
            DependencyProperty.Register(nameof(ExportDataCommand),
                typeof(ICommand),
                typeof(CustomLbxcs),
                new PropertyMetadata(null));








        public ObservableCollection<Book> BooksCollection
        {
            get { return (ObservableCollection<Book>)GetValue(BooksCollectionProperty); }
            set { SetValue(BooksCollectionProperty, value); }
        }

        // Using a DependencyProperty as the backing store for BooksCollection.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BooksCollectionProperty =
            DependencyProperty.Register(nameof(BooksCollection),
                typeof(ObservableCollection<Book>),
                typeof(CustomLbxcs),
                new PropertyMetadata(null));






    }
}


//D:\C\WpfApp17\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:WpfApp17"
    xmlns:localModel="clr-namespace:WpfApp17.Models">


    <Style TargetType="{x:Type local:CustomLbxcs}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:CustomLbxcs}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <Grid Margin="5">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition Height="*"/>
                            </Grid.RowDefinitions>
                            <StackPanel Grid.Row="0"
                                        Margin="5,0"
                                        Orientation="Horizontal">
                                <StackPanel.Resources>
                                    <Style TargetType="Button">
                                        <Setter Property="Margin" Value="5,0"/>
                                        <Setter Property="FontSize" Value="30"/>
                                        <Setter Property="Width" Value="150"/>
                                    </Style>
                                </StackPanel.Resources>
                                <Button Content="Load"
                                        Command="{TemplateBinding LoadDataCommand}"/>
                                <Button Content="Clear"
                                        Command="{TemplateBinding ClearDataCommand}"/>
                                <Button Content="Export"
                                        Command="{TemplateBinding ExportDataCommand}"/>
                            </StackPanel>
                            <ListBox Grid.Row="1"
                                     Margin="5"
                                     BorderBrush="Cyan"
                                     BorderThickness="2"
                                     ItemsSource="{Binding BooksCollection,RelativeSource={RelativeSource AncestorType=local:CustomLbxcs}}"
                                     VirtualizingPanel.IsVirtualizing="True"
                                     VirtualizingPanel.VirtualizationMode="Recycling"
                                     VirtualizingPanel.CacheLength="5,5"
                                     VirtualizingPanel.CacheLengthUnit="Item"
                                     ScrollViewer.CanContentScroll="True"
                                     ScrollViewer.IsDeferredScrollingEnabled="True"
                                     SelectionMode="Extended"
                                     HorizontalAlignment="Stretch"
                                     HorizontalContentAlignment="Stretch"
                                     >
                                <ListBox.Template>
                                    <ControlTemplate TargetType="ListBox">
                                        <Border Background="{TemplateBinding Background}" 
                                                BorderBrush="{TemplateBinding BorderBrush}" 
                                                BorderThickness="{TemplateBinding BorderThickness}">
                                            <ScrollViewer HorizontalScrollBarVisibility="Auto">
                                                <ItemsPresenter HorizontalAlignment="Stretch"/>
                                            </ScrollViewer>
                                        </Border>
                                    </ControlTemplate>
                                </ListBox.Template>
                                <ListBox.ItemContainerStyle>
                                    <Style TargetType="ListBoxItem">
                                        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                                    </Style>
                                </ListBox.ItemContainerStyle>
                                <ListBox.ItemTemplate>
                                    <DataTemplate DataType="{x:Type localModel:Book}">
                                        <Border BorderBrush="Cyan"
                                                BorderThickness="1"
                                                Margin="5">
                                            <Grid>
                                                <Grid.Resources>
                                                    <Style TargetType="TextBlock">
                                                        <Setter Property="FontSize" Value="20"/>
                                                        <Style.Triggers>
                                                            <Trigger Property="IsMouseOver" Value="True">
                                                                <Setter Property="Foreground" Value="Red"/>
                                                                <Setter Property="FontWeight" Value="ExtraBold"/>
                                                            </Trigger>
                                                        </Style.Triggers>
                                                    </Style>
                                                </Grid.Resources>
                                                <Grid.RowDefinitions>
                                                    <RowDefinition/>
                                                    <RowDefinition/>
                                                    <RowDefinition/>
                                                </Grid.RowDefinitions>
                                                <Grid.ColumnDefinitions>
                                                    <ColumnDefinition/>
                                                    <ColumnDefinition/>
                                                    <ColumnDefinition/>
                                                    <ColumnDefinition/>
                                                </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 Author}"/>
                                                <TextBlock Grid.Row="0" Grid.Column="3" Text="{Binding Comment}"/>
                                                <TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding Content}"/>
                                                <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Summary}"/>
                                                <TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding Title}"/>
                                                <TextBlock Grid.Row="1" Grid.Column="3" Text="{Binding Topic}"/>
                                                <TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding CategoryName}"/>
                                                <TextBlock Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3" Text="{Binding ISBN}"/>
                                            </Grid>
                                        </Border>
                                    </DataTemplate>
                                </ListBox.ItemTemplate>
                            </ListBox>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>


//D:\C\WpfApp17\MainWindow.xaml
<Window x:Class="WpfApp17.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:WpfApp17"
        mc:Ignorable="d"
        Title="{Binding MainTitle}" 
        WindowState="Maximized">
    <Window.DataContext>
        <local:MainVM/>
    </Window.DataContext>
    <Grid>
        <local:CustomLbxcs
            LoadDataCommand="{Binding LoadCommand}"
            ClearDataCommand="{Binding ClearCommand}"
            ExportDataCommand="{Binding ExportCommand}"
            BooksCollection="{Binding BksCollection}"
            HorizontalAlignment="Stretch" 
            VerticalAlignment="Stretch"/>
    </Grid>
</Window>


//D:\C\WpfApp17\MainWindow.xaml.cs
using Microsoft.Win32;
using Newtonsoft.Json;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Net.Http;
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;
using WpfApp17.Models;

namespace WpfApp17
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }

    public class MainVM : INotifyPropertyChanged
    {
        static string url = "http://172.16.107.152:5000/getbooks/1000000";
        static HttpClient client = new HttpClient()
        {
            Timeout = TimeSpan.FromMinutes(1)
        };

        static bool isLoading = false;

        public MainVM()
        {
            if(!DesignerProperties.GetIsInDesignMode(new DependencyObject()))
            {
                _ = LoadDataAsync();
            }
        }

        private ICommand loadCommand;
        public ICommand LoadCommand
        {
            get
            {
                if (loadCommand == null)
                {
                    loadCommand = new DelegateCommand(LoadCommandExecuted); 
                }
                return loadCommand;
            }
        }

        private void LoadCommandExecuted(object? obj)
        {
            _ = LoadDataAsync();
        }

        private ICommand clearCommand;
        public ICommand ClearCommand
        {
            get
            {
                if(clearCommand == null)
                {
                    clearCommand = new DelegateCommand(ClearCommandExecuted);
                }
                return clearCommand;
            }
        }

        private void ClearCommandExecuted(object? obj)
        {
            BksCollection?.Clear();
            MainTitle = $"{DateTime.Now},cleared data!";
        }

        private ICommand exportCommand;
        public ICommand ExportCommand
        {
            get
            {
                if(exportCommand==null)
                {
                    exportCommand=new DelegateCommand(ExportCommandExecuted);
                }
                return exportCommand;
            }
        }

        private void ExportCommandExecuted(object? obj)
        {
            if(BksCollection==null || !BksCollection.Any())
            {
                MessageBox.Show($"Data is null or empty!");
                return;
            }

            SaveFileDialog dlg = new SaveFileDialog();
            dlg.Filter = $"Json Files|*.json|All Files|*.*";
            dlg.FileName = $"Export_{DateTime.Now.ToString("yyyyMMddHHmmssffff")}.json";
            if(dlg.ShowDialog()==true)
            {
                string jsonStr=JsonConvert.SerializeObject(BksCollection, Formatting.Indented);
                using(StreamWriter writer=new StreamWriter(dlg.FileName,false,Encoding.UTF8))
                {
                    writer.WriteLine(jsonStr);
                    MessageBox.Show($"{DateTime.Now}\nExported in {dlg.FileName}", $"Export Json");
                }
            }
        }

        private async Task LoadDataAsync()
        {
            if (isLoading)
            {
                return;
            }
            isLoading = true;

            MainTitle = $"{DateTime.Now},loading...";
            try
            {
                string jsonStr = await client.GetStringAsync(url);
                if (!string.IsNullOrWhiteSpace(jsonStr))
                {
                    var bks = JsonConvert.DeserializeObject<List<Book>>(jsonStr);
                    if (bks != null && bks.Any())
                    {
                        BksCollection = new ObservableCollection<Book>(bks);
                        MainTitle = $"{DateTime.Now},FirstId:{BksCollection.FirstOrDefault()?.Id}," +
                            $"LastId:{BksCollection.LastOrDefault()?.Id}";
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex?.Message);
            }
            finally
            {
                isLoading = false;
            }
        }

        private ObservableCollection<Book> bksCollection;
        public ObservableCollection<Book> BksCollection
        {
            get
            {
                return bksCollection;
            }
            set
            {
                if (value != bksCollection)
                {
                    bksCollection = value;
                    OnPropertyChanged();
                }
            }
        }

        private string mainTitle = $"{DateTime.Now}";
        public string MainTitle
        {
            get
            {
                return mainTitle;
            }
            set
            {
                if (value != mainTitle)
                {
                    mainTitle = value;
                    OnPropertyChanged();
                }
            }
        }

        public event PropertyChangedEventHandler? PropertyChanged;
        private void OnPropertyChanged([CallerMemberName] string propName = "")
        {
            var handler = Volatile.Read(ref PropertyChanged);
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propName));
            }
        }
    }
}

  

 

 

 

 

image

 

 

 

image

 

 

 

image

 

 

image

 

 

 

 

image

 

 

 

 

image

 

 

 

 

 

image

 

posted @ 2026-10-06 20:45  FredGrit  阅读(2)  评论(0)    收藏  举报