How to create WPF user control

How to create WPF user control? Following the previous article, I introduced how to encapsulate the base class of ViewModel , but as the project gets bigger, it is really tiring to do one function point by one function point. In many systems, there are many similar functions locally, and the data display is almost the same.

How to create WPF user control

Perhaps the only difference is the placement and display style; this kind of thing, the realization of one function and one function, it is a bit moving towards the state of 996; therefore, in the JHRS framework , it also reflects the lazy The idea of ​​working is that it can be packaged into a control, and resolutely made a control for everyone to enjoy.

How to create WPF user control
How to create WPF user control

Of course, user controls can be nested in user controls. A page with complex functions can be composed of many user controls. In the end, you will find that the more elegant the user controls are packaged, the more complex functional pages are not difficult. Just like building blocks, throw the controls up, and the data binding is done. Finally, if you want to adjust the style, just adjust it slightly.

WPF user control package

Because Prism is introduced in the framework to modularize each subsystem, when we encapsulate WPF user controls, we divide it into two situations, one is to encapsulate the user control common to the entire system, and the other is to each sub-module’s own User controls; user controls that are common to the entire system need to maintain high scalability and flexibility. Even if functions are added to the control later, try not to affect the page that has already used the control . Pages can flexibly set some attributes to meet the functional requirements of different pages (Page).

Only three basic user controls are encapsulated in the framework, the DataGrid with dynamic columns, the Combobox with adjustable interfaces, and the dynamic paging table.

DataGrid with dynamic columns in JHRS development framework

Most management systems are inseparable from tables to display data, and WPF projects basically use DataGrid to display data; friends who are familiar with WPF development know that if you manually press a table, the code thief is annoying, and you need to list one by one.

Write code and bind data for the columns; and the idea provided in the JHRS framework is based on annotations (custom BindDescriptionAttribute class is used to describe each column) dynamically generate each column of data and automatically bind the data, for complex columns, such as If a column is displayed as a drop-down box (ComoboBox) or a more complex display, you only need to define the DataTemplate in the Resources (Resources), and then load it dynamically. The same routine applies to the operation column of each row.

The DataGrid package idea for dynamic columns is: write a DataGridEx class, inherited from the DataGrid class, in the DataGridEx class, you need to define a dependency property we call DataSource, use it to bind the data, and then assign the DataSource in the callback function of the DataSourceProperty Give the original ItemSource property, and finally rewrite the OnInitialized method, and pass the data source to the DataGrid extension method GenerateColumns to dynamically generate columns to complete the WPF user control encapsulation. See the code below for details.

DataGridEx class source code

/// <summary>
    /// Lightweight DataGrid extension
    /// </summary>
    public class DataGridEx: DataGrid
    {
        /// <summary>
        /// Constructor
        /// </summary>
        public DataGridEx()
        {
            this.AutoGenerateColumns = false;
            this.Loaded += DataGridEx_Loaded;
            this.LoadingRow += PagingDataList_LoadingRow;
        }

        /// <summary>
        /// Add style to the table
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DataGridEx_Loaded(object sender, RoutedEventArgs e)
        {
            this.CanUserAddRows = false;
        }

        /// <summary>
        /// Generate serial number
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PagingDataList_LoadingRow(object sender, DataGridRowEventArgs e)
        {
            if (EnableRowNumber)
                //Need pagination
                e.Row.Header = e.Row.GetIndex() + 1;
        }

        /// <summary>
        /// Operation column key
        /// </summary>
        public string OperatingKey {get; set;} = string.Empty;
        /// <summary>
        /// The width of the operation column
        /// </summary>
        public DataGridLength OperationWidth {get; set;}

        /// <summary>
        /// Whether to enable the serial number
        /// </summary>
        public bool EnableRowNumber {get; set;} = true;

        /// <summary>
        /// Prohibited columns
        /// </summary>
        public string DisableCloumn {get; set;}

        public IEnumerable<object> DataSource
        {
            get {return (IEnumerable<object>)GetValue(DataSourceProperty);}
            set {SetValue(DataSourceProperty, value);}
        }

        private bool IsGenerateColumns = false;
        // Using a DependencyProperty as the backing store for DataSource. This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DataSourceProperty =
            DependencyProperty.Register("DataSource", typeof(IEnumerable<object>), typeof(DataGridEx), new PropertyMetadata((d, e) =>
            {
                DataGridEx u = d as DataGridEx;
                u.ItemsSource = u.DataSource;

                if (u.IsGenerateColumns || u.DataSource == null) return;
                var index = 0;
                if (u.EnableRowNumber)
                {
                    var acolumn = new DataGridTextColumn
                    {
                        Header = "Serial Number",
                        Width = new DataGridLength(50),
                        Binding = new Binding("Header") {RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(DataGridRow), 1)}
                    };
                    u.Columns.Insert(0, acolumn);
                    index++;
                }
                u.GenerateColumns(index, u.ItemsSource, u.OperatingKey, u.OperationWidth);
                u.IsGenerateColumns = true;

            }));


        //protected override void OnInitialized(EventArgs e)
        //{
        // if (IsGenerateColumns || ItemsSource == null) return;
        // var index = 0;
        // if (EnableRowNumber)
        // {
        // var acolumn = new DataGridTextColumn
        // {
        // Header = "Serial Number",
        // Width = new DataGridLength(50),
        // Binding = new Binding("Header") {RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(DataGridRow), 1)}
        // };
        // this.Columns.Insert(0, acolumn);
        // index++;
        //}
        // this.GenerateColumns(index, ItemsSource, OperatingKey, OperationWidth);
        // IsGenerateColumns = true;
        //}
    }

How to create WPF user control

The above is basically the complete source code, please refer to github here .

DataGridExtensions extension class source code

    /// <summary>
    /// DataGrid extension method
    /// </summary>
    public static class DataGridExtensions
    {
        /// <summary>
        /// Dynamically generate columns
        /// </summary>
        /// <param name="dataGrid">DataGrid control instance</param>
        /// <param name="index">Column insertion position</param>
        /// <param name="data">data source</param>
        /// <param name="operationKey">operation column resource</param>
        /// <param name="operationWidth">operation column width</param>
        public static void GenerateColumns(this DataGrid dataGrid, int index, object data, string operationKey, DataGridLength operationWidth)
        {
            IList<BindDescriptionAttribute> list = GetColumns(data);
            //Window win = Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive);
            //Page page = win.GetChildObject<Page>("page");
            //if (page == null) throw new Exception("The (Page) page object whose name is page in the current window has not been obtained. Reason: No Name is set for Page, and the name must be [page]!");

            Page page = GetParentObject<Page>(dataGrid, "page");

            for (int i = 0; i <list.Count; i++)
            {
                switch (list[i].ShowAs)
                {
                    case ShowScheme. Normal text:
                        dataGrid.Columns.Insert(i + index, new DataGridTextColumn
                        {
                            Header = list[i].HeaderName,
                            Binding = new Binding(list[i].PropertyName),
                            Width = list[i].Width
                        });
                        break;
                    case ShowScheme.Customization:
                        if (page.FindResource(list[i].ResourceKey) != null)
                        {
                            DataGridTemplateColumn val = new DataGridTemplateColumn();
                            val.Header = list[i].HeaderName;
                            val.Width = list[i].Width;
                            val.CellTemplate = page.FindResource(list[i].ResourceKey) as DataTemplate;
                            dataGrid.Columns.Insert(i + index, val);
                        }
                        break;
                }
            }
            if (!string.IsNullOrWhiteSpace(operationKey) && page != null)
            {
                var resource = page.FindResource(operationKey);
                if (resource!=null)
                {
                   
                    var col = new DataGridTemplateColumn() {Header = "operation", Width = operationWidth };
                    col.CellTemplate = resource as DataTemplate;
                    dataGrid.Columns.Add(col);
                }
            }
        }

        /// <summary>
        /// Get the mapping relationship between data source objects and columns
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private static IList<BindDescriptionAttribute> GetColumns(object data)
        {
            List<BindDescriptionAttribute> list = new List<BindDescriptionAttribute>();
            var pros = data.GetType().GenericTypeArguments[0].GetProperties();
            foreach (var item in pros)
            {
                var a = item.GetCustomAttribute<BindDescriptionAttribute>();
                if (a != null) {a.PropertyName = item.Name; list.Add(a);}
            }
            return list.OrderBy(x => x.DisplayIndex).ToArray();
        }

        /// <summary>
        /// Find the parent control
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static T GetParentObject<T>(DependencyObject obj, string name) where T: FrameworkElement
        {
            DependencyObject parent = VisualTreeHelper.GetParent(obj);

            while (parent != null)
            {
                if (parent is T && (((T)parent).Name == name | string.IsNullOrEmpty(name)))
                {
                    return (T)parent;
                }

                parent = VisualTreeHelper.GetParent(parent);
            }

            return null;
        }
    }

