
The repository README is very plain, but after reading the author's blog posts linked in the README, you will like it. Without further ado, here is the table of contents:
- Animation Encapsulation
https://blog.csdn.net/u010975589/article/details/95974854
- Property Form
https://blog.csdn.net/u010975589/article/details/95970200
- Message Dialog
https://blog.csdn.net/u010975589/article/details/95985190
- Applying MVC in WPF
https://blog.csdn.net/u010975589/article/details/100019431
- Other Feature Descriptions
https://blog.csdn.net/u010975589/article/details/103083605
Detailed introduction below:
1. Animation Encapsulation
Original title: Example: Custom StoryBoardService in WPF to encapsulate StoryBoard and Animation in code to simplify animation writing
Original link: https://blog.csdn.net/u010975589/article/details/95974854
1.1 Purpose: Simplify animation writing by encapsulating StoryBoard and Animation
1.2 Example

Description: Fade out is a commonly used animation in WPF. The image above shows the effect after encapsulation via StoryBoarService. In code, just execute the following:
DoubleStoryboardEngine.Create(1, 0, 1, "Opacity").Start(element);
The close effect above can be defined as a command like this:
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;
}
Invoke the following command in Xaml to achieve the close fade-out effect:
Command="{x:Static base:CommandService.CollapsedOfOpacityCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource
AncestorType=GroupBox}}"
The CommandParameter passed in will fade out when the command is executed.
The animation effect only requires one line of code, simplifying the tedious coding process for animations in code.
DoubleStoryboardEngine.Create(1, 0, 1, "Opacity").Start(element);
1.3 Code:
Currently, only the DoubleAnimation encapsulation is implemented; other types will be encapsulated later.
1.3.1 Base class for encapsulation modification
/// <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 Exposed extension DoubleStoryboardEngine
/// <summary> DoubleAnimation 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: Property 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 Easing function factory
/// <summary> Description: https://docs.microsoft.com/en-us/dotnet/framework/wpf/graphics-multimedia/easing-functions </summary>
public static class EasingFunctionFactroy
{
/// <summary> PowerEase: Creates an animation that accelerates and/or decelerates using the formula f(t) = t^p, where p equals the Power property. </summary>
public static PowerEase PowerEase { get; set; } = new PowerEase();
/// <summary> BackEase: Retracts the motion of an animation slightly before it begins to animate in the path indicated. </summary>
public static BackEase BackEase { get; set; } = new BackEase();
/// <summary> ElasticEase: Creates an animation that resembles a spring oscillating back and forth until it comes to rest. </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: Creates an animation that accelerates and/or decelerates using a circular function. </summary>
public static CircleEase CircleEase { get; set; } = new CircleEase();
/// <summary> QuadraticEase: Creates an animation that accelerates and/or decelerates using the formula f(t) = t^2. </summary>
public static QuadraticEase QuadraticEase { get; set; } = new QuadraticEase();
/// <summary> CubicEase: Creates an animation that accelerates and/or decelerates using the formula f(t) = t^3. </summary>
public static CubicEase CubicEase { get; set; } = new CubicEase();
/// <summary> QuarticEase: Creates an animation that accelerates and/or decelerates using the formula f(t) = t^4. </summary>
public static QuarticEase QuarticEase { get; set; } = new QuarticEase();
/// <summary> QuinticEase: Creates an animation that accelerates and/or decelerates using the formula f(t) = t^5. </summary>
public static QuinticEase QuinticEase { get; set; } = new QuinticEase();
/// <summary> ExponentialEase: Creates an animation that accelerates and/or decelerates using an exponential formula. </summary>
public static ExponentialEase ExponentialEase { get; set; } = new ExponentialEase();
/// <summary> SineEase: Creates an animation that accelerates and/or decelerates using a sine formula. </summary>
public static SineEase SineEase { get; set; } = new SineEase();
}
1.3.4 Usage
/// <summary> Constructor </summary>
/// <param name="from"> Starting value</param>
/// <param name="to"> Ending value </param>
/// <param name="second"> Duration in seconds </param>
/// <param name="property"> Property name to modify </param>
///
public static DoubleStoryboardEngine Create(double from, double to, int second, string property)
{
return new DoubleStoryboardEngine(from, to, second, property);
}
2. Property Form
Original title: Example: Simple ObjectProperyForm developed in WPF for binding entity forms
Original link: https://blog.csdn.net/u010975589/article/details/95970200
2.1 Purpose: Custom control to directly bind entity data, simplifying development cycles
2.2 Implementation
- Bind entity object
- Display property name via attributes
- Add validation conditions via attributes
- Currently implemented DataTemplates for String, Int, Double, DateTime, Bool simple types; other templates are extensible
- More updates to follow...
2.3 Example

