WPF open source project: WPF controlbase

Screenshot of warehouse

The warehouse README is very plain, but you will like it by reading several blog posts posted by the author README. Don't talk nonsense. The introduction Directory:

  1. Animation encapsulation
https://blog.csdn.net/u010975589/article/details/95974854
  1. property sheet
https://blog.csdn.net/u010975589/article/details/95970200
  1. Message conversation
https://blog.csdn.net/u010975589/article/details/95985190
  1. Applying MVC in WPF
https://blog.csdn.net/u010975589/article/details/100019431
  1. Description of other functions
https://blog.csdn.net/u010975589/article/details/103083605

Details are as follows:

1. Animation packaging

Original title: example: WPF Custom in StoryBoarService Encapsulate in code StoryBoard,Animation Used to simplify animation
 Original link: https://blog.csdn.net/u010975589/article/details/95974854

1.1 purpose: to simplify the preparation of Animation by encapsulating StoryBoard and Animation

1.2 example

Note: gradual hiding is a commonly used animation in WPF. The above figure shows the effect encapsulated by StoryBoarService. In the code, just execute the following code:

DoubleStoryboardEngine.Create(1, 0, 1, "Opacity").Start(element);

The above closing effect can define a command as follows:

public class CollapsedOfOpacityCommand : ICommand
{

    public bool CanExecute(object parameter) => true;

    public void Execute(object parameter)
    {
        if(parameter is UIElement element)
        {
            var engine = DoubleStoryboardEngine.Create(1, 0, 1, "Opacity");

            engine.Start(element);
        }
    }

    public event EventHandler CanExecuteChanged;
}

The following commands are invoked in Xaml to close the hidden effect.

Command="{x:Static base:CommandService.CollapsedOfOpacityCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=GroupBox}}"

The incoming CommandParmeter will be gradually hidden when the command is executed

The code of animation effect only needs one sentence of code, which simplifies the cumbersome coding process of animation in the code

DoubleStoryboardEngine.Create(1, 0, 1, "Opacity").Start(element);

1.3 Code:

At present, only the encapsulation of DoubleAnimation is implemented, and other types will be encapsulated later

1.3.1 closed modification base class

///< summary > animation engine base class < / summary >
public abstract class StoryboardEngineBase : IDisposable
{
    protected Storyboard storyboard = new Storyboard();

    public EventHandler CompletedEvent { get; set; }

    public EasingFunctionBase Easing { get; set; } = EasingFunctionFactroy.PowerEase;

    public PropertyPath PropertyPath { get; set; }

    public Duration Duration { get; set; }

    public void Dispose()
    {
        storyboard.Completed -= CompletedEvent;
    }

    public abstract StoryboardEngineBase Start(UIElement element);

    public abstract StoryboardEngineBase Stop();

    public StoryboardEngineBase(int second, string property)
    {
        this.PropertyPath = new PropertyPath(property);
        this.Duration = new Duration(TimeSpan.FromSeconds(second));
    }

}

///< summary > Animation generic engine base class < / summary >
public abstract class StoryboardEngineBase<T> : StoryboardEngineBase
{
    public StoryboardEngineBase(T from, T to, int second, string property) : base(second, property)
    {
        this.FromValue = from;
        this.ToValue = to;
    }

    public T FromValue { get; set; }

    public T ToValue { get; set; }

    //public RepeatBehavior RepeatBehavior { get; set; };

}

1.3.2 open extended DoubleStoryboardEngine

///< summary > doubleanimation animation engine < / summary >
public class DoubleStoryboardEngine : StoryboardEngineBase<double>
{
    public static DoubleStoryboardEngine Create(double from, double to, int second, string property)
    {
        return new DoubleStoryboardEngine(from, to, second, property);
    }

    public DoubleStoryboardEngine(double from, double to, int second, string property) : base(from, to, second, property)
    {

    }

    public override StoryboardEngineBase Start(UIElement element)
    {
        //  Do: timeline
        DoubleAnimation animation = new DoubleAnimation(1, 0, this.Duration);

        if (this.Easing != null)
            animation.EasingFunction = this.Easing;

        //if (this.RepeatBehavior != default(RepeatBehavior))
        //    animation.RepeatBehavior = (RepeatBehavior);

        //  Do: attribute animation
        storyboard.Children.Add(animation);
        Storyboard.SetTarget(animation, element);
        Storyboard.SetTargetProperty(animation, this.PropertyPath);

        if (CompletedEvent != null)
            storyboard.Completed += CompletedEvent;
        storyboard.Begin();

        return this;
    }

    public override StoryboardEngineBase Stop()
    {
        this.storyboard.Stop();

        return this;
    }
}

1.3.3 excessive effect factory

///< summary > Description: https://docs.microsoft.com/zh-cn/dotnet/framework/wpf/graphics-multimedia/easing-functions  </summary>
public static class EasingFunctionFactroy
{
    ///< summary > powerease: create an animation of the formula used for acceleration and / or deceleration, f(t) = tp, where p is equal to the Power attribute</ summary>
    public static PowerEase PowerEase { get; set; } = new PowerEase();
    ///< summary > backease: slightly retract the action of animation, and then start the path indicated by animation processing</ summary>
    public static BackEase BackEase { get; set; } = new BackEase();
    ///< summary > elasticease: create an animation similar to a spring going back and forth until still < / summary >
    public static ElasticEase ElasticEase { get; set; } = new ElasticEase();
    ///< summary > bounceease: creates a bouncing effect</ summary>
    public static BounceEase BounceEase { get; set; } = new BounceEase();
    ///< summary > circleease: create an animation that uses circular functions for acceleration and / or deceleration</ summary>
    public static CircleEase CircleEase { get; set; } = new CircleEase();

    ///< summary > quadratecase: create an animation of the formula used for acceleration and / or deceleration f(t) = t2</ summary>
    public static QuadraticEase QuadraticEase { get; set; } = new QuadraticEase();

    ///< summary > cubicease: create an animation of the formula used for acceleration and / or deceleration f(t) = t3</ summary>
    public static CubicEase CubicEase { get; set; } = new CubicEase();
    ///< summary > quarticease: create an animation of the formula used for acceleration and / or deceleration f(t) = t4</ summary>
    public static QuarticEase QuarticEase { get; set; } = new QuarticEase();
    ///< summary > quinticease: create an animation of the formula used for acceleration and / or deceleration f(t) = t5</ summary>
    public static QuinticEase QuinticEase { get; set; } = new QuinticEase();