How to create WPF user control

See here for completeness .

BindDescriptionAttribute annotation class

In the data returned from the server by calling the web api, you need to define the relevant entity class in the local WPF project, and mark the binding description class (BindDescriptionAttribute) on the attribute of the entity class. This class directly describes how the exhibition surface is displayed ( Generally use DataGrid, you can expand irregular forms according to this idea), this is also the preparation work before WPF user control encapsulation.

/// <summary>
    /// DataGrid binding data source description
    /// </summary>
    public class BindDescriptionAttribute: Attribute
    {
        /// <summary>
        /// column name
        /// </summary>
        public string HeaderName {get; set;}

        /// <summary>
        /// shown as
        /// </summary>
        public ShowScheme ShowAs {get; set;}

        /// <summary>
        /// display order
        /// </summary>
        public int DisplayIndex {get; set;}

        /// <summary>
        /// DataGrid column binding property name
        /// </summary>
        public string PropertyName {get; set;}

        /// <summary>
        /// Key of the content template in the application
        /// </summary>
        public string ResourceKey {get; set;}

        /// <summary>
        /// column width
        /// </summary>
        public DataGridLength Width {get; set;}

        /// <summary>
        /// Column width ByGrid
        /// </summary>
        public GridLength CloumnWidth {get; set;}


        /// <summary>
        /// DataGrid binding data source description
        /// </summary>
        /// <param name="headerName">Column name</param>
        /// <param name="showAs">show as</param>
        /// <param name="width">width</param>
        /// <param name="displayIndex">display order</param>
        /// <param name="resourceKey">Custom column Key</param>
        public BindDescriptionAttribute(string headerName, ShowScheme showAs = ShowScheme. normal text, string width = "Auto", int displayIndex = 0, string resourceKey = "")
        {
            HeaderName = headerName;
            DisplayIndex = displayIndex;
            ResourceKey = resourceKey;
            ShowAs = showAs;
            var convert = new DataGridLengthConverter();
            Width = (DataGridLength)convert.ConvertFrom(width);
            var gridCOnvert = new GridLengthConverter();
            CloumnWidth = (GridLength)gridCOnvert.ConvertFrom(width);

            if (showAs == ShowScheme.Custom && string.IsNullOrWhiteSpace(resourceKey))
                throw new ArgumentException($"{nameof(resourceKey)} parameters need to be specified when customizing columns!");
        }
    }

    /// <summary>
    /// Display mode
    /// </summary>
    public enum ShowScheme
    {
        Normal text = 1,
        Custom = 4
    }

How to create WPF user control

The above enumeration ShowScheme describes the display scheme of the corresponding column, whether to use ordinary text or load a data template (DataTemplate).

How to use DataGridEx to realize dynamic table function

To use your own extended dynamic DataGrid is actually the same as using a regular DataGrid, except that the data binding is to use the DataSource property. The following is the dynamic table xaml encapsulated by the WPF user control, as shown in the following code:

<Page
    x:Class="JHRS.RegisterManagement.Views.RegisterList"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:prism="http://prismlibrary.com/"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:c="clr-namespace:JHRS.Core.Controls.Common;assembly=JHRS.Core"
    xmlns:b="http://schemas.microsoft.com/xaml/behaviors" 
    prism:ViewModelLocator.AutoWireViewModel="True" 
    mc:Ignorable="d" 
    d:DesignHeight="450" d:DesignWidth="800"
    x:Name="page"
    Title="RegisterList" Background="White">
    
    <b:Interaction.Triggers>
        <b:EventTrigger EventName="Loaded" >
            <b:InvokeCommandAction Command="{Binding LoadedCommand}" CommandParameter="{Binding ElementName=page}" />
        </b:EventTrigger>
    </b:Interaction.Triggers>
    <Grid x:Name="maskContainer">
        <c:DataGridEx DataSource="{Binding PageData}" IsReadOnly="True"/>
    </Grid>