Entity definition:
public class Student
{
[Display("Name")]
[Required]
public string Name { get; set; }
[Display("Class")]
[Required]
public string Class { get; set; }
[Display("Address")]
[Required]
public string Address { get; set; }
[Display("Email")]
[Required]
public string Emall { get; set; }
[Display("Enabled")]
[Required]
public bool IsEnbled { get; set; }
[Display("Time")]
[Required]
public DateTime time { get; set; }
[Display("Age")]
[Required]
public int Age { get; set; }
[Display("Average Score")]
public double Score { get; set; }
[Display("Phone Number")]
[Required]
[RegularExpression(@"^1[3|4|5|7|8][0-9]{9}$", ErrorMessage = "Invalid phone number!")]
public string Tel { get; set; }
}
- DisplayAttribute: used to identify the display name
- RequiredAttribute: used to indicate that data cannot be empty
- RegularExpression: references regex to validate data matching
- Other attributes to be added later...
Usage:
<UserControl.Resources>
<local:Student
x:Key="S.Student.HeBianGu"
Name="HeBianGu"
Address="Gaoxin District, Chengdu City, Sichuan Province"
Class="Grade 4"
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
></wpfcontrollib:ObjectPropertyForm>
2.4 Code
2.4.1 Get properties and attributes via 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 base type, extension classes, and factory method
/// <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: Validate data
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 property type </summary>
public class StringPropertyItem : ObjectPropertyItem<string>
{
public StringPropertyItem(PropertyInfo property, object obj) : base(property, obj)
{
}
}
/// <summary> DateTime property type </summary>
public class DateTimePropertyItem : ObjectPropertyItem<DateTime>
{
public DateTimePropertyItem(PropertyInfo property, object obj) : base(property, obj)
{
}
}
/// <summary> Double property type </summary>
public class DoublePropertyItem : ObjectPropertyItem<double>
{
public DoublePropertyItem(PropertyInfo property, object obj) : base(property, obj)
{
}
}
/// <summary> Int property type </summary>
public class IntPropertyItem : ObjectPropertyItem<int>
{
public IntPropertyItem(PropertyInfo property, object obj) : base(property, obj)
{
}
}
/// <summary> Bool property 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 templates
<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=""
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=""
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=""
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=""
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=""
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 Extensibility
2.4.4.1 Just define an extension type, e.g.:
/// <summary> String property type </summary>
public class StringPropertyItem : ObjectPropertyItem<string>
{
public StringPropertyItem(PropertyInfo property, object obj) : base(property, obj)
{
}
}
2.4.4.2 Then add a DataTemplate, e.g.:
<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=""
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 Dialog
Original title: Example: Custom MessageService in WPF using DialogHost, Snackbar, NotifyIcon to display various scenario prompts
Original link: https://blog.csdn.net/u010975589/article/details/95985190
3.1 Purpose
Different interaction scenarios require different messages, and different messages need different effects to display. Use DialogHost, NotifyIcon, Snackbar to display various scenario prompt messages, applicable in ViewModel.
3.2 Implementation
- Waiting dialog
- Confirmation dialog
- Confirm and cancel dialog
- Percentage progress and text progress dialog
- Toast notification (NotifyIcon)
- Snackbar message
3.3 Example