    ///< summary > exponentialease: create an animation that uses exponential formulas for acceleration and / or deceleration</ summary>
    public static ExponentialEase ExponentialEase { get; set; } = new ExponentialEase();

    ///< summary > sineease: create an animation that uses sinusoidal formulas for acceleration and / or deceleration</ summary>
    public static SineEase SineEase { get; set; } = new SineEase();

}

1.3.4 application method

///< summary > construction method < / summary >
///< param name = "from" > starting value < / param >
///< param name = "to" > end value < / param >
///< param name = "second" > interval time seconds < / param >
///< param name = "property" > modify the property name < / param >
/// 
public static DoubleStoryboardEngine Create(double from, double to, int second, string property)
{
    return new DoubleStoryboardEngine(from, to, second, property);
}

2. Attribute form

Original title: example: WPF Simple development ObjectProperyForm Used to bind entity forms
 Original link: https://blog.csdn.net/u010975589/article/details/95970200

2.1 purpose: custom controls are used to directly bind entity data and simplify the development cycle

2.2 realization

  1. Bind entity object
  2. Displays the attribute name by property
  3. Add verification conditions through characteristics
  4. Several simple DataTemplate templates such as String, Int, Double, DateTime and Bool have been implemented, and other templates support extension
  5. Other subsequent updates

2.3 example

Entities are defined as follows:

public class Student
{
    [Display("full name")]
    [Required]
    public string Name { get; set; }

    [Display("class")]
    [Required]
    public string Class { get; set; }

    [Display("address")]
    [Required]
    public string Address { get; set; }

    [Display("mailbox")]
    [Required]
    public string Emall { get; set; }

    [Display("available")]
    [Required]
    public bool IsEnbled { get; set; }

    [Display("time")]
    [Required]
    public DateTime time { get; set; }

    [Display("Age")]
    [Required]
    public int Age { get; set; }

    [Display("average")] 
    public double Score { get; set; }

    [Display("Telephone number")]
    [Required]
    [RegularExpression(@"^1[3|4|5|7|8][0-9]{9}$", ErrorMessage = "The mobile phone number is illegal!")]
    public string Tel { get; set; }
}
  • DisplayAttribute: used to identify the display name
  • Resuiledattribute: used to identify that the data cannot be empty
  • RgularExpression: reference regular expression to verify whether the data matches
  • Subsequent updates to other properties

Application mode:

<UserControl.Resources>
    <local:Student x:Key="S.Student.HeBianGu" 
                    Name="Riverside bone" 
                    Address="Chengdu hi tech Zone, Sichuan Province" 
                    Class="fourth grade" 
                    Emall="7777777777@QQ.com" Age="33" Score="99.99" IsEnbled="True" time="2019-09-09"/>
</UserControl.Resources>
 
<wpfcontrollib:ObjectPropertyForm Grid.Row="1" Title="Student information"  SelectObject="{StaticResource S.Student.HeBianGu}" >
    <base:Interaction.Behaviors>
        <base:MouseDragElementBehavior ConstrainToParentBounds="True"/>
        <base:SelectZIndexElementBehavior/>
    </base:Interaction.Behaviors>

2.4 code

2.4.1 acquiring attributes and characteristics through reflection

 ObservableCollection<ObjectPropertyItem> PropertyItemSource
{
    get { return (ObservableCollection<ObjectPropertyItem>)GetValue(PropertyItemSourceProperty); }
    set { SetValue(PropertyItemSourceProperty, value); }
}

// Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty PropertyItemSourceProperty =
    DependencyProperty.Register("PropertyItemSource", typeof(ObservableCollection<ObjectPropertyItem>), typeof(ObjectPropertyForm), new PropertyMetadata(new ObservableCollection<ObjectPropertyItem>(), (d, e) =>
      {
          ObjectPropertyForm control = d as ObjectPropertyForm;

          if (control == null) return;

          ObservableCollection<ObjectPropertyItem> config = e.NewValue as ObservableCollection<ObjectPropertyItem>;

      }));


void RefreshObject(object o)
{
    Type type = o.GetType();

    var propertys = type.GetProperties();

    this.PropertyItemSource.Clear();

    foreach (var item in propertys)
    {
        var from = ObjectPropertyFactory.Create(item, o);

        this.PropertyItemSource.Add(from);
    }

    this.ItemsSource = this.PropertyItemSource;
}

2.4.2 define types, base classes, extensions and factory methods

///< summary > type base class < / summary >
public class ObjectPropertyItem : NotifyPropertyChanged
{
    public string Name { get; set; }
    public PropertyInfo PropertyInfo { get; set; }

    public object Obj { get; set; }
    public ObjectPropertyItem(PropertyInfo property, object obj)
    {
        PropertyInfo = property;


        var display = property.GetCustomAttribute<DisplayAttribute>();

        Name = display == null ? property.Name : display.Name;

        Obj = obj;
    }


}

///< summary > generic type base class < / summary >
public class ObjectPropertyItem<T> : ObjectPropertyItem
{
    private T _value;
    ///< summary > description < / summary >
    public T Value
    {
        get { return _value; }
        set
        {

            this.Message = null;

            //  Do: check data validity
            if (Validations != null)
            {
                foreach (var item in Validations)
                {
                    if (!item.IsValid(value))
                    {
                        this.Message = item.ErrorMessage;  
                    }
                }
            }

            _value = value; 

            RaisePropertyChanged("Value");

            this.SetValue(value);
        }
    }

    void SetValue(T value)
    {
        this.PropertyInfo.SetValue(Obj, value);
    }

    List<ValidationAttribute> Validations { get; }

    public ObjectPropertyItem(PropertyInfo property, object obj) : base(property, obj)
    {
        Value = (T)property.GetValue(obj); 

        Validations = property.GetCustomAttributes<ValidationAttribute>()?.ToList();

        if(Validations!=null&& Validations.Count>0)
        {
            this.Flag = "*";
        }
    }