</Page>

How to create WPF user control

In the above code , <c:DataGridEx DataSource=”{Binding PageData}” IsReadOnly=”True”/> This line is the usage of dynamic table binding data encapsulated by WPF user control, and PageData is an IEnumerable<object defined by ViewModel >Properties, you only need to call the interface in the ViewModel of the current page (Page) to get data and assign values ​​to PageData, as shown in the following code:

/// <summary>
         /// Bind paging data
         /// </summary>
         [WaitComplete]
         protected async override Task<object> BindPagingData()
         {
             List<Account> list = new List<Account>();
             for (int i = 0; i <15; i++)
             {
                 list.Add(new Account
                 {
                     Name = "Zhao Jiaren" + i,
                     RegTime = DateTime.Now.AddDays(i),
                     RoleName = "Administrator" + i,
                     Title = "Unemployed" + i,
                     UserID = 100 + i
                 });
             }
             PageData = list;
             await Task.Delay(200);
             return true;
         }

How to create WPF user control

The Account class is defined in this way.

public class Account
     {
         [BindDescription("User ID")]
         public int UserID {get; set;}
         [BindDescription("Username")]
         public string Name {get; set;}
         [BindDescription("Registration Time")]
         public DateTime RegTime {get; set;}
         [BindDescription("Character name is stable")]
         public string RoleName {get; set;}
         [BindDescription("level")]
         public string Title {get; set;}
     }

How to create WPF user control

The complete code can be found here. The following picture is the final result:

WPF user control package
How to create WPF user control

Combobox extended control

The native Combobox is a drop-down box control provided by WPF. If the agreement is in the entire system, the default item of all drop-down box controls is Please select, as shown in the following figure:

WPF user control package
How to create WPF user control

If you add this uniformly, you do this in the framework. First customize a BaseComboBox class, inherit from the ComboBox class, and add a default item in the code. The code is as follows:

using JHRS.Http; 
using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Linq; 
using System.Net.Http; 
using System.Text; 
using System.Threading.Tasks; 
using System.Windows;
 using System.Windows.Controls;

namespace JHRS.Core.Controls.Common 
{ 
/// 
/// Drop-down box control base class /// 
public abstract class BaseComboBox: ComboBox 
{ 
/// /// Drop-down box data source /// 
public IList Data 
{ 
get {return (IList)GetValue(DataProperty);} 
set 
{ 
AddDefault(value); 
SetValue(DataProperty, value); 
} 
}

/// <summary>
    /// Logged in to get Token client object
    /// </summary>
    protected HttpClient AuthClient => AuthHttpClient.Instance;

    /// <summary>
    /// server configuration
    /// </summary>
    protected string BaseUrl => AuthHttpClient.Instance.BaseAddress!.ToString();

    // Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(IList), typeof(BaseComboBox), new PropertyMetadata(null, (d, e) =>
        {
            BaseComboBox c = (BaseComboBox)d;
            var list = e.NewValue as IList;
            if (list != null)
                c.AddDefault(list);
            c.Data = list;
            c.ItemsSource = list;
        }));

    /// <summary>
    /// Constructor
    /// </summary>
    public BaseComboBox()
    {
        this.Initialized += OnInitialized;
    }

    /// <summary>
    /// Drop-down box initialization event, subclass implementation, can load their own data.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected abstract void OnInitialized(object sender, EventArgs e);

    private string DefaultSelectedValue = "-1";
    private string DefaultSelectedText = "—Please select—";

    /// <summary>
    /// Add default item: please select
    /// </summary>
    /// <param name="data"></param>
    private void AddDefault(IList data)
    {
        if (data == null || data.Count == 0) return;
        var pros = data[0].GetType().GetProperties();
        bool hasSelect = false;
        var s = pros.FirstOrDefault(x => x.Name == SelectedValuePath);
        var d = pros.FirstOrDefault(x => x.Name == DisplayMemberPath);
        if (s == null) throw new Exception("SelectedValuePath attribute is not specified for ComboBox, note: attribute is case sensitive!");
        if (d == null) throw new Exception("DisplayMemberPath attribute is not specified for ComboBox, note: attribute is case sensitive!");
        foreach (var item in data)
        {
            if (s == d && (s.GetValue(item, null) + "") == DefaultSelectedText)
            {
                hasSelect = true;
                break;
            }
            else if ((s.GetValue(item, null) + "") == DefaultSelectedValue && (d.GetValue(item, null) + "") == DefaultSelectedText)
            {
                hasSelect = true;
                break;
            }
        }
        if (hasSelect == false)
        {
            var subType = data.GetType().GenericTypeArguments[0];
            if (subType.Name.StartsWith("<>f__AnonymousType")) return;
            var m = Activator.CreateInstance(subType);
            if (s != d)
            {
                s.SetValue(m, Convert.ChangeType(DefaultSelectedValue, s.PropertyType), null);
                d.SetValue(m, Convert.ChangeType(DefaultSelectedText, d.PropertyType), null);
            }
            else
            {
                d.SetValue(m, Convert.ChangeType(DefaultSelectedText, d.PropertyType), null);
            }
            data.Insert(0, m);
        }
    }
}