Description:
- Dialog: Conventional dialog messages as shown above, waiting dialog, message dialog, progress dialog;
(Currently only these types are encapsulated; custom dialogs only require creating a user control and calling the general loading method. More updates to come...)
Prompt message: When progress saves successfully, a prompt message is needed, displayed for 2s and then auto-hides (as shown in the "Friendly Tip" section);
Toast notification: When the program is in a hidden or certain state, a toast notification message is needed;
3.4 Code
[ViewModel("Loyout")]
class LoyoutViewModel : MvcViewModelBase
{
/// <summary> General command method </summary>
protected override async void RelayMethod(object obj)
{
string command = obj?.ToString();
// Do: Dialog message
if (command == "Button.ShowDialogMessage")
{
await MessageService.ShowSumitMessge("This is a message dialog?");
}
// Do: Waiting message
else if (command == "Button.ShowWaittingMessge")
{
await MessageService.ShowWaittingMessge(() => Thread.Sleep(2000));
}
// Do: Percentage progress dialog
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
else if (command == "Button.ShowStringProgress")
{
Action<IStringProgress> action = l =>
{
for (int i = 1; i <= 100; i++)
{
l.MessageStr = $"Submitting page {i} of 100 data items";
Thread.Sleep(50);
}
Thread.Sleep(1000);
MessageService.ShowSnackMessageWithNotice("Submission complete: 100 succeeded, 0 failed!");
};
await MessageService.ShowStringProgress(action);
}
// Do: Confirm/cancel dialog
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 confirm");
}
};
MessageService.ShowResultMessge("Are you sure you want to exit the system?", action);
}
// Do: Prompt message
else if (command == "Button.ShowSnackMessage")
{
MessageService.ShowSnackMessageWithNotice("This is a prompt message?");
}
// Do: Toast notification
else if (command == "Button.ShowNotifyMessage")
{
MessageService.ShowNotifyMessage("You have an alarm message to handle, please check", "Notify By HeBianGu");
}
}
}
4. Applying MVC in WPF
Original title: Encapsulation: Brief introduction to custom MVC framework based on WPF
Original link: https://blog.csdn.net/u010975589/article/details/100019431
4.1 Purpose
When using ASP.NET Core, I deeply appreciate the convenience of the MVC framework for page navigation and data processing. However, there seems to be no ready-made MVC framework in WPF. Therefore, I developed a custom MVC framework and experienced its advantages during use. Below is a brief introduction to this MVC framework based on MVVM.
4.2 Project Structure

It mainly consists of three parts: Controller, View, ViewModel
Where View and ViewModel follow the traditional WPF MVVM pattern.
The difference lies in page navigation, which is controlled by the Controller. Here is an example of a Controller definition.
4.3 Controller Structure and Definition
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 Frontend Page
As shown below, the red part corresponds to the Route to be navigated in the Controller.