    private string _message;
    ///< summary > description < / summary >
    public string Message
    {
        get { return _message; }
        set
        {
            _message = value;
            RaisePropertyChanged("Message");
        }
    }

    public string Flag { get; set; }

}

///< summary > string attribute type < / summary >
public class StringPropertyItem : ObjectPropertyItem<string>
{
    public StringPropertyItem(PropertyInfo property, object obj) : base(property, obj)
    {
    }
}

///< summary > time attribute type < / summary >
public class DateTimePropertyItem : ObjectPropertyItem<DateTime>
{
    public DateTimePropertyItem(PropertyInfo property, object obj) : base(property, obj)
    {
    }
}

///< summary > double attribute type < / summary >
public class DoublePropertyItem : ObjectPropertyItem<double>
{
    public DoublePropertyItem(PropertyInfo property, object obj) : base(property, obj)
    {
    }
}

///< summary > int attribute type < / summary >

public class IntPropertyItem : ObjectPropertyItem<int>
{
    public IntPropertyItem(PropertyInfo property, object obj) : base(property, obj)
    {
    }
}

///< summary > bool attribute type < / summary >
public class BoolPropertyItem : ObjectPropertyItem<bool>
{
    public BoolPropertyItem(PropertyInfo property, object obj) : base(property, obj)
    {
    }
}

Type factory:

public class ObjectPropertyFactory
{
    public static ObjectPropertyItem Create(PropertyInfo info, object obj)
    {
        if (info.PropertyType == typeof(int))
        {
            return new IntPropertyItem(info, obj);
        }
        else if (info.PropertyType == typeof(string))
        {
            return new StringPropertyItem(info, obj);
        }
        else if (info.PropertyType == typeof(DateTime))
        {
            return new DateTimePropertyItem(info, obj);
        }
        else if (info.PropertyType == typeof(double))
        {
            return new DoublePropertyItem(info, obj);
        }
        else if (info.PropertyType == typeof(bool))
        {
            return new BoolPropertyItem(info, obj);
        }

        return null;
    }
}

2.4.3 style template

<DataTemplate DataType="{x:Type base:StringPropertyItem}">
    <Grid Width="{Binding RelativeSource={RelativeSource AncestorType=local:ObjectPropertyForm},Path=Width-5}" 
          Height="35" Margin="5,0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="2*"/>
            <ColumnDefinition Width="30"/>
        </Grid.ColumnDefinitions>

        <TextBlock Text="{Binding Name}" 
                    FontSize="14" 
                    HorizontalAlignment="Center" 
                    VerticalAlignment="Center"/>

        <TextBlock Text="{Binding Flag}" 
                    Grid.Column="1" Margin="5,0"
                    FontSize="14"  Foreground="{DynamicResource S.Brush.Red.Notice}" 
                    HorizontalAlignment="Right" 
                    VerticalAlignment="Center"/>

        <local:FTextBox Text="{Binding Value,UpdateSourceTrigger=PropertyChanged}" Style="{DynamicResource DefaultTextBox}"
                  FontSize="14" Width="Auto" CaretBrush="Black"
                  Grid.Column="2" Height="30" base:ControlAttachProperty.FIcon=""
                  VerticalContentAlignment="Center" 
                  HorizontalAlignment="Stretch" VerticalAlignment="Center"/>

        <TextBlock Text="&#xe626;" Grid.Column="3" Style="{DynamicResource FIcon }"
                    Foreground="{DynamicResource S.Brush.Red.Notice}" 
                    Visibility="{Binding Message,Converter={x:Static base:XConverter.VisibilityWithOutStringConverter},ConverterParameter={x:Null},Mode=TwoWay}"
                    FontSize="14" TextTrimming="CharacterEllipsis" ToolTip="{Binding Message}"
                    HorizontalAlignment="Center" 
                    VerticalAlignment="Center"/>
    </Grid>
</DataTemplate>

<DataTemplate DataType="{x:Type base:BoolPropertyItem}">
    <Grid Width="{Binding RelativeSource={RelativeSource AncestorType=local:ObjectPropertyForm},Path=Width-5}" Height="35" Margin="5,0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="2*"/>
            <ColumnDefinition Width="30"/>
        </Grid.ColumnDefinitions>

        <TextBlock Text="{Binding Name}" 
                    FontSize="14" 
                    HorizontalAlignment="Center" 
                    VerticalAlignment="Center"/>

        <TextBlock Text="{Binding Flag}" 
                    Grid.Column="1" Margin="5,0"
                    FontSize="14"  Foreground="{DynamicResource S.Brush.Red.Notice}" 
                    HorizontalAlignment="Right" 
                    VerticalAlignment="Center"/>
        <CheckBox IsChecked="{Binding Value}"  FontSize="14" Grid.Column="2" Height="30" 
                  VerticalContentAlignment="Center"  
                  HorizontalAlignment="Left" VerticalAlignment="Center"/>


        <TextBlock Text="&#xe626;" Grid.Column="3" Style="{DynamicResource FIcon }"
                    Foreground="{DynamicResource S.Brush.Red.Notice}" Visibility="{Binding Message,Converter={x:Static base:XConverter.VisibilityWithOutStringConverter},ConverterParameter={x:Null}}"
                    FontSize="14"   TextTrimming="CharacterEllipsis" ToolTip="{Binding Message}"
                    HorizontalAlignment="Center" 
                    VerticalAlignment="Center"/>
    </Grid>
</DataTemplate>

<DataTemplate DataType="{x:Type base:DateTimePropertyItem}">
    <Grid Width="{Binding RelativeSource={RelativeSource AncestorType=local:ObjectPropertyForm},Path=Width-5}" Height="35" Margin="5,0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="2*"/>
            <ColumnDefinition Width="30"/>
        </Grid.ColumnDefinitions>

        <TextBlock Text="{Binding Name}" 
                    FontSize="14" 
                    HorizontalAlignment="Center" 
                    VerticalAlignment="Center"/>

        <TextBlock Text="{Binding Flag}" 
                    Grid.Column="1" Margin="5,0"
                    FontSize="14"  Foreground="{DynamicResource S.Brush.Red.Notice}" 
                    HorizontalAlignment="Right" 
                    VerticalAlignment="Center"/>
        <DatePicker SelectedDate="{Binding Value}"  FontSize="14" Grid.Column="2" Height="30" 
                  VerticalContentAlignment="Center"  Width="Auto"
                  HorizontalAlignment="Stretch" VerticalAlignment="Center"/>


        <TextBlock Text="&#xe626;" Grid.Column="3" Style="{DynamicResource FIcon }"
                    Foreground="{DynamicResource S.Brush.Red.Notice}" Visibility="{Binding Message,Converter={x:Static base:XConverter.VisibilityWithOutStringConverter},ConverterParameter={x:Null}}"
                    FontSize="14"   TextTrimming="CharacterEllipsis" ToolTip="{Binding Message}"
                    HorizontalAlignment="Center" 
                    VerticalAlignment="Center"/>
    </Grid>
</DataTemplate>

<DataTemplate DataType="{x:Type base:IntPropertyItem}">
    <Grid Width="{Binding RelativeSource={RelativeSource AncestorType=local:ObjectPropertyForm},Path=Width-5}" Height="35" Margin="5,0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="2*"/>
            <ColumnDefinition Width="30"/>
        </Grid.ColumnDefinitions>

        <TextBlock Text="{Binding Name}" 
                    FontSize="14" 
                    HorizontalAlignment="Center" 
                    VerticalAlignment="Center"/>

        <TextBlock Text="{Binding Flag}" 
                    Grid.Column="1" Margin="5,0"
                    FontSize="14"  Foreground="{DynamicResource S.Brush.Red.Notice}" 
                    HorizontalAlignment="Right" 
                    VerticalAlignment="Center"/>
        <Slider Value="{Binding Value}"  FontSize="14" Grid.Column="2" Height="30" 
                  VerticalContentAlignment="Center"  
                  HorizontalAlignment="Stretch" VerticalAlignment="Center"/>


        <TextBlock Text="&#xe626;" Grid.Column="3" Style="{DynamicResource FIcon }"
                    Foreground="{DynamicResource S.Brush.Red.Notice}" Visibility="{Binding Message,Converter={x:Static base:XConverter.VisibilityWithOutStringConverter},ConverterParameter={x:Null}}"
                    FontSize="14"   TextTrimming="CharacterEllipsis" ToolTip="{Binding Message}"
                    HorizontalAlignment="Center" 
                    VerticalAlignment="Center"/>
    </Grid>
</DataTemplate>

<DataTemplate DataType="{x:Type base:DoublePropertyItem}">
    <Grid Width="{Binding RelativeSource={RelativeSource AncestorType=local:ObjectPropertyForm},Path=Width-5}" Height="35" Margin="5,0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="2*"/>
            <ColumnDefinition Width="30"/>
        </Grid.ColumnDefinitions>

        <TextBlock Text="{Binding Name}" 
                    FontSize="14" 
                    HorizontalAlignment="Center" 
                    VerticalAlignment="Center"/>

        <TextBlock Text="{Binding Flag}" 
                    Grid.Column="1" Margin="5,0"
                    FontSize="14"  Foreground="{DynamicResource S.Brush.Red.Notice}" 
                    HorizontalAlignment="Right" 
                    VerticalAlignment="Center"/>
        <Slider Value="{Binding Value}"  FontSize="14" Grid.Column="2" Height="30" 
                  VerticalContentAlignment="Center"  
                  HorizontalAlignment="Stretch" VerticalAlignment="Center"/>


        <TextBlock Text="&#xe626;" Grid.Column="3" Style="{DynamicResource FIcon }"
                    Foreground="{DynamicResource S.Brush.Red.Notice}" Visibility="{Binding Message,Converter={x:Static base:XConverter.VisibilityWithOutStringConverter},ConverterParameter={x:Null}}"
                    FontSize="14"   TextTrimming="CharacterEllipsis" ToolTip="{Binding Message}"
                    HorizontalAlignment="Center" 
                    VerticalAlignment="Center"/>
    </Grid>
</DataTemplate>

<Style TargetType="local:ObjectPropertyForm">
    <Setter Property="Background" Value="{DynamicResource S.Brush.TextBackgroud.Default}"/>
    <Setter Property="BorderThickness" Value="0"/>
    <!--<Setter Property="BorderBrush" Value="{x:Null}"/>-->
    <Setter Property="HorizontalAlignment" Value="Stretch"/>
    <Setter Property="VerticalAlignment" Value="Center"/>
    <Setter Property="HorizontalContentAlignment" Value="Center"/>
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <!--<Setter Property="FocusVisualStyle" Value="{x:Null}"/>-->
    <Setter Property="Padding" Value="0" />
    <Setter Property="Width" Value="500" />
    <Setter Property="Height" Value="Auto" />
    <Setter Property="ItemsSource" Value="{Binding PropertyItemSource,Mode=TwoWay}" />
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <StackPanel/>

            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:ObjectPropertyForm">
                <GroupBox Header="{TemplateBinding Title}">
                    <Border HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
                        VerticalAlignment="{TemplateBinding VerticalAlignment}"
                        Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                        <ItemsPresenter/>
                    </Border>
                </GroupBox>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

2.4.4 open extension

2.4.4.1 only one extension type needs to be defined, such as:
///< summary > string attribute type < / summary >
public class StringPropertyItem : ObjectPropertyItem<string>
{
    public StringPropertyItem(PropertyInfo property, object obj) : base(property, obj)
    {
    }
}
2.4.4.2 add another DataTmeplate, such as:
<DataTemplate DataType="{x:Type base:StringPropertyItem}">
    <Grid Width="{Binding RelativeSource={RelativeSource AncestorType=local:ObjectPropertyForm},Path=Width-5}" 
          Height="35" Margin="5,0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="2*"/>
            <ColumnDefinition Width="30"/>
        </Grid.ColumnDefinitions>

        <TextBlock Text="{Binding Name}" 
                    FontSize="14" 
                    HorizontalAlignment="Center" 
                    VerticalAlignment="Center"/>

        <TextBlock Text="{Binding Flag}" 
                    Grid.Column="1" Margin="5,0"
                    FontSize="14"  Foreground="{DynamicResource S.Brush.Red.Notice}" 
                    HorizontalAlignment="Right" 
                    VerticalAlignment="Center"/>

        <local:FTextBox Text="{Binding Value,UpdateSourceTrigger=PropertyChanged}" Style="{DynamicResource DefaultTextBox}"
                  FontSize="14" Width="Auto" CaretBrush="Black"
                  Grid.Column="2" Height="30" base:ControlAttachProperty.FIcon=""
                  VerticalContentAlignment="Center" 
                  HorizontalAlignment="Stretch" VerticalAlignment="Center"/>

        <TextBlock Text="&#xe626;" Grid.Column="3" Style="{DynamicResource FIcon }"
                    Foreground="{DynamicResource S.Brush.Red.Notice}" 
                    Visibility="{Binding Message,Converter={x:Static base:XConverter.VisibilityWithOutStringConverter},ConverterParameter={x:Null},Mode=TwoWay}"
                    FontSize="14" TextTrimming="CharacterEllipsis" ToolTip="{Binding Message}"
                    HorizontalAlignment="Center" 
                    VerticalAlignment="Center"/>
    </Grid>