How to create WPF user control

The above is the complete code . It should be noted that, in the above code, there is a bug in handling anonymous types, but it has not been fixed. The strong type binding will add the default item [—please select—] .

Business-related ComboBox drop-down box

In actual projects, there will be many data sources of drop-down boxes that need to be obtained by calling the interface. After encapsulating it, you can avoid calling interfaces in many functional pages (Pages) or controls to get data to ComboBox to bind data. Therefore, after packaging, drag it over and use it directly. This is the drop-down box of WPF user control packaging.

In the framework, drop-down boxes such as department, dictionary, and general business status are encapsulated. Here we only put the sample code of the drop-down box of a department, because the department needs to adjust the interface to obtain, the WPF user control encapsulation code commented out below is in the real project Call the interface to get the data to bind the code.

using JHRS.Core.Controls.Common;
using JHRS.Core.Models;
using System;
using System.Collections.Generic;

namespace JHRS.Core.Controls.DropDown
{
    /// <summary>
    /// Department drop-down box
    /// </summary>
    public class DepartmentComboBox: BaseComboBox
  {
    /// <summary>
    /// Initialize department data and call the interface to get the data.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected override void OnInitialized(object sender, EventArgs e)
    {
      this.DisplayMemberPath = "Name";
      this.SelectedValuePath = "Id";

      //var response = await RestService.For<IDepartmentApi>(AuthHttpClient.Instance).GetAll();
      //if (response.Succeeded)
      //{
      // Data = response.Data as IList;
      //}

      List<DepartmentOutputDto> list = new List<DepartmentOutputDto>();
      for (int i = 0; i <20; i++)
      {
        list.Add(new DepartmentOutputDto
        {
          Id = i + 1,
          Name = $"Testing Department{i + 1}"
        });
      }
      base.Data = list;
    }
  }

}

How to create WPF user control

How to use the drop-down box after packaging

As shown in the figure below, in the demo framework, the WPF user control encapsulated control is directly dragged to the relevant position of the user control, and then bound to the value you need to get, you don’t need to pay attention to the data of the department when you use it. Where it came from, you only need to know what attribute you use to receive the selected value.

WPF user control package
How to create WPF user control

The above is the method used, let’s take a look at how to encapsulate the paging table.

Dynamic pagination table

The paging table in each system is a big head, or a more complex function, and in the frame, the table and the paging control are encapsulated together to form a user control. When it is needed, it is also directly dragged over and the paging interface is called. Get the data binding; WPF user control encapsulates the data displayed in each column of the table, which is also solved by the most basic dynamic table idea introduced above.

XAML code for pagination table control

