Cancel an async task

Published 12/20/2018 by Christian in Code
Tags: , , ,

So, I wanted to do asynchronously database search that could take a long time. But if the user refine her search criteria, the async should restart, and whatever already found records be disregarded.

Using the following Microsoft article as a template, this is what I came up with as a proof of concept.

 https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/cancel-an-async-task-or-a-list-of-tasks

 

This WPF program has 3 buttons. 

Start - will cancel all already started jobs, waits for 5 sec. and then download some content from a msdn site.

Cancel All - will simply cancel all started jobs.

Clear - will cancel all started jobs, and clear any messages.

 

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;

// Add a using directive and a reference for System.Net.Http.
using System.Net.Http;

// Add the following using directive for System.Threading.
using System.Threading;

namespace CancelATask
{
    public partial class MainWindow : Window
    {
        // ***Declare a System.Threading.CancellationTokenSource.
        List<CancellationTokenSource> cts = new List<CancellationTokenSource>();

        public MainWindow()
        {
            InitializeComponent();
        }


        private async void startButton_Click(object sender, RoutedEventArgs e)
        {
            cts?.ForEach(c => c.Cancel());
            // ***Instantiate the CancellationTokenSource.
            using (CancellationTokenSource c = new CancellationTokenSource())
            {
                cts.Add(c);

                try
                {
                    // ***Send a token to carry the message if cancellation is requested.
                    int contentLength = await AccessTheWebAsync(c.Token);
                    resultsTextBox.Text += $"\r\nLength of the downloaded string: {contentLength}.\r\n";
                }
                // *** If cancellation is requested, an OperationCanceledException results.
                catch (OperationCanceledException)
                {
                    resultsTextBox.Text += $"\r\nDownload canceled. Current no of jobs: {cts.Count}\r\n";
                }
                catch (Exception)
                {
                    resultsTextBox.Text += "\r\nDownload failed.\r\n";
                }
                cts.Remove(c);
            }
        }


        // ***Add an event handler for the Cancel button.
        private void cancelButton_Click(object sender, RoutedEventArgs e)
        {
            cts?.ForEach(c => c.Cancel());
        }

        private void clearButton_Click(object sender, RoutedEventArgs e)
        {
            cts?.ForEach(c => c.Cancel());
            resultsTextBox.Clear();
        }

        // ***Provide a parameter for the CancellationToken.
        async Task<int> AccessTheWebAsync(CancellationToken c)
        {
            HttpClient client = new HttpClient();

            resultsTextBox.Text += "Waiting for download to commence.\r\n";

            // You might need to slow things down to have a chance to cancel.
            await Task.Delay(5000, c);

            resultsTextBox.Text += "Commencing to download.\r\n";
            // GetAsync returns a Task<HttpResponseMessage>. 
            // ***The c argument carries the message if the Cancel button is chosen.
            HttpResponseMessage response = await client.GetAsync("http://msdn.microsoft.com/en-us/library/dd470362.aspx", c);

            // Retrieve the website contents from the HttpResponseMessage.
            byte[] urlContents = await response.Content.ReadAsByteArrayAsync();

            // The result of the method is the length of the downloaded web site.
            return urlContents.Length;
        }
    }

    // Output for a successful download:

    // Ready to download.

    // Length of the downloaded string: 158125.


    // Or, if you cancel:

    // Ready to download.

    // Download canceled.
}

 

As always feel free to ask or comment.


OK - So I’ve got a WPF DataGrid, with a DataGridTemplateColumn. The DataTemplate is a plane TextBox. When I tab through the cells, I want to select the text in the TextBox. And when I click the text in the TextBox, I also want to select it. That proved more difficult than I expected.

But hacking and slashing from this blog

http://andora.us/blog/2011/06/09/wpf-textbox-select-all-on-focus/

and this answer

http://stackoverflow.com/questions/1104164/wpf-datagridtemplatecolumn-am-i-missing-something

I came up with a solution. Although not strictly MVVM, I still think it falls under that design pattern. All View related stuff is still handled in the View.

Anyway - First of all. when you tab through the cells the first tab stop is the border of the cell, and the second is the actual TextBox.

Secondly, In WPF, the default behavior of the TextBox on focus is to put the cursor where it was the last time the TextBox had lost focus or if it hasn’t had focus yet, at the beginning.

The first problem I solved by by using the answer from stackoverflow:

<DataGridTemplateColumn.CellStyle>
	<Style TargetType="{x:Type DataGridCell}">
		<Setter Property="KeyboardNavigation.IsTabStop" Value="False"/>
	</Style>
</DataGridTemplateColumn.CellStyle>

The second by following Andora block. So the hole XAML DataGrid looks like this:

<DataGrid Name="TextBlockDataGrid" ItemsSource="{Binding Path=Rows}" Style="{StaticResource DefaultSettingsDataGrid}">
	<DataGrid.Columns>
		<DataGridTextColumn Binding="{Binding Text}" IsReadOnly="True"/>
		<DataGridTemplateColumn Width="*">
			<DataGridTemplateColumn.CellStyle>
				<Style TargetType="{x:Type DataGridCell}">
					<Setter Property="KeyboardNavigation.IsTabStop" Value="False"/>
				</Style>
			</DataGridTemplateColumn.CellStyle>
			<DataGridTemplateColumn.CellTemplate>
				<DataTemplate>
					<Border BorderThickness="{Binding ErrorBorderThickness}" BorderBrush="{Binding ErrorBorderBrush}">
						<TextBox Text="{Binding UserText, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" 
								 HorizontalAlignment="Right" 
								 GotKeyboardFocus="TextBox_GotKeyboardFocus" 
								 PreviewMouseDown="TextBox_PreviewMouseDown"
								 Style="{StaticResource DefaultTextBox}"/>
					</Border>
				</DataTemplate>
			</DataGridTemplateColumn.CellTemplate>
		</DataGridTemplateColumn>
	</DataGrid.Columns>
</DataGrid>

And the code-behind like this:

private void TextBox_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
	try
	{
		((TextBox)sender).SelectAll();
	}
	catch { }
}

private void TextBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
	try
	{
		// If its a triple click, select all text for the user.
		if (e.ClickCount == 3)
		{
			((TextBox)sender).SelectAll();
			return;
		}

		// Find the TextBox
		DependencyObject parent = e.OriginalSource as UIElement;
		while (parent != null && !(parent is TextBox))
		{
			parent = System.Windows.Media.VisualTreeHelper.GetParent(parent);
		}

		if (parent != null)
		{
			if (parent is TextBox)
			{
				var textBox = (TextBox)parent;
				if (!textBox.IsKeyboardFocusWithin)
				{
					// If the text box is not yet focussed, give it the focus and
					// stop further processing of this click event.
					textBox.Focus();
					e.Handled = true;
				}
			}
		}
	}
	catch { }
}

 

As always, feel free to comment, or ask.


Text - or anything else for that matter - can be scaled  to fill all available space by using a Viewbox. Here is an example.

<Viewbox HorizontalAlignment="Center" VerticalAlignment="Center">
	<TextBlock Text="{Binding Path=HeadlineText}"/>
</Viewbox>

As always, feel free to comment, or ask.


There are of course a lot of ways to collapse a XAML TextBlock, but here is one using a style.

<TextBlock Text="{Binding Path=DescriptiveText}">
	<TextBlock.Style>
		<Style TargetType="TextBlock">
			<Style.Triggers>
				<Trigger Property="Text" Value="{x:Null}">
					<Setter Property="Visibility" Value="Collapsed" />
				</Trigger>
				<Trigger Property="Text" Value="">
					<Setter Property="Visibility" Value="Collapsed" />
				</Trigger>
			</Style.Triggers>
		</Style>
	</TextBlock.Style>
</TextBlock>

As always, feel free to comment, or ask.


ICommand implementation

Published 10/31/2014 by Christian
Tags: , ,

This is a small class that implements ICommand

public class RelayCommand : ICommand
{
	#region Fields
	private readonly Action<object> execute;
	private readonly Predicate<object> canExecute;
	#endregion Fields

	#region Constructors
	public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
	{
		if (execute == null)
			throw new ArgumentNullException("execute");

		this.execute = execute;
		this.canExecute = canExecute;
	}
	#endregion Constructors

	#region ICommand Members
	[DebuggerStepThrough]
	public bool CanExecute(object parameter)
	{
		return canExecute == null ? true : canExecute(parameter);
	}

	public event EventHandler CanExecuteChanged
	{
		add { CommandManager.RequerySuggested += value; }
		remove { CommandManager.RequerySuggested -= value; }
	}

	public void Execute(object parameter)
	{
		execute(parameter);
	}
	#endregion ICommand Members
}

As always, feel free to comment, or ask.


This post is about how control events, like MouseDoubleClick, MouseEnter ect., can be handled in a MVVM design

First a general Behaviour class

public static partial class Behaviours
{
	public static class EventBehaviourFactory
	{
		public static DependencyProperty CreateCommandExecutionEventBehaviour(RoutedEvent routedEvent, string propertyName, Type ownerType)
		{
			DependencyProperty property = DependencyProperty.RegisterAttached(propertyName, typeof(ICommand), ownerType,
				new PropertyMetadata(null,
					new ExecuteCommandOnRoutedEventBehaviour(routedEvent).PropertyChangedHandler));

			return property;
		}

		/// <summary>
		/// An internal class to handle listening for an event and executing a command,
		/// when a Command is assigned to a particular DependencyProperty
		/// </summary>
		private class ExecuteCommandOnRoutedEventBehaviour : ExecuteCommandBehaviour
		{
			private readonly RoutedEvent _routedEvent;

			public ExecuteCommandOnRoutedEventBehaviour(RoutedEvent routedEvent)
			{
				_routedEvent = routedEvent;
			}

			/// <summary>
			/// Handles attaching or Detaching Event handlers when a Command is assigned or unassigned
			/// </summary>
			/// <param name="sender"></param>
			/// <param name="oldValue"></param>
			/// <param name="newValue"></param>
			protected override void AdjustEventHandlers(DependencyObject sender, object oldValue, object newValue)
			{
				UIElement element = sender as UIElement;
				if (element == null) { return; }

				if (oldValue != null)
				{
					element.RemoveHandler(_routedEvent, new RoutedEventHandler(EventHandler));
				}

				if (newValue != null)
				{
					element.AddHandler(_routedEvent, new RoutedEventHandler(EventHandler));
				}
			}

			protected void EventHandler(object sender, RoutedEventArgs e)
			{
				HandleEvent(sender, e);
			}
		}

		internal abstract class ExecuteCommandBehaviour
		{
			protected DependencyProperty _property;
			protected abstract void AdjustEventHandlers(DependencyObject sender, object oldValue, object newValue);

			protected void HandleEvent(object sender, EventArgs e)
			{
				DependencyObject dp = sender as DependencyObject;
				if (dp == null)
				{
					return;
				}

				ICommand command = dp.GetValue(_property) as ICommand;

				if (command == null)
				{
					return;
				}

				if (command.CanExecute(e))
				{
					command.Execute(e);
				}
			}

			/// <summary>
			/// Listens for a change in the DependencyProperty that we are assigned to, and
			/// adjusts the EventHandlers accordingly
			/// </summary>
			/// <param name="sender"></param>
			/// <param name="e"></param>
			public void PropertyChangedHandler(DependencyObject sender, DependencyPropertyChangedEventArgs e)
			{
				// the first time the property changes,
				// make a note of which property we are supposed
				// to be watching
				if (_property == null)
				{
					_property = e.Property;
				}

				object oldValue = e.OldValue;
				object newValue = e.NewValue;

				AdjustEventHandlers(sender, oldValue, newValue);
			}
		}
	}
}

The ControlEventBehaviour class

public class ControlEventBehaviour
{
	#region Button
	public static readonly DependencyProperty MouseEnterCommand = Behaviours.EventBehaviourFactory.CreateCommandExecutionEventBehaviour
			 (Button.MouseEnterEvent, "MouseEnterCommand", typeof(ControlEventBehaviour));

	public static void SetMouseEnterCommand(DependencyObject o, ICommand value)
	{
		o.SetValue(MouseEnterCommand, value);
	}

	public static ICommand GetMouseEnterCommand(DependencyObject o)
	{
		return o.GetValue(MouseEnterCommand) as ICommand;
	}

	public static readonly DependencyProperty MouseLeaveCommand = Behaviours.EventBehaviourFactory.CreateCommandExecutionEventBehaviour
		 (Button.MouseLeaveEvent, "MouseLeaveCommand", typeof(ControlEventBehaviour));

	public static void SetMouseLeaveCommand(DependencyObject o, ICommand value)
	{
		o.SetValue(MouseLeaveCommand, value);
	}

	public static ICommand GetMouseLeaveCommand(DependencyObject o)
	{
		return o.GetValue(MouseLeaveCommand) as ICommand;
	}

	public static readonly DependencyProperty MouseDoubleClickCommand = Behaviours.EventBehaviourFactory.CreateCommandExecutionEventBehaviour
		 (Button.MouseDoubleClickEvent, "MouseDoubleClickCommand", typeof(ControlEventBehaviour));