</DataTemplate>

3. Message dialogue

Original title: example: WPF Custom in MessageService application DialogHost,Snackbar,NotifyIcon Display various scene prompt messages
 Original link: https://blog.csdn.net/u010975589/article/details/95985190

3.1 purpose

Different interactive scenarios need to prompt different messages, and different messages need to be displayed with different effects. DialogHost, NotifyIcon and Snackbar are used to display various scenario prompt messages, which are applied in ViewModel

3.2 realization

  1. Wait dialog
  2. OK dialog box
  3. OK and cancel dialog box
  4. Percentage progress and text progress dialog boxes
  5. Bubble prompt message (NotifyIcon)
  6. Prompt message (Snackbar)

3.3 example

explain:

  1. Dialog box: General dialog messages are shown in the figure above, including waiting dialog box, message dialog box and progress dialog box;

(at present, only the above types are encapsulated. To customize the dialog box, you only need to create a user control and call the general loading method. Subsequent updates...)

  1. Prompt message: when the progress is saved successfully, a prompt message is required, which can be displayed for 2s and automatically hidden (as shown in the friendly prompt part in the figure);
  2. Bubble message: when the program is hidden or in a certain state, a bubble prompt message needs to be applied;

3.4 code

[ViewModel("Loyout")]
class LoyoutViewModel : MvcViewModelBase
{

    
    ///< summary > General method of command < / summary >
    protected override async void RelayMethod(object obj)

    {
        string command = obj?.ToString();

        //  Do: conversation message
        if (command == "Button.ShowDialogMessage")
        {
            await MessageService.ShowSumitMessge("Is this a message dialog?");

        }
        //  Do: wait for message
        else if (command == "Button.ShowWaittingMessge")
        {

            await MessageService.ShowWaittingMessge(() => Thread.Sleep(2000));

        }
        //  Do: percentage progress dialog box
        else if (command == "Button.ShowPercentProgress")
        {
            Action<IPercentProgress> action = l =>
                {
                    for (int i = 0; i < 100; i++)
                    {
                        l.Value = i;

                        Thread.Sleep(50);
                    }

                    Thread.Sleep(1000);

                    MessageService.ShowSnackMessageWithNotice("Loading complete!");
                };
            await MessageService.ShowPercentProgress(action);

        }
        //  Do: text progress dialog box
        else if (command == "Button.ShowStringProgress")
        {
            Action<IStringProgress> action = l =>
            {
                for (int i = 1; i <= 100; i++)
                {
                    l.MessageStr = $"Submitting current page page{i}Data,100 copies in total";

                    Thread.Sleep(50);
                }

                Thread.Sleep(1000);

                MessageService.ShowSnackMessageWithNotice("Submission completed: 100 successes, 0 failures!");
            };

            await MessageService.ShowStringProgress(action);

        }
        //  Do: confirm cancel dialog box
        else if (command == "Button.ShowResultMessge")
        {
            Action<object, DialogClosingEventArgs> action = (l, k) =>
            {
                if ((bool)k.Parameter)
                {
                    MessageService.ShowSnackMessageWithNotice("You clicked cancel");
                }
                else
                {
                    MessageService.ShowSnackMessageWithNotice("You clicked OK");
                }
            };

            MessageService.ShowResultMessge("Are you sure you want to exit the system?", action);


        }
        //  Do: prompt message
        else if (command == "Button.ShowSnackMessage")
        {
            MessageService.ShowSnackMessageWithNotice("Is this a prompt message?");
        } 
        //  Do: bubble message
        else if (command == "Button.ShowNotifyMessage")
        {
            MessageService.ShowNotifyMessage("You have an alarm message to process, please check it", "Notify By HeBianGu");
        }
    }
}

4. Apply MVC in WPF

Original title: encapsulation: a brief introduction to custom development based on WPF of MVC frame
 Original link: https://blog.csdn.net/u010975589/article/details/100019431

4.1 purpose

When using Asp.net Core, I deeply feel the convenience of MVC framework as page Jump data processing, but there seems to be no ready-made MVC framework in WPF. Therefore, I customize and develop a set of MVC framework, and I also realize the advantages of the framework in the process of use. Here is a brief introduction to this set of MVC framework based on MVVM

4.2 project structure

It is mainly composed of three parts: Controller, View and ViewModel

View and ViewModel are MVVM patterns in traditional WPF

The difference is that the page Jump is applied to the Controller for control. The following example is the definition of the Controller

4.3 structure and definition of controller

4.3.1 define LoyoutController

[Route("Loyout")]
class LoyoutController : Controller
{

    public LoyoutController(ShareViewModel shareViewModel) : base(shareViewModel)
    {

    }

    public async Task<IActionResult> Center()
    {
        return View();
    }

    [Route("OverView/Button")]
    public async Task<IActionResult> Mdi()
    {
        return View();
    }

    public async Task<IActionResult> Left()
    {
        return View();
    }

    public async Task<IActionResult> Right()
    {
        return View();
    }

    public async Task<IActionResult> Top()
    {
        return View();
    }

    public async Task<IActionResult> Bottom()
    {
        return View();
    }

    [Route("OverView/Toggle")]
    public async Task<IActionResult> Toggle()
    {
        return View();
    }

