Skip to content

Collection of .NET libraries like utilities and controls that target .NET Standard, .NET 5, .NET Core, .NET Framework, .NET Core WPF, .NET Framework WPF andUWP

License

Notifications You must be signed in to change notification settings

BionicCode/BionicCode.Net

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BionicCode.Net GitHub Release

.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

BC BC BC BC

BC BC BC BC BC

NuGet Packages Download

BionicCode.Utilities.Net.WpfBionicCode.Utilities.Net.Wpf.NuGet

BionicCode.Utilities.Net.Core.WpfBionicCode.Utilities.Net.Core.Wpf.NuGet

BionicCode.Utilities.Net.CoreBionicCode.Utilities.Net.Core.Wpf.NuGet

BionicCode.Utilities.Net.StandardBionicCode.Utilities.Net.Standard.NuGet

BionicCode.Utilities.Net.Framework.WpfBionicCode.Utilities.Net.Framework.Wpf.NuGet

BionicCode.Utilities.Net.FrameworkBionicCode.Utilities.Net.Framework.NuGet

BionicCode.Utilities.Net.UwpBionicCode.Utilities.Net.Uwp.NuGet

BionicCode.Utilities.Net.Core.Wpf

BionicCode.Utilities.Net.Framework.Wpf

BionicCode.Utilities.Net.Standard

BionicCode.Utilities.Net.UWP

Table of Contents (selection)

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

ViewModel

  • Abstract base view model class that implements and encapsulates INotifyPropertyChanged and INotifyDataErrorInfo.
  • Allows to control whether invalid data is set on a property or neglected until validation passes by setting the default parameter isRejectInvalidValueEnabled of TrySetValue() to true (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 of TrySetValue() to true (is silent by default).
  • Additionally exposes a PropertyValueChanged event which is raised in tandem with INotifyPropertyChanged.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);
    }
  }
}

Example without validation

private string name;
public string Name
{
  get => this.name;
  set
  {
    if (TrySetValue(value, ref this.name))
    {
      DoSomething(this.name);
    }
  }
}

AsyncRelayComand<TCommandParam>

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.

Example

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");
    

Extension Methods WPF

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"];

ValueConverters

BoolToStringConverter

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>
  

InvertValueConverter

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>
  

Profiler

Static helper methods to measure performance e.g. the execution time of a code portion.

Example

// 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());

PasswordBox

Invokes a ICommand when PasswordBox.PasswordChangedevent was raised. The password is send as SecureString to secure the value.

Example

<PasswordBox PasswordBox.Command="{Binding VerifyPasswordCommand}" />
  

TextControl

Attached behavior that supports dynamic text highlighting for controls derived from TextBlock or RichTextBox.

Example

<!-- 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}" />

Popup

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.

Example

<Popup Popup.IsSticky="True" />
  

ValueChangedEventArgs<T>

Generic EventArgs implementation that provides value change information like OldValue and NewValue.

Example

// 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);
}

ValueEventArgs<T>

Generic EventArgs implementation that provides to carry a value.

Example

// 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;  
}

AppSettingsConnector

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.

Example

// 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;
}

MRU (Most Recently Used) file manager

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.

Example: Save file to MRU list

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);
}

Example: Read file from MRU list

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;

EventAggregator

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.

Example

Aggregate events

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)});
Listen to events by name, raised by any aggregated event source

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);
Type declarations used in above examples
// 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);
}

MVVM Dialog attached behavior

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.

Minimal Example

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);
  }
}
Implementing DialogViewModel (required)

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)
  { 
  }
}
Implementing DataTemplate for FileExistsDialogViewModel (required)

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="&#xE814;" />
      </Viewbox>

      <Viewbox x:Key="WarningLightIcon"
               x:Shared="False">
        <ContentControl FontFamily="Segoe MDL2 Assets"
                        Content="&#xE7BA;" />
      </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>      

Extended Example

Implementing IDialogViewModelProviderSource (optional)
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);
  }
}
Implementing DialogViewModel (required)

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)
  { 
  }
}
Implementing DataTemplate for FileExistsDialogViewModel (required)

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="&#xE814;" />
      </Viewbox>

      <Viewbox x:Key="WarningLightIcon"
               x:Shared="False">
        <ContentControl FontFamily="Segoe MDL2 Assets"
                        Content="&#xE7BA;" />
      </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>      

InvertExtension

MarkupExtensionthat 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.

Example

<!-- 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}"/>

EnumExtension

MarkupExtension to display return a collection of enumeration values of a specific enum type.

Example

<ComboBox ItemsSource="{Enum {x:Static MyEnum}}"/>

<ComboBox ItemsSource="{Enum EnumType={x:Static MyEnum}}"/>

AutoResetStream

A Stream decorator that resets the stream's position after read/write access. Resets to the optionally provided SeekOrigin assigned to AutoResetStream.ResetOrigin

Example

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
}

About

Collection of .NET libraries like utilities and controls that target .NET Standard, .NET 5, .NET Core, .NET Framework, .NET Core WPF, .NET Framework WPF andUWP

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages