.NET libraries containing utilities and controls that target .NET Standard, .NET 5, .NET 5 WPF, .NET Core, .NET Framework, .NET Core WPF, .NET Framework WPF and UWP
class (example) | description | namespace |
---|---|---|
ViewModel |
Configurable implementation of IViewModel , INotifyPropertyChanged , INotifyDataErrorInfo with property validation support via delegates or Lambda expressions |
.Net.Standard |
AsyncRelayCommand<T> |
Implementatiom of ICommand that supports asynchrnous command execution. |
.Net.Core.Wpf .Net.Framework.Wpf .Net.Uwp |
Extension Methods for WPF | E.g., DependencyObject.TryFindVisualChildElement , DependencyObject.TryFindVisualChildElementByName , DependencyObject.TryFindVisualParentElement , DependencyObject.TryFindVisualParentElementByName , DependencyObject.EnumerateVisualChildElements , object.ToDictionary |
.Net.Core.Wpf .Net.Framework.Wpf |
Extension Methods for .NET Standard | .Net.Standard | |
ValueConverters | Default implementations of IValueConverter and IMultiValueConverter e.g., BooleanMultiValueConverter , FilePathTruncateConverter , BoolToStringConverter , InvertValueConverter |
.Net.Core.Wpf .Net.Framework.Wpf |
Collections WPF | ObservablePropertyChangedCollection<T> |
.Net.Core.Wpf .Net.Framework.Wpf |
Profiler |
.Net.Standard | |
AppSettingsConnector |
A default API to the AppSettings that provides strongly typed reading and writing (e.g. bool , int , double , string ) of key-value pair values. |
.Net.Core.Wpf .Net.Framework.Wpf |
MruManager |
Most Recently Used (MRU) file manager. An API that maintains an MRU table stored in the Application Settings file. | .Net.Core.Wpf .Net.Framework.Wpf |
EventAggregator |
Implementation of the Event Aggregator pattern that supports dynamic aggregation of different typed event sources. | .Net.Standard |
AutoResetStream |
A Stream decorator that automatically resets the stream's position after read/write access. |
.Net.Standard |
Dialog |
Easy to use attached behavior and infrastructure to allow MVVM friendly dialog handling from a view model class in a fire-and-forget manner. To display dialogs implement IDialogViewModel classes and create a DataTemplate for each implementation. The DataTemplate is the rendered in a native Window . Addition attached properties allow for styling of the dialog Window or to assign an optional DataTemplateSelector . The attached behavior will handle showing and closing of the dialog. |
.Net.Core.Wpf .Net.Framework.Wpf |
Attached Behaviors for WPF | Popup - e.g., allows to make the Popup sticky and moves it with the current placement target. TextControl - Allows to highlight text ranges in TextBlock and RichTextBox controls. PasswordBox - Enables to send the PasswordBox.SecurePassword value to the view model using a ICommand . |
.Net.Core.Wpf .Net.Framework.Wpf |
EventArgs | ValueChangedEventArgs<T> , ValueEventArgs<T> , PropertyValueChangedArgs<T> |
.Net.Standard |
Markup Extensions | InvertExtension , EnumExtension , PrimitiveTypeExtension , EqualsExtension |
.Net.Core.Wpf .Net.Framework.Wpf |
- Abstract base view model class that implements and encapsulates
INotifyPropertyChanged
andINotifyDataErrorInfo
. - Allows to control whether invalid data is set on a property or neglected until validation passes by setting the default parameter
isRejectInvalidValueEnabled
ofTrySetValue()
totrue
(neglects invalid values by default). - Also allows to control whether to throw an exception on validation error or not (silent) by setting the default parameter
isThrowExceptionOnValidationErrorEnabled
ofTrySetValue()
totrue
(is silent by default). - Additionally exposes a
PropertyValueChanged
event which is raised in tandem withINotifyPropertyChanged.PropertyChanged
but additionally carries old value and new value as event args.
private string name;
public string Name
{
get => this.name;
set
{
if (TrySetValue(
value,
(stringValue) =>
{
var messages = new List<string>() {"Name must start with an underscore"};
return (stringValue.StartsWith("_"), messages);
},
ref this.name))
{
DoSomething(this.name);
}
}
}
private string name;
public string Name
{
get => this.name;
set
{
if (TrySetValue(value, ref this.name))
{
DoSomething(this.name);
}
}
}
Reusable generic command class that encapsulates ICommand
and allows asynchronous execution.
When used with a Binding
, the command will always execute asynchronously as long as an awaitable execute handler is assigned to the command.
Declare async ICommand
:
// ICommand. The command delegate 'Task ProcessStringAsync(string)' returns a 'Task' object and is awaitable.
public IAsyncRelayCommand<string> SomeAsyncCommand => new AsyncRelayCommand<string>(ProcessStringAsync);
Execute XAML:
<!-- Executes asynchronously, because an awaitable delegate was registered with the IAsyncRelayCommand -->
<Button Command="{Binding SomeAsyncCommand}" />
Execute C#:
// Execute asynchronously
await this.SomeAsyncCommand.ExecuteAsync("String value");
// Execute synchronously
this.SomeAsyncCommand.Execute("String value");
var listView = new ListView();
if (listView.TryFindVisualChildElement(out ScrollViewer scrollViewer)
{
scrollViewer.ScrollToVerticalOffset(12);
}
var myControl = new MyControl();
if (myControl.TryFindVisualChildElementByName("PART_ContentHost", out ContentPresenter contentPresenter)
{
var content = contentPresenter.Content;
}
var myControl = new MyControl();
foreach (TextBox childTextBox in myControl.EnumerateVisualChildElements<TextBox>()
{
childTextBox.Text = string.Empty;
}
var listView = new ListView();
if (listView.TryFindVisualParentElement(out MyControl myControl)
{
myControl.DoSomething();
}
var listView = new ListView();
if (listView.TryFindVisualParentElementByName("MyControl", out MyControl myControl)
{
myControl.DoSomething();
}
var myClass = new MyClass();
Dictionary<string, object> myClassDictionary = myClass.ToDictionary();
object value = myClass["PropertyName"];
Implementation of IValueConverter
that converts a bool
to a custom string representation e.g., convert true
to "Enabled"
.
<ToggleButton IsChecked="{Binding IsEnabled}">
<ToggleButton.Content>
<Binding Path="IsEnabled">
<Binding.Converter>
<BoolToStringConverter TrueValue="On"
FalseValue="{Binding DisabledText}"
NullValue="Undefined" />
</Binding.Converter>
</Binding>
</ToggleButton.Content>
</ToggleButton>
Implementation of IValueConverter
that inverts a bool
, Visibility
, double
, decimal
, float
, int
.
<ToggleButton>
<ToggleButton.IsChecked>
<Binding>
<Binding.Converter>
<InvertValueConverter />
</Binding.Converter>
</Binding>
</ToggleButton.IsChecked>
</ToggleButton>
Static helper methods to measure performance e.g. the execution time of a code portion.
// Specify a custom output
Profiler.LogPrinter = (timeSpan) => PrintToFile(timeSpan);
// Measure the average execution time of a specified number of iterations.
TimeSpan elapsedTime = Profiler.LogAverageTime(() => ReadFromDatabase(), 1000);
// Measure the execution times of a specified number of iterations.
List<TimeSpan> elapsedTime = Profiler.LogTimes(() => ReadFromDatabase(), 1000);
// Measure the execution time.
TimeSpan elapsedTime = Profiler.LogTime(() => ReadFromDatabase());
Invokes a ICommand
when PasswordBox.PasswordChanged
event was raised. The password is send as SecureString
to secure the value.
<PasswordBox PasswordBox.Command="{Binding VerifyPasswordCommand}" />
Attached behavior that supports dynamic text highlighting for controls derived from TextBlock
or RichTextBox
.
<!-- Bind Text and HighlightRanges to a view model -->
<TextBlock Text="{Binding Message}"
TextControl.HighlighBackground="Orange"
TextControl.HighlighForeground="White"
TextControl.HighlightRanges="{Binding HighlightTextRanges}"
TextControl.IsHighlighEnabled="True" />
<!-- Alternatively define the HighlightRange items inline -->
<TextBlock Text="{Binding Message}"
TextControl.HighlighBackground="Orange"
TextControl.HighlighForeground="White"
TextControl.IsHighlighEnabled="True">
<attachedBehaviors:TextControl.HighlightRanges>
<attachedBehaviors:HighlightRange StartIndex="2" EndIndex="8"/>
<attachedBehaviors:HighlightRange StartIndex="12" EndIndex="15"/>
</attachedBehaviors:TextControl.HighlightRanges>
</TextBlock>
<!--
Use a RichTextBox and bind it to a string by binding TextControl.Text instead of RichTextBox.Document.
The text will be automatically converted to a FlowDocument and assigned to RichTextBox.Document.
-->
<RichTextBox TextControlText="{Binding Message}"
TextControl.HighlighBackground="Orange"
TextControl.HighlighForeground="White"
TextControl.IsHighlighEnabled="True"
TextControl.HighlightRanges="{Binding HighlightTextRanges}" />
Set of attached behaviors for the System.Windows.Controls.Primitives.Popup
control.
When Popup.IsSticky
is set to true
, the Popup
is forced to stick to the current Popup.PlacementTarget
. The Popup
will follow the Popup.PlacementTarget
whenever it changes it's screen coordinates.
<Popup Popup.IsSticky="True" />
Generic EventArgs
implementation that provides value change information like OldValue
and NewValue
.
// Specify a named ValueTuple as event argument
event EventHandler<ValueChangedEventArgs<(bool HasError, string Message)>> Completed;
// Publish event
protected virtual void RaiseCompleted((bool HasError, string Message) oldValue, (bool HasError, string Message) newValue)
{
this.Completed?.Invoke(this, new ValueChangedEventArgs<(bool HasError, string Message)>(oldValue, newValue));
}
// Receive event
private void OnCompleted(object sender, ValueChangedEventArgs<(bool HasError, string Message)> e)
{
(bool HasError, string Message) newValue = e.NewValue;
if (newValue.HasError)
{
this.TaskCompletionSource.TrySetException(new InvalidOperationException(newValue.Message));
}
this.TaskCompletionSource.TrySetResult(true);
}
Generic EventArgs
implementation that provides to carry a value.
// Specify a named ValueTuple as event argument
event EventHandler<ValueEventArgs<int> Completed;
// Publish event
protected virtual void RaiseCompleted(int value)
{
this.Completed?.Invoke(this, new ValueEventArgs<int>(value));
}
// Receive event
private void OnCompleted(object sender, ValueEventArgs<int> e)
{
int value = e.Value;
}
A static default API to the AppSettings that provides strongly typed reading and writing (e.g. boo
, int
, double
, string
) of key-value pair values.
// Write the Most Recently Used file count to the AppSettings file
AppSettingsConnector.WriteInt("mruCount", 10);
// If key exists read the Most Recently Used file count from the AppSettings file
if (TryReadInt("mruCount", out int mruCount))
{
this.MruCount = mruCount;
}
The MruManager
maintains a collection of MostRecentlyUsedFileItem
elrecentlyements that map to a recently used file. Once the max number of recently used files is reached and a new file is added, the MruManager
automatically removes the least used file to free the MRU table.
var mruManager = new MruManager();
mruManager.MaxMostRecentlyUsedCount = 30;
var openFileDialog = new OpenFileDialog();
bool? result = openFileDialog.ShowDialog();
// Process open file dialog box results
if (result == true)
{
string filename = openFileDialog.FileName;
// Save the picked file in the MRU list
mruManager.AddMostRecentlyUsedFile(filename);
}
var mruManager = new MruManager();
mruManager.MaxMostRecentlyUsedCount = 30;
MostRecentlyUsedFileItem lastUsedFile = mruManager.MostRecentlyUsedFile;
// Since the list is a ReadOnlyObservableCollection you can directly bind to it
// and receive CollectionChanged notifications, which will automatically update the binding target
ReadOnlyObservableCollection<MostRecentlyUsedFileItem> mruList = mruManager.MostRecentlyUsedFiles;
Dynamic implementation of the EventAggregator design pattern.
Listen to broadcasted events by a specific source type, by a specific event name, by matching event handler signature or by matching EventArgs
type.
Allows to listen to an event without the need to reference the source instance.
Register event sources with an instance of EventAggregator
:
var aggregator = new EventAggregator();
// Create instances that are source of an event
var mainWindowViewModel = new MainWindowViewModel();
var mainPageViewModel = new MainPageViewModel();
var settingsPageViewModel = new SettingsPageViewModel();
// Listen to a list of events published by a specific instance
aggregator.TryRegisterObservable(mainWindowViewModel,
new[]
{
nameof(INotifyPropertyChanged.PropertyChanged),
nameof(MainWindowViewModel.ItemCreated)
});
aggregator.TryRegisterObservable(
mainPageViewModel,
new[] {nameof(INotifyPropertyChanged.PropertyChanged)});
aggregator.TryRegisterObservable(
settingsPageViewModel,
new[] {nameof(INotifyPropertyChanged.PropertyChanged)});
Subscribe to the EventAggregator
and listen to specific events of all event sources:
// Listen to everything that publishes the 'INotifyPropertyChanged.PropertyChanged' event
aggregator.TryRegisterObserver<PropertyChangedEventHandler>(
nameof(INotifyPropertyChanged.PropertyChanged),
ShowMessage_OnPropertyChanged);
Listen to events by name, raised by all event sources that match a specific type (e.g., class or interface)
Subscribe to the EventAggregator
and listen to specific events of specific event sources (by source type):
// Only listen to the 'INotifyPropertyChanged.PropertyChanged' event raised by any instance of type 'MainWindowViewModel'
aggregator.TryRegisterObserver<PropertyChangedEventHandler>(
nameof(INotifyPropertyChanged.PropertyChanged),
mainWindowViewModel.GetType(),
ShowMessage_OnPropertyChanged);
// Only listen to the 'INotifyPropertyChanged.PropertyChanged' event of all instances that implement 'IPage' interface
aggregator.TryRegisterObserver<PropertyChangedEventHandler>(
nameof(INotifyPropertyChanged.PropertyChanged),
typeof(IPage),
ShowMessage_OnPropertyChanged);
Listen to all events that match the signature of the event handler or that use a matching EventArgs
type
Subscribe to the EventAggregator
and listen to all events that have an event delegate with matching signature:
// Subscribe by defining the event delegate explicitly
aggregator.TryRegisterGlobalObserver(new PropertyChangedEventHandler(ShowMessage_OnPropertyChanged));
Subscribe to the EventAggregator
and listen to all events that use a matching EventArgs
type:
// Subscribe by defining the EventArgs as generic type parameter
aggregator.TryRegisterGlobalObserver<PropertyChangedEventArgs>(ShowMessage_OnPropertyChanged);
// Event callback
private void ShowMessage_OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
MessageBox.Show($"Handling 'PropertyChanged event'. Sender={sender.GetType().Name}; Value={e.PropertyName}");
}
// Event sources
class MainPageViewModel : IPage, INotifyPropertyChanged
{
public string Title
{
private string title;
get => this.title;
set
{
this.title = value;
OnPropertyChanged();
}
}
}
class SettingsPageViewModel : IPage, INotifyPropertyChanged
{
private string title;
public string Title
{
get => this.title;
set
{
this.title = value;
OnPropertyChanged();
}
}
}
class MainWindowViewModel : INotifyPropertyChanged
{
public void CreateItem()
{
this.Items.Add("New Item");
OnItemCreated();
}
private ObservableCollection<string> items;
public ObservableCollection<string> Items
{
get => this.items;
set
{
this.items = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public event EventHandler ItemCreated;
protected virtual void OnItemCreated() => this.ItemCreated?.Invoke(this, EventArgs.Empty);
}
Provides a clean way to show dialogs that are requested by view models. This apporach uses dialog view models that are templated by specific DataTemplate
definitions, This templates are rendered as content of native dialog Window
instances.
Because once the dialog was closed a custom asynchronous continuation delegate is invoked by the DialogViewModel
the dialog can be shown without the requirement for the requesting view model to wait for a response. This way the dialogs can be shown in a fire-and-forget manner supporting asynchronous delegates.
DialogViewModel
is the only mandatory abstract class (or alternatively the IDialogViewModel
interface) to implement. DialogViewModel
offers ready to use logic for the data and response handling. Virtual methods add a way to add customization or adjust behavior. The Dialog
attached behavior handles the view logic which includes aplying styles and templates on the dialog and showing/ closing the Window.
The interfaces IDialogViewModelProvider
and IDialogViewModelProviderSource
are not a requirement and are supposed to provide a clean separation. The IDialogViewModelProviderSource.DialogRequested
event driven flow can be omitted. Just provide a mechanism to bind a DialogViewModel
(or IDialogViewModel
) implementation to the Dialog.DialogDataContext
Attached Property. Dialog
and DialogViewMoel
are the only core classes that are required to make it work.
Implementing IDialogViewModelProvider (just provide a binding source for the Dialog
attached behavior)
class MainWindowViewModel : IDialogViewModelProvider
{
public void ShowDialog()
{
// Create the IDialogViewModel for the File Exists dialog
var dialogTitleBarIcon = new BitmapImage(new Uri("../../logo.ico", UriKind.Relative));
if (titleBarIcon.CanFreeze)
{
titleBarIcon.Freeze();
}
var message = "File exists. Do you want to replace it?";
var dialogTitle = "File Exists";
// Set the continuation callback which will be invoked once the dialog closed
var fileExistsdialogViewModel = new FileExistsDialogViewModel(
message,
dialogTitle,
dialogTitleBarIcon,
dialogViewModel => HandleFileExistsDialogResponseAsync(dialogViewModel, filePath, settingsData));
// Show the dialog by setting the DialogViewModel property to an instance of IDialogViewModel
this.DialogViewModel = fileExistsdialogViewModel;
}
// Continuation callback. Will be invoked once the dialog closed.
// The parameter is the previously created FileExistsDialogViewmodel containing data set from the dialog.
private async Task HandleFileExistsDialogResponseAsync(IDialogViewModel dialogViewModel, string filePath, string settingsData)
{
if (dialogViewModel.DialogResult == DialogResult.Accepted)
{
await SaveFileAsync(filePath, settingsData);
}
}
// IDialogViewModelProvider interface implementation
private IDialogViewModel dialogViewModel;
public IDialogViewModel DialogViewModel
{
get => this.dialogViewModel;
private set => TrySetValue(value, ref this.dialogViewModel);
}
}
This is the only mandatory abstract class (or alternatively the IDialogViewModel
interface) to implement.
The interfaces IDialogViewModelProvider
and IDialogViewModelProvider
are just to provide a clean separation. The IDialogViewModelProviderSource.DialogRequested
can be omitted. Just provide a mechanism to bind a DialogViewModel
(or IDialogViewModel
) implementation to the Dialog.DialogDataContext
Attached Property.
Dialog
and DialogViewMoel
are the core classes to make it work.
public class FileExistsDialogViewModel : DialogViewModel
{
public FileExistsDialogViewModel(string message, string title) : base(message, title)
{
}
public FileExistsDialogViewModel(string message, string title, Func<IDialogViewModel, Task> sendResponseCallbackAsync) : base(message, title, sendResponseCallbackAsync)
{
}
public FileExistsDialogViewModel(string message, string title, ImageSource titleBarIcon, Func<IDialogViewModel, Task> sendResponseCallbackAsync) : base(message, title, titleBarIcon, sendResponseCallbackAsync)
{
}
}
Make sure the templates are declared in the proper scope. It is recommended to declare them in the App.xaml.
Application x:Class="BionicCode.BionicNuGetDeploy.Main.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pages="clr-namespace:BionicCode.BionicNuGetDeploy.Main.Pages"
xmlns:dialog="clr-namespace:BionicUtilities.Net.Dialog;assembly=BionicUtilities.Net"
Startup="RunApplication">
<Application.Resources>
<Viewbox x:Key="WarningIcon"
x:Shared="False">
<ContentControl FontFamily="Segoe MDL2 Assets"
Content="" />
</Viewbox>
<Viewbox x:Key="WarningLightIcon"
x:Shared="False">
<ContentControl FontFamily="Segoe MDL2 Assets"
Content="" />
</Viewbox>
<DataTemplate DataType="{x:Type pages:FileExistsDialogViewModel}">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0"
Orientation="Horizontal"
Margin="0,0,48,24">
<Grid Margin="0,0,16,0">
<ContentControl Panel.ZIndex="1"
Content="{StaticResource WarningIcon}"
VerticalAlignment="Center"
Height="32"
Foreground="Orange"
Background="Black" />
<ContentControl Panel.ZIndex="2"
Content="{StaticResource WarningLightIcon}"
VerticalAlignment="Center"
Height="32"
Margin="0,4,0,0" />
</Grid>
<TextBlock Text="{Binding Message}" />
</StackPanel>
<StackPanel Grid.Row="1"
FocusManager.FocusedElement="{Binding ElementName=CancelButton}"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Content="Yes"
Padding="0"
Command="{Binding SendResponseAsyncCommand}"
CommandParameter="{x:Static dialog:DialogResult.Accepted}"
Margin="0,0,16,0" />
<Button x:Name="CancelButton"
Content="No"
IsCancel="True"
IsDefault="True"
BorderThickness="3"
Padding="0"
Command="{Binding SendResponseAsyncCommand}"
CommandParameter="{x:Static dialog:DialogResult.Denied}" />
</StackPanel>
</Grid>
</DataTemplate>
</Application.Resources>
</Application>
Setting the Attached Property Dialog.DialogDataContext
on Window
(or any other FrameworkElement
- required)
<Window x:Class="BionicCode.BionicNuGetDeploy.Main.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dialog="clr-namespace:BionicUtilities.Net.Dialog;assembly=BionicUtilities.Net"
mc:Ignorable="d"
Title="MainWindow"
Dialog.DialogDataContext="{Binding DialogViewModel}">
</Window>
class SettingsPageViewModel : IDialogViewModelProviderSource
{
public async Task TrySaveFileAsync(string filePath, string settingsData)
{
if (File.Exists(filePath))
{
// Create the IDialogViewModel for the File Exists dialog
var dialogTitleBarIcon = new BitmapImage(new Uri("../../logo.ico", UriKind.Relative));
if (titleBarIcon.CanFreeze)
{
titleBarIcon.Freeze();
}
var message = "File exists. Do you want to replace it?";
var dialogTitle = "File Exists";
// Set the continuation callback which will be invoked once the dialog closed
var fileExistsdialogViewModel = new FileExistsDialogViewModel(
message,
dialogTitle,
dialogTitleBarIcon,
dialogViewModel => HandleFileExistsDialogResponseAsync(dialogViewModel, filePath, settingsData));
// Request File Exists dialog to ask if existing file can be overwritten
OnDialogRequested(newfileExistsdialogViewModel);
return;
}
await SaveFileAsync(filePath, settingsData);
}
// Continuation callback. Will be invoked once the dialog closed.
// The parameter is the previously created FileExistsDialogViewmodel containing data set from the dialog.
private async Task HandleFileExistsDialogResponseAsync(IDialogViewModel dialogViewModel, string filePath, string settingsData)
{
if (dialogViewModel.DialogResult == DialogResult.Accepted)
{
await SaveFileAsync(filePath, settingsData);
}
}
// IDialogViewModelProviderSource interface implementation
public event EventHandler<ValueEventArgs<IDialogViewModel>> DialogRequested;
protected virtual void OnDialogRequested(IDialogViewModel dialogViewModel)
{
this.DialogRequested?.Invoke(this, new ValueEventArgs<IDialogViewModel>(dialogViewModel));
}
}
Implementing IDialogViewModelProvider (optional - just provide a binding source for the Dialog
attached behavior)
class MainWindowViewModel : IDialogViewModelProvider
{
public MainPageViewModel()
{
// A view model that implements IDialogViewModelProvider and can request dispalying of a dialog
var settingsPageViewModel = new SettingsPageViewModel();
// Listen for dialog requests and provide the dialog view model for binding of the attahced behavior.
// Show the dialog by setting the DialogViewModel property to an instance of IDialogViewModel.
settingsPageViewModel.DialogRequested += (sender, args) => this.DialogViewModel = args.Value);
this.Pages = new ObservableCollection<IPage>() { settingsPageViewModel };
}
// IDialogViewModelProvider interface implementation
private IDialogViewModel dialogViewModel;
public IDialogViewModel DialogViewModel
{
get => this.dialogViewModel;
private set => TrySetValue(value, ref this.dialogViewModel);
}
}
This is the only mandatory abstract class (or alternatively the IDialogViewModel
interface) to implement.
The interfaces IDialogViewModelProvider
and IDialogViewModelProvider
are just to provide a clean separation. The IDialogViewModelProviderSource.DialogRequested
can be omitted. Just provide a mechanism to bind a DialogViewModel
(or IDialogViewModel
) implementation to the Dialog.DialogDataContext
Attached Property.
Dialog
and DialogViewMoel
are the core classes to make it work.
public class FileExistsDialogViewModel : DialogViewModel
{
public FileExistsDialogViewModel(string message, string title) : base(message, title)
{
}
public FileExistsDialogViewModel(string message, string title, Func<IDialogViewModel, Task> sendResponseCallbackAsync) : base(message, title, sendResponseCallbackAsync)
{
}
public FileExistsDialogViewModel(string message, string title, ImageSource titleBarIcon, Func<IDialogViewModel, Task> sendResponseCallbackAsync) : base(message, title, titleBarIcon, sendResponseCallbackAsync)
{
}
}
Make sure the templates are declared in the proper scope. It is recommended to declare them in the App.xaml.
Application x:Class="BionicCode.BionicNuGetDeploy.Main.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pages="clr-namespace:BionicCode.BionicNuGetDeploy.Main.Pages"
xmlns:dialog="clr-namespace:BionicUtilities.Net.Dialog;assembly=BionicUtilities.Net"
Startup="RunApplication">
<Application.Resources>
<Viewbox x:Key="WarningIcon"
x:Shared="False">
<ContentControl FontFamily="Segoe MDL2 Assets"
Content="" />
</Viewbox>
<Viewbox x:Key="WarningLightIcon"
x:Shared="False">
<ContentControl FontFamily="Segoe MDL2 Assets"
Content="" />
</Viewbox>
<DataTemplate DataType="{x:Type pages:FileExistsDialogViewModel}">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0"
Orientation="Horizontal"
Margin="0,0,48,24">
<Grid Margin="0,0,16,0">
<ContentControl Panel.ZIndex="1"
Content="{StaticResource WarningIcon}"
VerticalAlignment="Center"
Height="32"
Foreground="Orange"
Background="Black" />
<ContentControl Panel.ZIndex="2"
Content="{StaticResource WarningLightIcon}"
VerticalAlignment="Center"
Height="32"
Margin="0,4,0,0" />
</Grid>
<TextBlock Text="{Binding Message}" />
</StackPanel>
<StackPanel Grid.Row="1"
FocusManager.FocusedElement="{Binding ElementName=CancelButton}"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Content="Yes"
Padding="0"
Command="{Binding SendResponseAsyncCommand}"
CommandParameter="{x:Static dialog:DialogResult.Accepted}"
Margin="0,0,16,0" />
<Button x:Name="CancelButton"
Content="No"
IsCancel="True"
IsDefault="True"
BorderThickness="3"
Padding="0"
Command="{Binding SendResponseAsyncCommand}"
CommandParameter="{x:Static dialog:DialogResult.Denied}" />
</StackPanel>
</Grid>
</DataTemplate>
</Application.Resources>
</Application>
Setting the Attached Property Dialog.DialogDataContext
on Window
(or any other FrameworkElement
- required)
<Window x:Class="BionicCode.BionicNuGetDeploy.Main.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dialog="clr-namespace:BionicUtilities.Net.Dialog;assembly=BionicUtilities.Net"
mc:Ignorable="d"
Title="MainWindow"
Dialog.DialogDataContext="{Binding DialogViewModel}"
Dialog.DialogDataContext="{Binding DialogViewModel}"
Dialog.IsModal="True"
Dialog.IsClosable="False">
</Window>
MarkupExtension
that inverts a value provided locally or by any MarkupExtension
e.g., Binding
.
Setting the Invert.ValueInverter
property (similar to Binding.Converter
) allows to specify a custom inversion logic and conversion type support.
<!-- Use Binding value -->
<TextBlock Text="{Invert {Binding RelativeSource={RelativeSource AncestorType=Window}, Path=Text}}"/>
<!-- Returns: -12 -->
<TextBlock Text="{Invert Value=1.2}"/>
<!-- Provide a custom implementation of IValueInverter. Returns: 0.12 -->
<TextBlock Text="{Invert -0.12, ValueInverter={StaticResource CustomValueInverter}"/>
<!-- Returns: Visibility.Collapsed -->
<TextBlock Text="{Invert {x:Static Visibility.Visible}}"/>
<!-- Returns: False -->
<TextBlock Text="{Invert True}"/>
MarkupExtension
to display return a collection of enumeration values of a specific enum
type.
<ComboBox ItemsSource="{Enum {x:Static MyEnum}}"/>
<ComboBox ItemsSource="{Enum EnumType={x:Static MyEnum}}"/>
A Stream
decorator that resets the stream's position after read/write access. Resets to the optionally provided SeekOrigin
assigned to AutoResetStream.ResetOrigin
using (var fileStream = new FileStream("C:\Temp", FileMode.CreateNew))
{
bool leaveFileStreamOpen = true;
using (var autoResetStream = new AutoResetStream(fileStream, leaveFileStreamOpen))
{
byte[] buffer = new byte[1024];
int bytesRead = await autoResetStream.ReadAsync(buffer, 0, buffer.Length); // bytesRead: 1024
int currentPosition = autoResetStream.Position; // currentPosition: 0
}
currentPosition = fileStream.Position; // currentPosition: 0
}