    [Route("OverView/Carouse")]
    public async Task<IActionResult> Carouse()
    {
        return View();
    }

    [Route("OverView/Evaluate")]
    public async Task<IActionResult> Evaluate()
    {
        return View();
    }

    [Route("OverView/Expander")]
    public async Task<IActionResult> Expander()
    {
        return View();
    }

    [Route("OverView/Gif")]
    public async Task<IActionResult> Gif()
    {
        return View();
    }

    [Route("OverView/Message")]
    public async Task<IActionResult> Message()
    {
        return View();
    }

    [Route("OverView/Upgrade")]
    public async Task<IActionResult> Upgrade()
    {
        return View();
    }

    [Route("OverView/Property")]
    public async Task<IActionResult> Property()
    {
        return View();
    }

    [Route("OverView/ProgressBar")]
    public async Task<IActionResult> ProgressBar()
    {
        return View();
    }

    [Route("OverView/Slider")]
    public async Task<IActionResult> Slider()
    {
        return View();
    }

    [Route("OverView/Tab")]
    public async Task<IActionResult> Tab()
    {
        return View();
    }

    [Route("OverView/Tree")]
    public async Task<IActionResult> Tree()
    {
        return View();
    }

    [Route("OverView/Observable")]
    public async Task<IActionResult> Observable()
    {
        return View();
    }

    [Route("OverView/Brush")]
    public async Task<IActionResult> Brush()
    {
        return View();
    }

    [Route("OverView/Shadow")]
    public async Task<IActionResult> Shadow()
    {
        return View();
    }

    [Route("OverView/Button")]
    public async Task<IActionResult> Button()
    {
        await MessageService.ShowWaittingMessge(() => Thread.Sleep(500));

        this.ViewModel.ButtonContentText = DateTime.Now.ToString();

        return View();

    }



    [Route("OverView/Grid")]
    public async Task<IActionResult> Grid()
    {
        return View();
    }

    [Route("OverView/Combobox")]
    public async Task<IActionResult> Combobox()
    {
        return View();
    }

    [Route("OverView")]
    public async Task<IActionResult> OverView()
    {
        await MessageService.ShowWaittingMessge(() => Thread.Sleep(500));

        MessageService.ShowSnackMessageWithNotice("OverView");

        return View();
    }

    [Route("OverView/TextBox")]
    public async Task<IActionResult> TextBox()
    {
        return View();
    }

    [Route("OverView/Book")]
    public async Task<IActionResult> Book()
    {
        return View();
    }

    [Route("OverView/Xaml")]
    public async Task<IActionResult> Xaml()
    {
        return View();
    }

    [Route("OverView/Dimension")]
    public async Task<IActionResult> Dimension()
    {
        return View();
    }

    [Route("OverView/Geometry")]
    public async Task<IActionResult> Geometry()
    {
        return View();
    }

    [Route("OverView/Panel")]
    public async Task<IActionResult> Panel()
    {
        return View();
    }
    [Route("OverView/Transform3D")]
    public async Task<IActionResult> Transform3D()
    {
        return View();
    }

    [Route("OverView/Drawer")]
    public async Task<IActionResult> Drawer()
    {
        return View();
    }
}

4.3.2 front end page

As follows, the red part corresponds to the Route to jump in the Controller

For example, if the red Button is selected, the Button() method will be called first to jump to the ButtonControl.xaml page under the View file corresponding to the current Controller