<UserControl x:Class="JHRS.Core.Controls.DataGrids.PagingDataGrid"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:hc="https://handyorg.github.io/handycontrol"
             xmlns:local="clr-namespace:JHRS.Core.Controls.DataGrids"
             Loaded="UserControl_Loaded">
    <Grid Name="ucDataGrid">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="5"/>
            <RowDefinition Height="30"/>
        </Grid.RowDefinitions>
        <DataGrid x:Name="pagingDataList" Grid.Row="0" IsReadOnly="True"
                  ItemsSource="{Binding PageData}" CanUserAddRows="False" SelectionMode="Single"
                  LoadingRow="pagingDataList_LoadingRow">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Header, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}, Mode=FindAncestor}}" CanUserSort="False" Header="serial number" IsReadOnly="True" Width="40"/>
                <DataGridTemplateColumn Width="40" x:Name="isCheckbox">
                    <DataGridTemplateColumn.Header>
                        <CheckBox Click="CheckBox_Click"></CheckBox>
                    </DataGridTemplateColumn.Header>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <CheckBox Click="chkItem_Click" x:Name="chkItem" VerticalAlignment="Center" HorizontalAlignment="Center"></CheckBox>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
        <hc:Pagination x:Name="ucPagination" Grid.Row="2" HorizontalAlignment="Right" MaxPageCount="{Binding PagingData.MaxPageCount}" PageIndex="{Binding PagingData.PageIndex, Mode=TwoWay}">
            <b:Interaction.Triggers>
                <b:EventTrigger EventName="PageUpdated">
                    <b:InvokeCommandAction Command="{Binding ChangePageIndexCommand}"/>
                </b:EventTrigger>
            </b:Interaction.Triggers>
        </hc:Pagination>
    </Grid>
</UserControl>

How to create WPF user control

In the xaml code encapsulated by this WPF user control, there are mainly two controls, a DataGrid control, and a Pagination control. The C# code in the background is as follows:

using JHRS.Core.Extensions;
using JHRS.Filter;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 JHRS.Core.Controls.DataGrids
{
    /// <summary>
    /// Interactive logic of PagingDataGrid.xaml
    /// </summary>
    public partial class PagingDataGrid: UserControl
    {
        public PagingDataGrid()
        {
            InitializeComponent();
            pagingDataList.AutoGenerateColumns = false;
        }

        /// <summary>
        /// Generate serial number
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pagingDataList_LoadingRow(object sender, DataGridRowEventArgs e)
        {
            if (EnablePagination)
                //Need pagination
                e.Row.Header = (PagingData.PageIndex-1) * PagingData.PageSize + e.Row.GetIndex() + 1;
            else
                //No need for paging
                e.Row.Header = e.Row.GetIndex() + 1;
        }

        /// <summary>
        /// Table data source
        /// </summary>
        public IEnumerable<object> PageData
        {
            get {return (IEnumerable<object>)GetValue(PageDataProperty);}
            set {SetValue(PageDataProperty, value);}
        }

        //Whether the column has been generated and the binding relationship has been established
        private bool IsGenerateColumns = false;
        // Using a DependencyProperty as the backing store for PageData. This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PageDataProperty =
            DependencyProperty.Register("PageData", typeof(IEnumerable<object>), typeof(PagingDataGrid), new PropertyMetadata((d, e) =>
            {
                PagingDataGrid pagingDataGrid = d as PagingDataGrid;
                if (pagingDataGrid.IsGenerateColumns) return;
                int num = 0;
                if (pagingDataGrid.EnableRowNumber) num++;

                if (pagingDataGrid.EnableCheckBoxColumn) num++;

                pagingDataGrid.pagingDataList.GenerateColumns(num, e.NewValue, pagingDataGrid.OperatingKey, pagingDataGrid.OperatingWidth);
                pagingDataGrid.IsGenerateColumns = true;
            }));


        /// <summary>
        /// Paging control data source
        /// </summary>
        public PagingData PagingData
        {
            get {return (PagingData)GetValue(PagingDataProperty);}
            set {SetValue(PagingDataProperty, value);}
        }

        // Using a DependencyProperty as the backing store for PagingData. This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PagingDataProperty =
            DependencyProperty.Register("PagingData", typeof(PagingData), typeof(PagingDataGrid));

        /// <summary>
        /// Whether to enable the paging function
        /// </summary>
        public bool EnablePagination {get; set;} = true;

        /// <summary>
        /// Whether to enable the serial number
        /// </summary>
        public bool EnableRowNumber {get; set;} = true;

        /// <summary>
        /// Whether the check box column
        /// </summary>
        public bool EnableCheckBoxColumn {get; set;} = false;

        /// <summary>
        /// Whether the checkbox column is enabled to select all
        /// </summary>
        public bool EnableSelectAll {get; set;} = false;

        /// <summary>
        /// Key of the operation column
        /// </summary>
        public string OperatingKey {get; set;}

        /// <summary>
        /// Operation column width
        /// </summary>
        public DataGridLength OperatingWidth {get; set;}

        /// <summary>
        /// Currently selected data
        /// </summary>
        public IEnumerable<object> CheckedList
        {
            get {return (IEnumerable<object>)GetValue(SelectedListProperty);}
            set {SetValue(SelectedListProperty, value);}
        }

        // Using a DependencyProperty as the backing store for SelectedList. This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SelectedListProperty =
            DependencyProperty.Register("CheckedList", typeof(IEnumerable<object>), typeof(PagingDataGrid),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));


        /// <summary>
        /// Initialize table behavior
        /// </summary>
        private void InintBehavior()
        {
            if (!EnablePagination) ucDataGrid.Children.Remove(ucPagination);
            if (!EnableRowNumber) pagingDataList.Columns.Remove(pagingDataList.Columns.FirstOrDefault(x => x.Header.ToString() == "serial number"));

            if (!EnableCheckBoxColumn) pagingDataList.Columns.Remove(isCheckbox);
            if (!EnableSelectAll) isCheckbox.Header = "Select";
        }

        /// <summary>
        /// Initialize table behavior
        /// </summary>
        private void InintBehavior ()
        {
            if (!EnablePagination) ucDataGrid. Children. Remove (ucPagination);
            if (!EnableRowNumber) pagingDataList. Columns. Remove (pagingDataList. Columns. FirstOrDefault (x => x. Header. ToString () == "serial number" ));
            if (!EnableCheckBoxColumn) pagingDataList. Columns. Remove (isCheckbox );
            if (!EnableSelectAll) isCheckbox. Header = "Select";
        }
        /// <summary>
        /// User control initialization
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UserControl_Loaded (object sender, RoutedEventArgs e)
        {
            InintBehavior ();
        }
        /// <summary>
        /// Check box select all event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CheckBox_Click (object sender, RoutedEventArgs e)
        {
            var c = sender as CheckBox;
            CheckedAll (pagingDataList, c. IsChecked);
            CheckedList = GetSelected ();
        }
        /// <summary>
        /// select all
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="isChecked"></param>
        private void CheckedAll (DependencyObject parent, bool? isChecked)
        {
            int numVisuals = VisualTreeHelper. GetChildrenCount (parent);
            for (int i = 0; i <numVisuals; i++)
            {
                DependencyObject v = VisualTreeHelper. GetChild (parent, i);
                CheckBox child = v as CheckBox;
                if (child == null)
                {
                    CheckedAll (v, isChecked);
                }
                else
                {
                    child. IsChecked = isChecked;
                    break;
                }
            }
        }
        /// <summary>
        /// Get all selected items
        /// </summary>
        /// <returns></returns>
        private List <object> GetSelected ()
        {
            List <object> list = new List <object >();
            foreach (var item in pagingDataList. ItemsSource)
            {
                var m = isCheckbox. GetCellContent (item);
                var c = m. GetChildObject <CheckBox >( "chkItem" );
                if (c != null && c. IsChecked == true)
                {
                    list. Add (item);
                }
            }
            return list;
        }
        /// <summary>
        /// Click to select the event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void chkItem_Click (object sender, RoutedEventArgs e)
        {
            CheckedList = GetSelected ();
        }
    }
}

How to create WPF user control

How to use dynamic pagination table

In the page or control that needs to be used, just drag in the control encapsulated by the WPF user control, the complete xaml code is as follows