	public static void SetMouseDoubleClickCommand(DependencyObject o, ICommand value)
	{
		o.SetValue(MouseDoubleClickCommand, value);
	}

	public static ICommand GetMouseDoubleClickCommand(DependencyObject o)
	{
		return o.GetValue(MouseDoubleClickCommand) as ICommand;
	}
	#endregion Button
}

The shown example only handle button events, but with slight modification can be used for all WPF controls.

In the View:

<Button Command="{Binding Path=ButtonClickCommand}"
		utilities:ControlEventBehaviour.MouseEnterCommand="{Binding Path=MouseEnter}"
		utilities:ControlEventBehaviour.MouseLeaveCommand="{Binding Path=MouseLeave}"
		utilities:ControlEventBehaviour.MouseDoubleClickCommand="{Binding Path=ButtonDoubleClick}">

And finally in the ViewModel. This is only for MouseEnter, but I'm sure that you get the gist of it :)

private RelayCommand _mouseEnter = null;
public virtual RelayCommand MouseEnter
{
	get
	{
		if (_mouseEnter == null)
		{
			_mouseEnter = new RelayCommand(param =>
			{
				try
				{
				}
				catch { }
			}, param => { return true; });
		}
		return _mouseEnter;
	}
}

As always, feel free to comment, or ask.


XAML style inheritance

Published 10/31/2014 by Christian
Tags: , ,

This makes the Bar style inherits from Foo

<ResourceDictionary x:Class="Baltz.Styles.Bar"
                    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                    xmlns:d="http://schemas.microsoft.com/expression/blend/2008">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Foo.xaml"/>
    </ResourceDictionary.MergedDictionaries>
    <Style x:Key="aKey" BasedOn="{StaticResource StyleInFoo}" TargetType="{x:Type aType}">
        <Setter Property="aTypeProperty" Value="aTypePropertyvalue"/>
    </Style>
</ResourceDictionary>

As always, feel free to comment, or ask.


 It took me some time to find a solution to this problem, but here is a working solution.

 

 I wanted to be able to drag 'n drop WPF DataGrid rows in MVVM, and although this solution is not strickly MVVM, I still think that it fits nicely in that design patten. All GUI it handled within the view, and all logic in the ViewModel.

 I found a good starting point for this solution somewhere, but again unfortunately I have forgotten where. I think it was on codeproject. My sincere apologies to the author.

 The follwing goes into the UserControl code behind:

using Baltz.ViewModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Baltz.View
{
    /// <summary>
    /// Interaction logic for DataGridDraggableRowView.xaml
    /// </summary>
    public partial class DataGridDraggableRowView : UserControl
    {
        public DataGridDraggableRowView()
        {
            InitializeComponent();
        }

        #region Static Methods
        private static T FindVisualParent<T>(UIElement element) where T : UIElement
        {
            var parent = element;
            while (parent != null)
            {
                var correctlyTyped = parent as T;
                if (correctlyTyped != null)
                {
                    return correctlyTyped;
                }

                parent = VisualTreeHelper.GetParent(parent) as UIElement;
            }
            return null;
        }
        #endregion

        #region Fields
        private int _originalIndex;
        private DataGridRow _oldRow;
        private DataGridDraggableRowItem _targetItem;
        private DataGridDraggableRowViewModel _viewModel;
        #endregion Fields

        #region Event Handlers
        /// <summary>
        /// Updates the grid as a drag progresses
        /// </summary>
        private void OnMainGridCheckDropTarget(object sender, DragEventArgs e)
        {
            var row = FindVisualParent<DataGridRow>(e.OriginalSource as UIElement);

            // Set the DragDropEffects 
            if ((row == null) || !(row.Item is DataGridDraggableRowItem))
            {
                e.Effects = DragDropEffects.None;
            }
            else
            {
                var currentIndex = row.GetIndex();

                // Erase old drop-line
                if (_oldRow != null)
                    _oldRow.BorderThickness = new Thickness(0);

                // Draw new drop-line
                var direction = (currentIndex - _originalIndex);
                if (direction < 0)
                    row.BorderThickness = new Thickness(0, 2, 0, 0);
                else if (direction > 0)
                    row.BorderThickness = new Thickness(0, 0, 0, 2);
                row.BorderBrush = new SolidColorBrush(Color.FromArgb(0xFF, 0x00, 0x9C, 0xDD));
                // Reset old row
                _oldRow = row;
            }
        }

        /// <summary>
        /// Gets the view model from the data Context and assigns it to a member variable.
        /// </summary>
        private void OnMainGridDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            _viewModel = (DataGridDraggableRowViewModel)this.DataContext;
        }

        /// <summary>
        /// Process a row drop on the DataGrid.
        /// </summary>
        private void OnMainGridDrop(object sender, DragEventArgs e)
        {
            e.Effects = DragDropEffects.None;
            e.Handled = true;

            // Verify that this is a valid drop and then store the drop target
            var row = FindVisualParent<DataGridRow>(e.OriginalSource as UIElement);
            if (row != null)
            {
                _targetItem = row.Item as DataGridDraggableRowItem;
                if (_targetItem != null)
                {
                    e.Effects = DragDropEffects.Move;
                }
            }

            // Erase last drop-line
            if (_oldRow != null)
                _oldRow.BorderThickness = new Thickness(0, 0, 0, 0);
        }

        /// <summary>
        /// Processes a drag in the main grid.
        /// </summary>
        private void OnMainGridMouseMove(object sender, MouseEventArgs e)
        {
            // Exit if left mouse button aren't pressed
            if (e.LeftButton != MouseButtonState.Pressed)
                return;

            // Find the row the mouse button was pressed on
            var row = FindVisualParent<DataGridRow>(e.OriginalSource as FrameworkElement);
            _originalIndex = row.GetIndex();

            // If the row was already selected, begin drag
            if ((row != null) && row.IsSelected)
            {
                // Get the grocery item represented by the selected row
                var selectedItem = (DataGridDraggableRowItem)row.Item;
                var finalDropEffect = DragDrop.DoDragDrop(row, selectedItem, DragDropEffects.Move);
                if ((finalDropEffect == DragDropEffects.Move) && (_targetItem != null))
                {
                    /* A drop was accepted. Determine the index of the item being 
                     * dragged and the drop location. If they are different, then 
                     * move the selectedItem to the new location. */

                    // Move the dragged item to its drop position
                    var oldIndex = _viewModel.Rows.IndexOf(selectedItem);
                    var newIndex = _viewModel.Rows.IndexOf(_targetItem);
                    if (oldIndex != newIndex)
                        _viewModel.Rows.Move(oldIndex, newIndex);
                    _targetItem = null;
                }
            }
        }
        #endregion
    }
}

A side note: This style is based on a style posted in another post

<ResourceDictionary x:Class="Ellab.Main.Settings.SettingsWindow.View.Styles.DraggableDataGridStyle"
                    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                    mc:Ignorable="d">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="DataGridStyle.xaml"/>
    </ResourceDictionary.MergedDictionaries>
    <Style x:Key="DefaultDraggableDataGrid" BasedOn="{StaticResource DefaultSettingsDataGrid}" TargetType="{x:Type DataGrid}">
        <Setter Property="HeadersVisibility" Value="Column"/>
        <Setter Property="AllowDrop" Value="True"/>
    </Style>
    <Style TargetType="{x:Type DataGridCell}">
        <Style.Resources>
            <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="#FF009CDD" />
            <SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="#FF009CDD" />
            <SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black" />
            <SolidColorBrush x:Key="{x:Static SystemColors.ControlTextBrushKey}" Color="Black" />
        </Style.Resources>
    </Style>
</ResourceDictionary>

And this is the complete View - most of it isn't relevant for the dragable rows.