For example, when the red "Button" is selected, the Button() method is called first, and then the page navigates to the ButtonControl.xaml file under the corresponding View folder of 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 business logic in the Button() method, such as CRUD operations on the current ViewModel. The ViewModel member in the current Controller is the internally encapsulated ViewModel, which corresponds to the ViewModel of the current Controller under the ViewModel folder.
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="Basic Controls" Logo="">
<wpfcontrollib:LinkActionGroup.Links>
<wpfcontrollib:LinkAction
DisplayName="Button"
Logo=""
Controller="Loyout"
Action="Button"
/>
<wpfcontrollib:LinkAction
DisplayName="TextBox"
Logo=""
Controller="Loyout"
Action="TextBox"
/>
<wpfcontrollib:LinkAction
DisplayName="Combobox"
Logo=""
Controller="Loyout"
Action="Combobox"
/>
<wpfcontrollib:LinkAction
DisplayName="Toggle"
Logo=""
Controller="Loyout"
Action="Toggle"
/>
<wpfcontrollib:LinkAction
DisplayName="Evaluate"
Logo=""
Controller="Loyout"
Action="Evaluate"
/>
<wpfcontrollib:LinkAction
DisplayName="Expander"
Logo=""
Controller="Loyout"
Action="Expander"
/>
<wpfcontrollib:LinkAction
DisplayName="Gif"
Logo=""
Controller="Loyout"
Action="Gif"
/>
<wpfcontrollib:LinkAction
DisplayName="ProgressBar"
Logo=""
Controller="Loyout"
Action="ProgressBar"
/>
<wpfcontrollib:LinkAction
DisplayName="Slider"
Logo=""
Controller="Loyout"
Action="Slider"
/>
</wpfcontrollib:LinkActionGroup.Links>
</wpfcontrollib:LinkActionGroup>
<wpfcontrollib:LinkActionGroup DisplayName="Layout Controls" Logo="">
<wpfcontrollib:LinkActionGroup.Links>
<wpfcontrollib:LinkAction
DisplayName="MdiControl"
Logo=""
Controller="Loyout"
Action="Mdi"
/>
<wpfcontrollib:LinkAction
DisplayName="Carouse"
Logo=""
Controller="Loyout"
Action="Carouse"
/>
<wpfcontrollib:LinkAction
DisplayName="Tab"
Logo=""
Controller="Loyout"
Action="Tab"
/>
<wpfcontrollib:LinkAction
DisplayName="Tree"
Logo=""
Controller="Loyout"
Action="Tree"
/>
<wpfcontrollib:LinkAction
DisplayName="ObservableSource"
Logo=""
Controller="Loyout"
Action="Observable"
/>
<wpfcontrollib:LinkAction
DisplayName="Property"
Logo=""
Controller="Loyout"
Action="Property"
/>
<wpfcontrollib:LinkAction
DisplayName="Panel"
Logo=""
Controller="Loyout"
Action="Panel"
/>
</wpfcontrollib:LinkActionGroup.Links>
</wpfcontrollib:LinkActionGroup>
<wpfcontrollib:LinkActionGroup DisplayName="Global Controls" Logo="">
<wpfcontrollib:LinkActionGroup.Links>
<wpfcontrollib:LinkAction
DisplayName="Message"
Logo=""
Controller="Loyout"
Action="Message"
/>
<wpfcontrollib:LinkAction
DisplayName="Upgrade"
Logo=""
Controller="Loyout"
Action="Upgrade"
/>
<wpfcontrollib:LinkAction
DisplayName="Drawer"
Logo=""
Controller="Loyout"
Action="Drawer"
/>
</wpfcontrollib:LinkActionGroup.Links>
</wpfcontrollib:LinkActionGroup>
<wpfcontrollib:LinkActionGroup DisplayName="Global Styles" Logo="">
<wpfcontrollib:LinkActionGroup.Links>
<wpfcontrollib:LinkAction
DisplayName="Brush"
Logo=""
Controller="Loyout"
Action="Brush"
/>
<wpfcontrollib:LinkAction
DisplayName="Shadow"
Logo=""
Controller="Loyout"
Action="Shadow"
/>
</wpfcontrollib:LinkActionGroup.Links>
</wpfcontrollib:LinkActionGroup>
</wpfcontrollib:LinkGroupExpander>
</Grid>
Through the LinkGroupExpander control, encapsulate LinkAction to achieve page navigation. Only need to define a few properties of LinkAction to navigate to the specified page, for example:
- Controller property: indicates which Controller to navigate to
- Action property: indicates which method to navigate to
- DisplayName property: name displayed in the UI
- Logo property: icon displayed in the UI
For example, the configuration for the Button() method in the Controller is as follows:
[Route("OverView/Button")]
public async Task<IActionResult> Button()
<wpfcontrollib:LinkAction
DisplayName="Button"
Logo=""
Controller="Loyout"
Action="Button"
/>
4.3.5 ControllerBase Definition
The main method is IActionResult View([CallerMemberName] string name = ""). This method is the core of the MVC implementation. It uses reflection to dynamically load the assembly, load the View and ViewModel from the project structure, and generate an IActionResult to return to the main page for navigation. 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;
}
}
Description:
- Load and generate the Control via Application.LoadComponent(uri);
- Find the corresponding ViewModel via reflection on the ViewModel base class NotifyPropertyChanged, and bind it to the View;
- Encapsulate the View and ViewModel into IActionResult and return it to the main page for loading.
The method return type in the Controller is async Task
4.4 View Structure and Definition
The View in the project corresponds to the methods in the Controller. It must strictly follow the structure [View/Loyout] in MVC. The benefit is reduced code amount and uniform code structure. The structure is as follows:

The red ButtonControl.xaml is the page that the Button() method in the Controller navigates to. Other pages follow the same logic.
4.5 ViewModel Structure and Definition
LoyoutViewModel corresponds to LoyoutController and all pages under View/Loyout.

4.6 Overall MVC Structure Effect

The above is a brief example of applying MVC in WPF. For details and examples, download the code from the following links.
Code repository: https://github.com/HeBianGu/WPF-ControlBase.git
Another example using Sqlite database is as follows:
Code repository: https://github.com/HeBianGu/WPF-ExplorerManager.git
5. Other Feature Descriptions
Original title: Example: Custom WPF base control UI library HeBianGu.General.WpfControlLib V2.0
Original link: https://blog.csdn.net/u010975589/article/details/103083605
5.1 Purpose
Encapsulate some controls into a custom control library for rapid development.
5.2 Implemented Features
- Basically implemented commonly used basic controls to meet rapid development of conventional software
- 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 to Style="{StaticResource S.Window.Login.Default}".
5.3.2 Main Page