<Page x:Class="JHRS.OutpatientSystem.Views.Reservation"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:local="clr-namespace:JHRS.OutpatientSystem.Views"
      xmlns:prism="http://prismlibrary.com/"
      xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
      prism:ViewModelLocator.AutoWireViewModel="True"
      xmlns:f="clr-namespace:JHRS.Core.Controls.Layouts;assembly=JHRS.Core"
      xmlns:p="clr-namespace:JHRS.Core.Controls.DataGrids;assembly=JHRS.Core"
      xmlns:c="clr-namespace:JHRS.Core.Controls.DropDown;assembly=JHRS.Core"
      x:Name="page"
      Title="Reservation" Background="#FFFFFFFF">
    <b:Interaction.Triggers>
        <b:EventTrigger EventName="Loaded">
            <b:InvokeCommandAction Command="{Binding LoadedCommand}" CommandParameter="{Binding ElementName=page}" />
        </b:EventTrigger>
    </b:Interaction.Triggers>
    <Page.Resources>
        <DataTemplate x:Key="Status">
            <c:StatusComboBox Name="cboStatus" SelectedValue="{Binding Status, Mode=TwoWay, UpdateSourceTrigger=Explicit,Converter={StaticResource EnumToIntConverter}}">
                <b:Interaction.Triggers>
                    <b:EventTrigger EventName="SelectionChanged">
                        <b:InvokeCommandAction Command="{Binding DataContext.SelectionChangedCommand,RelativeSource={RelativeSource AncestorType=Page}}" CommandParameter="{Binding ElementName=cboStatus}"/>
                    </b:EventTrigger>
                </b:Interaction.Triggers>
            </c:StatusComboBox>
        </DataTemplate>
        <DataTemplate x:Key="OperationKey">
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
                <Button Name="btnEdit" Content="Edit" Background="#00FFFFFF" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{Binding DataContext.EditCommand, RelativeSource={RelativeSource AncestorType=Page}}" CommandParameter="{Binding Path=DataContext, ElementName=btnEdit}" />
                <Button Name="btnView" Content="Details" Margin="10,0,0,0" ToolTip="Details" Command="{Binding DataContext.ViewDetailsCommand, RelativeSource={RelativeSource AncestorType=Page}}" CommandParameter=" {Binding Path=DataContext, ElementName=btnView}" />
            </StackPanel>
        </DataTemplate>
    </Page.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="50" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid VerticalAlignment="Top">
            <f:FunctionArea AddButtonText="Add an appointment" />
        </Grid>
        <Grid Name="maskContainer" Row="1">
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition Height="5" />
                <RowDefinition Height="30" />
            </Grid.RowDefinitions>
            <p:PagingDataGrid Name="ucDataGrid" OperatingKey="OperationKey" OperatingWidth="150*" EnableSelectAll="True" PageData="{Binding PageData}" PagingData="{Binding PagingData}" />
        </Grid>
    </Grid>
</Page>

How to create WPF user control

In the above code, <p:PagingDataGrid Name=”ucDataGrid” OperatingKey=”OperationKey” OperatingWidth=”150*” EnableSelectAll=”True” PageData=”{Binding PageData}” PagingData=”{Binding PagingData}” /> is the application dynamic paging For the table method, note that the bound data is the PageData dependent property of the user control you encapsulate, and the data source PageData bound inside is the paging data property defined by the ViewModel base class. You need to call the interface assignment in the ViewModel of each subclass. As shown in the following code:

         /// <summary>
         /// Bind paging data
         /// </summary>
         [WaitComplete]
         protected async override Task<object> BindPagingData()
         {
             var request = this.GetQueryRules(Query);
             var response = await RestService.For<IReservationApi>(AuthClient).GetPageingData(request);
             if (response.Succeeded)
             {
                 PageData = response.Data.Rows;
                 this.PagingData.Total = response.Data.Total;
             }
             return response;
         }

How to create WPF user control

Conclusion

Reasonable encapsulation of WPF user controls can reduce a lot of repetitive code. This article explains how to encapsulate the idea of ​​user controls. In actual projects, you can combine the team situation and project status to encapsulate. In short, in the IT world, it is justifiable to be lazy. The reason for asking the boss for more salary is because you work fast and well. Which foreman doesn’t like it?

The next article will introduce some principles of the catalog file in the team development . If there is a better way, everyone is welcome to put forward.

Related reading in this series

  1. WPF Enterprise Development Framework Building Guide (Revelation)
  2. Basic class library of JHRS development framework
  3. Selection of third-party frameworks for JHRS development framework
  4. WPF call Web API package of JHRS development framework
  5. Client portal project of JHRS development framework
  6. How to integrate the various subsystems of the JHRS development framework
  7. How to design a reasonable ViewModel base class in JHRS development framework
  8. How to create WPF user control
  9. Some principles of cataloging documents followed by the recommendations of the JHRS development framework
  10. WPF data verification of JHRS development framework
  11. The solution of JHRS development framework’s ViewModel mutual parameter transfer and pop-up frame return parameter
  12. JHRS development framework of stepping on the pit (final chapter)

Leave a Comment