<UserControl x:Class="foo.View.barDataGridDraggableRowView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Styles/DraggableDataGridStyle.xaml"/>
                <ResourceDictionary Source="Styles/ContentPresenterStyle.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
    <StackPanel Orientation="Vertical" Background="Transparent">
        <ContentPresenter Content="{Binding Path=HeaderButton}" Style="{StaticResource DefaultContentPresenter}"/>
        <StackPanel Orientation="Vertical" Background="Transparent" Visibility="{Binding Path=IsExpanded}">
            <DataGrid ItemsSource="{Binding Path=Rows}"
                  MouseMove="OnMainGridMouseMove"
                  DragEnter="OnMainGridCheckDropTarget"
                  DragLeave="OnMainGridCheckDropTarget"
                  DragOver="OnMainGridCheckDropTarget"
                  Drop="OnMainGridDrop" 
                  DataContextChanged="OnMainGridDataContextChanged"
                  Style="{StaticResource DefaultDraggableDataGrid}">
                <DataGrid.Columns>
                    <DataGridTemplateColumn IsReadOnly="True" Width="20">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <Image Source="../../Resources/DragDrop.png" Stretch="Fill" Opacity="{Binding Opacity}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTemplateColumn IsReadOnly="True">
                        <DataGridTemplateColumn.Header>
                            <TextBlock Text="{Binding DataContext.EnabledColumnHeader, RelativeSource={RelativeSource AncestorType=DataGrid}}" MaxWidth="65" TextWrapping="WrapWithOverflow" TextTrimming="CharacterEllipsis">
                                <TextBlock.LayoutTransform>
                                    <TransformGroup>
                                        <RotateTransform Angle="90" />
                                        <ScaleTransform ScaleX="-1" ScaleY="-1"/>
                                    </TransformGroup>
                                </TextBlock.LayoutTransform>
                            </TextBlock>
                        </DataGridTemplateColumn.Header>
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <CheckBox HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding EnabledChecked, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Opacity="{Binding Opacity}" IsEnabled="{Binding Enable}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTemplateColumn IsReadOnly="True">
                        <DataGridTemplateColumn.Header>
                            <TextBlock Text="{Binding DataContext.WizardColumnHeader, RelativeSource={RelativeSource AncestorType=DataGrid}}" MaxWidth="65" TextWrapping="WrapWithOverflow" TextTrimming="CharacterEllipsis">
                                <TextBlock.LayoutTransform>
                                    <TransformGroup>
                                        <RotateTransform Angle="90" />
                                        <ScaleTransform ScaleX="-1" ScaleY="-1"/>
                                    </TransformGroup>
                                </TextBlock.LayoutTransform>
                            </TextBlock>
                        </DataGridTemplateColumn.Header>
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <CheckBox HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding WizardChecked, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Opacity="{Binding Opacity}" IsEnabled="{Binding Enable}" Visibility="{Binding WizardVisible}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTemplateColumn IsReadOnly="True" Width="*">
                        <DataGridTemplateColumn.Header>
                            <TextBlock Text="{Binding DataContext.TextColumnHeader, RelativeSource={RelativeSource AncestorType=DataGrid}}" TextWrapping="WrapWithOverflow" TextTrimming="CharacterEllipsis" VerticalAlignment="Bottom"/>
                        </DataGridTemplateColumn.Header>
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Text}" HorizontalAlignment="Left" VerticalAlignment="Center" Opacity="{Binding Opacity}" IsEnabled="{Binding Enable}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTemplateColumn IsReadOnly="True" Width="40">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <Image Source="{Binding TileImage}" Opacity="{Binding Opacity}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                </DataGrid.Columns>
            </DataGrid>
            <ContentPresenter Content="{Binding Path=TilesRemainingText}"/>
        </StackPanel>
    </StackPanel>
</UserControl>

As always, feel free to comment, or ask.


An example of a ‘ViewModelBase’ class, that implements INotifyPropertyChanged.

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
    {
        if (EqualityComparer<T>.Default.Equals(storage, value))
            return false;
        storage = value;
        this.OnPropertyChanged(propertyName);
        return true;
    }
}

And use it like this:

private string _totalTime;
public string TotalTime
{
    get { return _totalTime; }
    set
    {
        if (_totalTime != value)
        {
            _totalTime = value;
            OnPropertyChanged();
        }
    }
}

... or even:

private string _totalTime;
public string TotalTime
{
    get { return _totalTime; }
    set { SetProperty(ref _totalTime, value); }
}

As always, feel free to comment, or ask.

 


An MVVMresources.xaml example.

Published 10/30/2014 by Christian
Tags: , , ,

OK - a quick WPF/XAML/MVVM post. Mostly for my own benefit, so I can remember the syntax. This one about a MVVM resource file.

<ResourceDictionary x:Class="FooBar.MVVMResources"
                    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                    xmlns:vm="clr-namespace:foo.ViewModel"
                    xmlns:vw="clr-namespace:foo.View"
                    xmlns:vm_common="clr-namespace:bar.ViewModel;assembly=BarAssembly"
                    xmlns:vw_common="clr-namespace:bar.View;assembly=BarAssembly">

    <DataTemplate DataType="{x:Type vm:fooViewModel}">
        <vw:fooView/>
    </DataTemplate>

    <DataTemplate DataType="{x:Type vm_common:barViewModel}">
        <vw_common:barView/>
    </DataTemplate>
</ResourceDictionary>

 

As always, feel free to comment, or ask.