The main page only needs to inherit the LinkWindowBase base class and set the style to Style="{DynamicResource S.Window.Link.Default}".
The entire main window uses a ViewBox layout, so it is compatible when scaled or applied to other resolution devices.
5.3.3 Theme Configuration Saving

Theme configuration information has been encapsulated in ApplicationBase. It will automatically save the set configuration (such as theme color, font size, etc.) when exiting.
Summary: Applying this pattern achieves reusability by encapsulating common parts in the base layer. To modify the style, simply modify the Style file or change dependency properties.
5.4 Theme Settings

Light theme example:

Dark theme example:

Theme settings mainly include:
- Set primary theme color
The primary color is used to highlight the parts to be emphasized. Currently, you can choose built-in colors, follow the system theme color, customize a color, or use a dynamic theme (i.e., set the theme to automatically change at specified intervals).
- Set theme
Currently four themes are implemented: light, dark, gray, and primary color theme.
- Set font size
Two built-in font sizes: Large and Small. These two are loaded via injection, meaning the initial values can be set when the program loads.
- Other configurations
Includes Chinese/English, setting standard row height, etc., which can be initialized at program load. Not detailed here.
Summary: The design intent is that aesthetics vary from person to person; using custom configuration can meet changing needs as much as possible.
5.5 Other Basic Controls
5.5.1 Data Grid

- a Compatible with theme font and settings; all subsequent controls have applied theme settings, no further explanation.
- b Rows per page
You can set the number of rows displayed per page.
- c Search
You can set search filter criteria; only items containing the specified search term will be displayed.
- d Page navigation
Previous page, next page, first page, last page, specific page.
- e Page information
The range of items currently displayed (from which to which) and the total number of items in the data source.
- f Two styles of grid pages
Summary: The above functions are encapsulated in the PagedDataGrid control. Simply bind the data source to achieve the above features. Printing and export functions are not yet implemented.
5.5.2 Tree List

- a Supports filtering by category
As shown above, select a specific type to filter the list.
- b Supports conditional search
As shown above, enter a condition to filter specific criteria.
Summary: Usage is to bind the data source to the TreeListView control.
5.5.3 Other Common Controls


- a Dialog
Uses built-in dialog, not a window, but an overlay layer, avoiding some issues caused by window dialogs.
- b Custom dialog window
More aesthetically pleasing than system dialogs, with show/hide animations. By injection, you can customize the number and functionality of buttons.
- c Message list
Currently two modes: displayed within the window and in the Windows system. You can customize the display method based on requirements. Example below:

- d Online upgrade example

- e Navigation menu example

- f Other features include
Button control, text input box control, dropdown list control, numeric control, date picker control, bindable password box control, progress bar control, drag control, tree control, pagination control, and other custom controls.
All the above controls support theme color and font size switching, suitable for common software functionality.
The overall structure uses a custom MVC loading method. Reference: https://blog.csdn.net/u010975589/article/details/100019431
Due to the large number of controls, not detailed here. Interested parties can download the source code or install the NuGet package.
5.6 Usage
Add the NuGet package as shown below:

Note: Some parts of this example reference third-party frameworks. The open source is only for learning and reference, not for commercial purposes.
Other examples using this framework:
- Example: WPF developed imitation GitHub client UI layout_HeBianGu's Blog-CSDN
- Example: WPF developed imitation Baidu Disk client UI layout_HeBianGu's Blog-CSDN
- Example: WPF drawing lightweight chart combination chart effect preview_HeBianGu's Blog-CSDN
- Encapsulation: WPF video player encapsulated based on Vlc.DotNet.Wpf_HeBianGu's Blog-CSDN
- Example: WPF developed Image control, supporting bird's-eye view, scroll zoom, magnifier, area selection and area zoom (Example 1)_HeBianGu's Blog-CSDN
5.7 Download Links
GitHub download: GitHub - HeBianGu/WPF-ControlBase: Wpf encapsulated custom control resource library
Example installer download:
- Link: https://pan.baidu.com/s/1y2UfDKIxoSOffj36gl7fOw
- Extraction code: l2ia
Update: 2019.12.16 Added .NET Core 3.0
Now supports Core3.0 and .NET 4.5. If the solution assembly fails to load, please install these two frameworks.