WPF CutomControl TemplateBinding command and ItemsSource as DependencyProperty

<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="{x:Type local:DGControl1}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:DGControl1}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <Grid>
                            <Grid.Resources>
                                <Style TargetType="Button">
                                    <Setter Property="FontSize" Value="30"/>
                                    <Setter Property="Width" Value="150"/>
                                    <Setter Property="Height" Value="70"/>
                                    <Setter Property="Margin" Value="10,0"/>
                                </Style>
                            </Grid.Resources>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition Height="*"/>
                            </Grid.RowDefinitions>
                            <StackPanel Grid.Row="0" Orientation="Horizontal">
                                <Button Content="Load" Command="{TemplateBinding LoadCommand}"/>
                                <Button Content="Clear" Command="{TemplateBinding ClearCommand}"/>
                                <Button Content="Export" Command="{TemplateBinding ExportJsonCommand}"/>
                            </StackPanel>

                            <DataGrid Grid.Row="2" 
                                      ItemsSource="{TemplateBinding BksCollection}"
                                      VirtualizingPanel.IsVirtualizing="True"
                                      VirtualizingPanel.VirtualizationMode="Recycling"
                                      VirtualizingPanel.CacheLength="5,5"
                                      VirtualizingPanel.CacheLengthUnit="Item"
                                      ScrollViewer.CanContentScroll="True"
                                      ScrollViewer.IsDeferredScrollingEnabled="True"
                                      UseLayoutRounding="True"
                                      SnapsToDevicePixels="True"
                                      AutoGenerateColumns="True"
                                      CanUserAddRows="False"
                                      IsReadOnly="True"
                                      Margin="5"
                                      BorderBrush="Cyan"
                                      BorderThickness="2"
                                      HeadersVisibility="Column">
                                <DataGrid.Resources>
                                    <Style TargetType="DataGridRow">
                                        <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>
                                </DataGrid.Resources>
                            </DataGrid>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>


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 WpfApp11.Model;

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:DGControl1/>
    ///
    /// </summary>
    public class DGControl1 : Control
    {
        static DGControl1()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(DGControl1), new FrameworkPropertyMetadata(typeof(DGControl1)));
        }



        public ICommand LoadCommand
        {
            get { return (ICommand)GetValue(LoadCommandProperty); }
            set { SetValue(LoadCommandProperty, value); }
        }

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






        public ICommand ClearCommand
        {
            get { return (ICommand)GetValue(ClearCommandProperty); }
            set { SetValue(ClearCommandProperty, value); }
        }

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






        public ICommand ExportJsonCommand
        {
            get { return (ICommand)GetValue(ExportJsonCommandProperty); }
            set { SetValue(ExportJsonCommandProperty, value); }
        }

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







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

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




    }
}

 

 

Install-Package Newtonsoft.json

 

 

//D:\C\WpfApp11\Model\Book.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace WpfApp11.Model
{
    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\WpfApp11\Model\DelegateCommand.cs
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Input;

namespace WpfApp11.Model
{
    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 ?? 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);
                }
            }
        }
    }
}


//D:\C\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="{x:Type local:DGControl1}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:DGControl1}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <Grid>
                            <Grid.Resources>
                                <Style TargetType="Button">
                                    <Setter Property="FontSize" Value="30"/>
                                    <Setter Property="Width" Value="150"/>
                                    <Setter Property="Height" Value="70"/>
                                    <Setter Property="Margin" Value="10,0"/>
                                </Style>
                            </Grid.Resources>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition Height="*"/>
                            </Grid.RowDefinitions>
                            <StackPanel Grid.Row="0" Orientation="Horizontal">
                                <Button Content="Load" Command="{TemplateBinding LoadCommand}"/>
                                <Button Content="Clear" Command="{TemplateBinding ClearCommand}"/>
                                <Button Content="Export" Command="{TemplateBinding ExportJsonCommand}"/>
                            </StackPanel>

                            <DataGrid Grid.Row="2" 
                                      ItemsSource="{TemplateBinding BksCollection}"
                                      VirtualizingPanel.IsVirtualizing="True"
                                      VirtualizingPanel.VirtualizationMode="Recycling"
                                      VirtualizingPanel.CacheLength="5,5"
                                      VirtualizingPanel.CacheLengthUnit="Item"
                                      ScrollViewer.CanContentScroll="True"
                                      ScrollViewer.IsDeferredScrollingEnabled="True"
                                      UseLayoutRounding="True"
                                      SnapsToDevicePixels="True"
                                      AutoGenerateColumns="True"
                                      CanUserAddRows="False"
                                      IsReadOnly="True"
                                      Margin="5"
                                      BorderBrush="Cyan"
                                      BorderThickness="2"
                                      HeadersVisibility="Column">
                                <DataGrid.Resources>
                                    <Style TargetType="DataGridRow">
                                        <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>
                                </DataGrid.Resources>
                            </DataGrid>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>