[Route("OverView/Button")]
public async Task<IActionResult> Button()
{
    await MessageService.ShowWaittingMessge(() => Thread.Sleep(500);

    this.ViewModel.ButtonContentText = DateTime.Now.ToString();

    return View();

}

You can write some business logic in the Button() method, such as adding, deleting, modifying and querying the current ViewModel. The ViewModel of the current Controller member is an internally encapsulated ViewModel, which corresponds to the ViewModel of the current Controller under the ViewModel file

4.3.3 example

4.3.4 the Xaml list on the left can be defined as follows:

<Grid>
    <wpfcontrollib:LinkGroupExpander ScrollViewer.HorizontalScrollBarVisibility="Disabled" x:Name="selectloyout" 
                                      SelectedLink="{Binding SelectLink,Mode=TwoWay}"
                                      Command="{x:Static wpfcontrollib:DrawerHost.CloseDrawerCommand}"
                                      CommandParameter="{x:Static Dock.Left}">
        <wpfcontrollib:LinkActionGroup DisplayName="Base control" Logo="&#xe69f;">
            <wpfcontrollib:LinkActionGroup.Links>
                <wpfcontrollib:LinkAction  DisplayName="Button" Logo="&#xe69f;"  Controller="Loyout" Action="Button" />
                <wpfcontrollib:LinkAction  DisplayName="TextBox"  Logo="&#xe6a3;" Controller="Loyout"  Action="TextBox"/>
                <wpfcontrollib:LinkAction  DisplayName="Combobox"  Logo="&#xe6a3;" Controller="Loyout" Action="Combobox"  />
                <wpfcontrollib:LinkAction  DisplayName="Toggle"  Logo="&#xe6a3;" Controller="Loyout" Action="Toggle"/>
                <wpfcontrollib:LinkAction  DisplayName="Evaluate" Logo="&#xe69f;" Controller="Loyout" Action="Evaluate"/>
                <wpfcontrollib:LinkAction  DisplayName="Expander" Logo="&#xe69f;" Controller="Loyout" Action="Expander"/>
                <wpfcontrollib:LinkAction  DisplayName="Gif" Logo="&#xe69f;" Controller="Loyout" Action="Gif"/>
                <wpfcontrollib:LinkAction  DisplayName="ProgressBar" Logo="&#xe69f;" Controller="Loyout" Action="ProgressBar"/>
                <wpfcontrollib:LinkAction  DisplayName="Slider" Logo="&#xe69f;" Controller="Loyout" Action="Slider"/>
            </wpfcontrollib:LinkActionGroup.Links>
        </wpfcontrollib:LinkActionGroup>

        <wpfcontrollib:LinkActionGroup DisplayName="Layout control" Logo="&#xe69f;">
            <wpfcontrollib:LinkActionGroup.Links>
                <wpfcontrollib:LinkAction  DisplayName="MdiControl" Logo="&#xe69f;" Controller="Loyout" Action="Mdi"/>
                <wpfcontrollib:LinkAction  DisplayName="Carouse" Logo="&#xe69e;" Controller="Loyout" Action="Carouse"/>
                <wpfcontrollib:LinkAction  DisplayName="Tab" Logo="&#xe69f;" Controller="Loyout" Action="Tab"/>
                <wpfcontrollib:LinkAction  DisplayName="Tree" Logo="&#xe69f;" Controller="Loyout" Action="Tree"/>
                <wpfcontrollib:LinkAction  DisplayName="ObservableSource" Logo="&#xe69f;" Controller="Loyout" Action="Observable"/>
                <wpfcontrollib:LinkAction  DisplayName="Property" Logo="&#xe69f;" Controller="Loyout" Action="Property"/>
                <wpfcontrollib:LinkAction  DisplayName="Panel" Logo="&#xe69f;" Controller="Loyout" Action="Panel"/> 
            </wpfcontrollib:LinkActionGroup.Links>
        
        </wpfcontrollib:LinkActionGroup>

        <wpfcontrollib:LinkActionGroup DisplayName="Global control" Logo="&#xe69f;">
            <wpfcontrollib:LinkActionGroup.Links>
                <wpfcontrollib:LinkAction  DisplayName="Message" Logo="&#xe69f;" Controller="Loyout" Action="Message"/>
                <wpfcontrollib:LinkAction  DisplayName="Upgrade" Logo="&#xe69e;" Controller="Loyout" Action="Upgrade"/>
                <wpfcontrollib:LinkAction  DisplayName="Drawer" Logo="&#xe69f;" Controller="Loyout" Action="Drawer"/> 
            </wpfcontrollib:LinkActionGroup.Links>
        </wpfcontrollib:LinkActionGroup>

        <wpfcontrollib:LinkActionGroup DisplayName="Global style" Logo="&#xe69f;">
            <wpfcontrollib:LinkActionGroup.Links>
                <wpfcontrollib:LinkAction  DisplayName="Brush" Logo="&#xe69f;" Controller="Loyout" Action="Brush"/>
                <wpfcontrollib:LinkAction  DisplayName="Shadow" Logo="&#xe69f;" Controller="Loyout" Action="Shadow"/>
                
            </wpfcontrollib:LinkActionGroup.Links>
        </wpfcontrollib:LinkActionGroup>

    </wpfcontrollib:LinkGroupExpander>
</Grid>

Through the LinkGroupExpander control, encapsulate the LinkAction to jump to the page. You only need to define several properties of the LinkAction to jump to the specified page, such as:

  • Controller attribute: used to indicate which controller to jump to
  • Action attribute: used to indicate which method to jump to
  • DisplayName property: the name displayed in the UI
  • Logo property: icon displayed in UI

The jump configuration corresponding to the Button() method in the Controller is as follows

[Route("OverView/Button")]
public async Task<IActionResult> Button()
<wpfcontrollib:LinkAction DisplayName="Button" Logo="&#xe69f;"  Controller="Loyout" Action="Button" />

4.3.5 definition of controller base class ControllerBase

The main method is IActionResult View([CallerMemberName] string name = ""). This method is the core function implemented by MVC. It mainly dynamically loads the assembly through reflection, loads the View and ViewModel in the project structure to generate IActionResult, and returns it to the main page for page Jump. The code is as follows:

public abstract class ControllerBase : IController
{
    protected virtual IActionResult View([CallerMemberName] string name = "")
    {
        var route = this.GetType().GetCustomAttributes(typeof(RouteAttribute), true).Cast<RouteAttribute>();

        string controlName = null;

        if (route.FirstOrDefault() == null)
        {
            controlName = this.GetType().Name;
        }
        else
        {
            controlName = route.FirstOrDefault().Name;
        }

        var ass = Assembly.GetEntryAssembly().GetName();

        string path = $"/{ass.Name};component/View/{controlName}/{name}Control.xaml";

        Uri uri = new Uri(path, UriKind.RelativeOrAbsolute);

        var content = Application.Current.Dispatcher.Invoke(() =>
        {
            return Application.LoadComponent(uri);
        });

        ActionResult result = new ActionResult();

        result.Uri = uri;
        result.View = content as ContentControl;

        Type type = Assembly.GetEntryAssembly().GetTypeOfMatch<NotifyPropertyChanged>(l => l.Name == controlName + "ViewModel");

        result.ViewModel = ServiceRegistry.Instance.GetInstance(type);

        Application.Current.Dispatcher.Invoke(() =>
        {
            (result.View as FrameworkElement).DataContext = result.ViewModel;

        });

        return result;
    }


    protected virtual IActionResult LinkAction([CallerMemberName] string name = "")
    {
        var route = this.GetType().GetCustomAttributes(typeof(RouteAttribute), true).Cast<RouteAttribute>();

        string controlName = null;

        if (route.FirstOrDefault() == null)
        {
            controlName = this.GetType().Name;
        }
        else
        {
            controlName = route.FirstOrDefault().Name;
        }

        var ass = Assembly.GetEntryAssembly().GetName();

        string path = $"/{ass.Name};component/View/{controlName}/{name}Control.xaml";

        Uri uri = new Uri(path, UriKind.RelativeOrAbsolute);

        var content = Application.Current.Dispatcher.Invoke(() =>
        {
            return Application.LoadComponent(uri);
        });

        ActionResult result = new ActionResult();

        result.Uri = uri;
        result.View = content;

        Type type = Assembly.GetEntryAssembly().GetTypeOfMatch<NotifyPropertyChanged>(l => l.Name == controlName + "ViewModel");

        result.ViewModel = ServiceRegistry.Instance.GetInstance(type);

        Application.Current.Dispatcher.Invoke(() =>
        {
            (result.View as FrameworkElement).DataContext = result.ViewModel;
        });

        return result;
    }

}

explain:

  1. Through Application.LoadComponent(uri); To load the build Control
  2. Find the corresponding ViewModel by reflecting the ViewModel base class NotifyPropertyChanged and bind it to the View
  3. Encapsulate the View and ViewModel into iationresult and return to the main page for loading

The method return type in the Controller is async Task, that is, the whole page Jump is performed asynchronously, which can effectively avoid the blocking effect in page switching

4.4 structure and definition in view

The definition of View in the project is corresponding to the method in the Controller. In MVC, the structure definition [View/Loyout] should be strictly followed. The advantage is that it can reduce the amount of code, unify the format and tidy the code. The structure is as follows:

The red ButtonControl.xaml is the page to jump to by the Button() method in the Controller. The same is true for other pages

4.5 structure and definition of ViewModel

LoyoutViewModel is the ViewModel corresponding to the LoyoutController and all pages under the whole View/Loyout

4.6 the effects of the overall MVC structure are as follows:

The above is a brief example of MVC application in WPF. The specific contents and examples can be downloaded from the following link

Code address: https://github.com/HeBianGu/WPF-ControlBase.git

Another example of applying a Sqlite database is as follows

Code address: https://github.com/HeBianGu/WPF-ExplorerManager.git

5. Description of other functions

Original title: example: Custom WPF Underlying control UI library HeBianGu.General.WpfControlLib V2.0 edition
 Original link: https://blog.csdn.net/u010975589/article/details/103083605

5.1 purpose

Encapsulates some controls into a custom control library to facilitate rapid development

5.2 realization of functions

  • The common basic controls are basically realized to meet the rapid development of conventional software
  • It also supports. Net core 3.0 +, and. Net framework 4.5+

5.3 overall overview

5.3.1 login page

The login page only needs to inherit the LoginWindowBase base class and set the Style="{StaticResource S.Window.Login.Default}"

5.3.2 main page

The main page only needs to inherit the LinkWindowBase base base class and set the Style="{DynamicResource S.Window.Link.Default}"

The overall main window is loaded in the ViewBox mode. When the window is zoomed or applied to other resolution devices, it will be compatible

5.3.3 saving subject configuration information

The theme configuration information has been encapsulated in ApplicationBase, and the set configuration information (such as theme color, font size, etc.) will be automatically saved when exiting

Summary: the application of this mode can achieve the purpose of reuse and encapsulate the general part to the bottom. If you need to modify the Style, you only need to modify the Style file or modify the dependent attributes to meet the function modification

5.4 theme setting

Examples of light themes are as follows:

Examples of dark themes are shown below:

The theme setting function mainly includes:

  1. Set theme main color

Theme color is mainly used to identify the part to be highlighted. At present, you can select the built-in color, follow the system theme color, customize the selection color, and use the dynamic theme (that is, set the theme to change automatically every specified time)

  1. set up themes

At present, the theme has realized four middle school themes, namely light color theme, dark color theme, gray theme and main color theme

  1. Set font size

At present, there are two built-in Font Sizes: Large and Small. These two colors are loaded by injection, that is, the initial values of the two fonts can be set when the program is loaded

  1. Other configurations

Including Chinese and English, setting the standard line height, etc. initialization settings can be made when the program is loaded, which will not be introduced here

**Summary: * * the purpose of this design is that aesthetics varies from person to person. Using custom configuration can meet changeable needs as much as possible

5.5 other basic controls

5.5.1 data table

  • a is compatible with theme font and theme settings. All controls to be mentioned later have applied theme settings and will not be described again
  • b) number of display bars per page

You can set the number of entries to be displayed on each page

  • c search

You can set search filter criteria so that entries containing specified search terms will be displayed

  • d page Jump

You can select previous page, next page, first page, last page and specified page

  • e page information

The number of entries to which the current page belongs to the data source, and the total number of entries of the data source

  • f two styles of grid pages

**Summary: * * the above functions are encapsulated in the control pageddatgrid. You can realize the above functions by binding the data source. Printing, export and other functions are not realized for the time being

5.5.2 tree list

  • a supports filtering by category

As shown in the figure above, select the specified type to filter the list

  • b support search by criteria

As shown in the figure above, you can filter the specified conditions by entering conditions

**Summary: * * the usage method is to bind the data source to the TreeListView control

5.5.3 other common controls

  • a dialog box

The built-in dialog box is not an application window, but an overlay, which can avoid some problems caused by the window dialog box

  • b dialogue window custom dialogue window

Compared with the system dialog window, it is more beautiful and increases the display and hiding effects. The number and functions of buttons can be customized by injection

  • c message list

At present, there are two modes: display in Window and display in Window system. You can customize the display mode according to your needs. Examples are as follows

  • d an example of online upgrade is shown below
  • e example of navigation menu is as follows
  • f other functions include

Button control, text input box control, drop-down list control, number control, date selection control, password box control supporting binding, progress bar control, drag control, tree control, pagination control and other custom controls.

The above controls have realized theme color and font size switching, which can meet the functions of common software

The overall structure is loaded in the custom Mvc mode, and the reference address is: https://blog.csdn.net/u010975589/article/details/100019431

Because there are too many controls to be introduced in detail, those interested can download the source code or load the nuget package

5.6 mode of use

The nuget package is added as shown in the figure below

Note: part of the functional code of this example refers to the third-party framework. Open source is only used for learning and reference, not for commercial purposes.

Other examples of applying this framework:

  • Example: generic GitHub client UI layout developed by WPF_ HeBianGu's blog - CSDN blog
  • Example: imitation Baidu network disk client UI layout developed by wpf_ HeBianGu's blog - CSDN blog_ wpf network disk
  • Example: preview the effect of composite drawing of lightweight Chart drawing with WPF_ HeBianGu's blog - CSDN blog
  • Packaging: WPF video player based on Vlc.DotNet.Wpf packaging_ HeBianGu's blog - CSDN blog
  • Example: the Image picture control developed by WPF supports bird Image, wheel magnification, magnifying glass, delineated range and delineated range magnification (example 1)_ HeBianGu's blog - CSDN blog

5.7 download address

GitHub download address: GitHub - hebiangu / WPF controlbase: WPF encapsulated custom control repository

Installation package sample download address:

  • Link: https://pan.baidu.com/s/1y2UfDKIxoSOffj36gl7fOw
  • Extraction code: l2ia

Update: add. Net Core 3.0 on December 16, 2019

At present, core 3.0 and. net 4.5 are supported. If there is a solution assembly that cannot be loaded, please install these two frameworks

Posted on Wed, 01 Dec 2021 14:20:06 -0500 by slands10