//D:\C\WpfApp11\DGControl1.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 WpfApp11.Model;

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:DGControl1/>
    ///
    /// </summary>
    public class DGControl1 : Control
    {
        static DGControl1()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(DGControl1), new FrameworkPropertyMetadata(typeof(DGControl1)));
        }



        public ICommand LoadCommand
        {
            get { return (ICommand)GetValue(LoadCommandProperty); }
            set { SetValue(LoadCommandProperty, value); }
        }

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






        public ICommand ClearCommand
        {
            get { return (ICommand)GetValue(ClearCommandProperty); }
            set { SetValue(ClearCommandProperty, value); }
        }

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






        public ICommand ExportJsonCommand
        {
            get { return (ICommand)GetValue(ExportJsonCommandProperty); }
            set { SetValue(ExportJsonCommandProperty, value); }
        }

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







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

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




    }
}


//D:\C\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="{Binding MainTitle}" WindowState="Maximized"  ResizeMode="NoResize">
    <Window.DataContext>
        <local:MainVM/>
    </Window.DataContext>
    <Grid>
        <local:DGControl1
            LoadCommand="{Binding LoadCmd}"
            ClearCommand="{Binding ClearCmd}"
            ExportJsonCommand="{Binding ExportCmd}"
            BksCollection="{Binding BooksCollection}"/>
    </Grid>
</Window>


//D:\C\WpfApp11\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.Text.Json.Serialization;
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 WpfApp11.Model;

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

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

        static bool isLoading = false;

        private ICommand loadCmd;
        public ICommand LoadCmd
        {
            get
            {
                if(loadCmd==null)
                {
                    loadCmd = new DelegateCommand(async s =>
                    {
                        await LoadCmdExecuted(s);
                    });
                }
                return loadCmd;
            }
        }

        private async Task LoadCmdExecuted(object? obj)
        {
            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())
                    {
                        BooksCollection = new ObservableCollection<Book>(bks);
                        MainTitle = $"{DateTime.Now},loaded {BooksCollection.Count} items," +
                            $"FirstId:{BooksCollection.FirstOrDefault()?.Id}," +
                            $"LastId:{BooksCollection.LastOrDefault()?.Id}";
                        System.Diagnostics.Debug.WriteLine(MainTitle);
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex?.Message);
            }
            finally
            {
                isLoading = false;
            }
        }


        private ICommand clearCmd;
        public ICommand ClearCmd
        {
            get
            {
                if(clearCmd==null)
                {
                    clearCmd = new DelegateCommand(ClearCmdExecuted);
                }
                return clearCmd;
            } 
        }

        private void ClearCmdExecuted(object? obj)
        {
            BooksCollection?.Clear();
        }

        private ICommand exportCmd;
        public ICommand ExportCmd
        {
            get
            {
                if(exportCmd==null)
                {
                    exportCmd = new DelegateCommand(ExportCmdExecuted);
                }
                return exportCmd;
            } 
        }

        private void ExportCmdExecuted(object? obj)
        {
            if(BooksCollection==null || !BooksCollection.Any())
            {
                MessageBox.Show("Data source is null");
                return;
            }

            SaveFileDialog dialog = new SaveFileDialog();
            dialog.Filter = "Json Files|*.json|All Files|*.*";
            dialog.FileName = $"Json_{DateTime.Now.ToString("yyyyMMddHHmmssffff")}";
            if(dialog.ShowDialog()==true)
            {
                string jsonStr=JsonConvert.SerializeObject(BooksCollection, Formatting.Indented);
                using(StreamWriter jsonWriter=new StreamWriter(dialog.FileName,false,Encoding.UTF8))
                {
                    jsonWriter.WriteLine(jsonStr);
                    MessageBox.Show($"Saved in \n{dialog.FileName}", "Save As Json");
                }
            }
        }

        private ObservableCollection<Book> booksCollection;
        public ObservableCollection<Book> BooksCollection
        {
            get
            {
                return booksCollection;
            }
            set
            {
                if (value != booksCollection)
                {
                    booksCollection = 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

 

posted @ 2026-10-01 21:58  FredGrit  阅读(3)  评论(0)    收藏  举报