Posts tagged ‘WPF’

WPF – Panel Slide-In Animation From Left Or Right Side

Button Click Slides in Panel

Goal

Create a panel which slides in from the right when a button is clicked. The second goal is to have it all done in Xaml with no codebehind code. This article describes how to do that and explains the process behind what is needed in a step by step process. Page #2 below has a link to the full code if one does not need the explanation.

Process

The picture here shows the final code in action where a ToggleButton switches it’s state (IsChecked) between true and false. When that state changes to true it will have the panel slide in from the right and false will have it slide back.

The process described below can be done by placing the code snippets in Xaml and switching to the Design view of Visual Studio.

Un-Moved Rectangle in Design Mode with and without TranslateTransform

The Panel is a Blue Rectangle

For the example we will use a basic Rectangle and place it on the screen in a plain Grid. The panel will be blue an it will reside on the right side of the screen. Here is the initial code, note in future code examples I will omit the Marigin and the alignments for the sake of brevity; but those omitions are needed and will be required in the final code.

<Grid>
    <Rectangle Fill="Blue" Width="100" Height="100" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,10,0,0"  />
</Grid>

Not Understanding TranslateTransform Is The First Hurdle

When researching and applying the concepts found, I did not understand the importance of TranslateTransform coordinates in this process. When I learned about this concept, other things fell into place.

What TranslateTransform process is, is that each object on the screen has a location, and that location is it’s anchor, so to speak. When one changes the TranslateTransform of an object, it moves it off its anchor and can be shifted right, left, up, or down by changes X and Y values from that initial position.

To achieve movement as mentioned one must provide the initial anchor point to the Rectangle. Below is a non-moved anchoring of the panel at "0,0" so it will appear the on the screen with 0 movement(s).

<Rectangle ...>
   <Rectangle.RenderTransform>
       <TranslateTransform X="0" Y="0" />
   </Rectangle.RenderTransform>
 </Rectangle>

Start it off the screen

Image moved 100 X pixels off its Anchor as shown in Design Mode

What we need now is to, adjust what we have, but start it out of sight to the right *side* of the screen. To achieve this we will change the above translate transform coordinates to instead move the X value and have the start in the off page position. To do that change the existing to this:

<TranslateTransform X="100" Y="0" />

Play with it in design mode by changing the X and Y values and not how it moves around from its initial anchor point.

Set Associative Values

Currently our panel has a Width of 100 and we want to move it off the screen, *and move it back* by that amount; an offset slide amount. What we will do is in the Grid Resource section, *though it could be placed in the page’s Resource section*, is a constant which we will apply to the panel’s Width and the X as an initial offset value.

    <Grid>
        <Grid.Resources>
            <system:Double x:Key="SlideOffSet">100</system:Double>
        </Grid.Resources>

        <Rectangle Width="{StaticResource SlideOffSet}" Height="100" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,10,0,0" >
            <Rectangle.RenderTransform>
                <TranslateTransform X="{StaticResource SlideOffSet}" Y="0" />
            </Rectangle.RenderTransform>
        </Rectangle>
    </Grid>

Check the design mode…it should still be off the screen and in the same dimensions as specified in the previous section.

Add a ToggleButton To Change States

We’ve added a toggle button which centers itself and reports its check status

We add a toggle button above the Rectangle panel because we want the panel to be drawn last; so to have it have the highest Zindex. So when it slides in, it will be above all other controls.

<Grid>
    <ToggleButton Height="30" Width="60" Margin="0,20,0,20" x:Name="SlideState">
        <TextBlock Text="{Binding IsChecked, ElementName=SlideState}"
                       FontSize="18" 
                       VerticalAlignment="Center" HorizontalAlignment="Center"  >
        </TextBlock>
    </ToggleButton>

    <Rectangle ...

The Animation Magic To Move It In and Out

To do this we will use Storyboards which when triggered will change the panel’s TranslateTransform X value to and from our slide offset. Go ahead and add the StoryBoards below under the SlideOffset setting in the Resources section.

<Storyboard x:Key="SlideRight">
    <DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"
                     From="0" To="{StaticResource SlideOffSet}"
                     Duration="0:0:0.3" />
</Storyboard>

<Storyboard x:Key="SlideLeft">
    <DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"
                     From="{StaticResource SlideOffSet}" To="0" 
                     Duration="0:0:0.3" />
</Storyboard>

The above code basically changes the target control, in our case the Rectangle, and shifts the X transform anchor value.

Initiate The Dark Move Magic On Button Click

Believe it or not, but we are not finished. The final bit of code has us, again, putting code in the Resource section which will define a style for our Rectangle.

This style is a trigger which is based, bound, off of the value of the IsChecked and depending on its value, initiates the SlideLeft (to move into view) and the SlideRight to move it back off the screen.

<Style TargetType="{x:Type Rectangle}">
    <Style.Triggers>
        <DataTrigger Binding="{Binding IsChecked, ElementName=SlideState}" Value="True">
            <DataTrigger.EnterActions>
                <BeginStoryboard Storyboard="{StaticResource SlideLeft}" />
            </DataTrigger.EnterActions>
            <DataTrigger.ExitActions>
                <BeginStoryboard Storyboard="{StaticResource SlideRight}" />
            </DataTrigger.ExitActions>
        </DataTrigger>
    </Style.Triggers>
</Style>

Final Thoughts & Code

On the following page (#2) is the full code for your perusal, with no changes from above. For me learning how to position a panel off the screen and then move it was trial and error of internet searches and reading StackOverflow posts. Each component on its own is understandable, but has to be consumed in a certain order. This post is that order in which I would have liked to have read first.

To further your understanding of this see Transforms Overview on Microsoft’s documentation.

Full Code on Page 2 —-(*if link to page #2 is not seen below, load the whole article and scroll down*)——–>

Share

Xaml: MVVM Example for Easier Binding

BasicBindingUpdate 11.07.2013 : Added ICommanding example.
Update 10.25.2013 : Added how to use the CallerMemberName attribute with INotifyPropertyChanged in .Net 4.

This Post describes an MVVM roll your own implementation which provides a basic example of the MVVM and binding.

Create The VM

The view model (VM) implements the INotifyPropertyChange process to report any changes to any bound controls.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace SO_WPF.ViewModels {

public class MainVM : INotifyPropertyChanged 
{

    private List<string> _Members;
    private int _MemberCount;
    private bool _IsMembershipAtMax;
    public bool IsMembershipAtMax 
    {
        get { return MemberCount > 3; }
    }
    public int MemberCount 
    { 
        get { return _MemberCount; }
        set
        {
            _MemberCount = value; 
            OnPropertyChanged();
            OnPropertyChanged("IsMembershipAtMax");
        } 
    }

    public List<string> Members 
    { 
        get { return _Members; }
        set { _Members = value; OnPropertyChanged(); } 
    }
    public MainVM()
    {
        // Simulate Asychronous access, such as to a db.

        Task.Run(() =>
                    {
                        Members = new List<string>() {"Alpha", "Beta", "Gamma", "Omega"};
                        MemberCount = Members.Count;
                    });
    }
    /// <summary>Event raised when a property changes.</summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>Raises the PropertyChanged event.</summary>
    /// <param name="propertyName">The name of the property that has changed.</param>
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

}
}

Main Page Code Behind

We now need to hook up the main page view model to the main page. To do that we will do two things, one, make a non INotifyPropertyChanged property on the class which will hold our view model. Two we will instantiate it, hook it up to the page’s data along with our property. Doing that ensures that everything on our page will get access to our view model, thanks to the data context and how controls inherit their parents data context.

using SO_WPF.ViewModels;

namespace SO_WPF
{
    public partial class MainWindow : Window
    {

        public MainVM ViewModel { get; set; }

        public MainWindow()
        {
            InitializeComponent();

            // Set the windows data context so all controls can have it.
            DataContext = ViewModel = new MainVM();

        }

    }
}

Xaml

Now in the xaml we can simply bind to the View Models properties directly without any fuss.

<Window x:Class="SO_WPF.MainWindow"
        xmlns:viewModels="clr-namespace:SO_WPF.ViewModels"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        d:DataContext="{d:DesignInstance {x:Type viewModels:MainVM}}"
        Title="MainWindow"
        Height="300"
        Width="400">

<StackPanel Orientation="Vertical">
    <ListBox Name="lbData"
                ItemsSource="{Binding Members}"
                SelectionMode="Multiple"
                Margin="10" />

    <Button Height="30"
            Width="80"
            Margin="10"
            Content="Click Me" />
</StackPanel>

</Window>

In the above code we bind the Members to the listbox just by specifying the Members property name. As to the highlighted lines, I have added them as a debug design option. That lets Visual Studio and Blend know that our data context is our MainVM. That allows for the editor to present us with the options of data binding to the appropriate items, and not having it blank.

This has been a simple example, but a powerful one which can be used as a binding strategy for any WPF, Silverlight or Windows Phone Xaml based applications in C#.

Note though it is not shown, sometimes in styles one needs the element name to bind to, where the data context will fail due to the nature of the style binding, the above page we would bind to the page name as provided (“MainWindow”) and then the property name as usual!

Extra Credit ICommanding

I won’t go into much detail about commanding, but the gist is that the ViewModel is not directly responsible for actions which can happen due to the ICommanding process, but allow for binding operations to occur against those actions and those actions are performed elsewhere usually on a view.

Below is our view model with the commanding public variables which can be consumed by controls (or other classes which have access to the VM).

public class MainVM : INotifyPropertyChanged
{
    #region Variables
       #region Commanding Operations

    public ICommand ToggleEditing { get; set; }
    public ICommand ReportError   { get; set; }
    public ICommand CheckSequence { get; set; }

       #endregion
       #region Properties
    public string ErrorMessage { // the usual INotifyProperty as shown before }
       #endregion
    #endregion
}

Then on our main page which consumes the view model we then process those requests by creating methods to fulfill those operations. Note that the example below references properties on the VM which were not shown in the example,
but that is not important in this article. But one variable is shown and that is the error message. This allow anyone to push an error using the commanding to the viewmodel which is subsequently shown. That is a peek at the power of commanding right there to allow a dependency injection of setting an error variable to be done outside the VM but used by those which consume the VM!

public partial class MainWindow : Window
{

    public MainWindow()
    {
        InitializeComponent();

        // Set the windows data context so all controls can have it.
        DataContext = ViewModel = new MainVM();

        SetupCommanding();
    }

    private void SetupCommanding()
    {
        // Commanding using OperationCommand class
        ViewModel.ToggleEditing         = new OperationCommand((o) => ViewModel.IsEditing = !ViewModel.IsEditing);
        ViewModel.ReportError           = new OperationCommand((o) => ViewModel.ErrorMessage = (string)o);
        ViewModel.CheckSequence         = new OperationCommand(CheckSequences, CanExecute);

    }

     private void CheckSequences(object obj)
     {
        ...
     }

    private bool CanExecute(object obj)
    {
        return !ViewModel.UnsavedsExist;
    }
}

Finally the ICommanding class used.

public class OperationCommand : ICommand
{

    #region Variables

    Func<object, bool> canExecute;
    Action<object> executeAction;

    public event EventHandler CanExecuteChanged;

    #endregion

    #region Properties

    #endregion

    #region Construction/Initialization

    public OperationCommand(Action<object> executeAction)
        : this(executeAction, null)
    {
    }

    public OperationCommand(Action<object> executeAction, Func<object, bool> canExecute)
    {
        if (executeAction == null)
        {
            throw new ArgumentNullException("Execute Action was null for ICommanding Operation.");
        }
        this.executeAction = executeAction;
        this.canExecute = canExecute;
    }

    #endregion

    #region Methods

    public bool CanExecute(object parameter)
    {
        bool result = true;
        Func<object, bool> canExecuteHandler = this.canExecute;
        if (canExecuteHandler != null)
        {
            result = canExecuteHandler(parameter);
        }

        return result;
    }

    public void RaiseCanExecuteChanged()
    {
        EventHandler handler = this.CanExecuteChanged;
        if (handler != null)
        {
            handler(this, new EventArgs());
        }
    }

    public void Execute(object parameter)
    {
        this.executeAction(parameter);
    }

    #endregion
Share

Xaml: Adding Visibility Behaviors Using Blend to A DataGrid for WPF or Silverlight

iStock_000015143879XSmallIn Xaml the determining when to trigger the visibility, or the hiding of  controls or their functionality is a key concept of doing either WPF or Silverlight programming. This article builds upon my article C#: WPF and Silverlight DataGrid Linq Binding Example Using Predefined Column Header Names in Xaml where we are going to add behaviors to the datagrid shown.  (Don’t worry about reading the article, for I will get you up to speed with the code snippets in this article.) We will use Microsoft’s Expression Blend product to do the dirty work of xaml modification to our DataGrid and it will be shown in a step by step process.

Concept

In the previous article the idea was to load our datagrid with two columns of data. The first column showed us a filename and the second column displayed a modified filename with a count in it. We will take that one step further and have a description show up with the file size. Here is the resulting look:

Inital with Description

The user gets the description when the row is clicked.

But what if we wanted to disable that functionality and automatically show the user all the items when the mouse hovers over the datagrid such as

Result

Setup

First thing we need to do is setup our datagrid. Below is the xaml and the code behind to load our datagrid. Note the datagrid has the RowDetailsVisibiltyMode set to collapsed. That means that when a user clicks on the row, it will only select it and not open up our description. The loading of the ItemsSource happens during the construction and after the initial initialization and is shown in C# in the second pane.

<DataGrid x:Name="dgPrimary"
            RowDetailsVisibilityMode="Collapsed">
    <DataGrid.RowDetailsTemplate>
        <DataTemplate>

            <TextBlock FontWeight="Bold"
                        Text="{Binding Size, StringFormat=Size \{0\} (bytes)}" />

        </DataTemplate>
    </DataGrid.RowDetailsTemplate>
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding Original}"
                            Header="File Name Before"
                            IsReadOnly="True" />
        <DataGridTextColumn Binding="{Binding New}"
                            Header="File Name After"
                            IsReadOnly="True" />
    </DataGrid.Columns>
</DataGrid>
dgPrimary.ItemsSource = 
    new DirectoryInfo( "c:\\" ).GetFiles()
                               .Select( ( fInfo, index ) => new
                    {
                        Original = fInfo.Name,
                        New = string.Format( "{0}_{1}{2}", 
                                    System.IO.Path.GetFileNameWithoutExtension( fInfo.Name ), 
                                    index, 
                                    System.IO.Path.GetExtension( fInfo.Name ) ),
                        Size = fInfo.Length
                    } );

Behaviors and Blend

One of the easiest ways to add a behavior [of the action] to a control is to use Blend to add an interaction behavior.  In our case we want a mouse over action to open up all of the Row Details and a secondary action to close them when the mouse leaves. The behavior we need to search for in blend is the ChangePropertyAction. Below we drag (or add) two behaviors to the datagrid and change the RowDetailsVisibilityMode to visible on mouse enter and to collapsed on mouse leave.

cpaEnter cpaLeave

Then when we build and run the app, the mouse hover over makes the descriptions visible and collapsed depending on the mouse. Here is the final xaml:

<DataGrid x:Name="dgPrimary"
            RowDetailsVisibilityMode="Collapsed">
    <DataGrid.RowDetailsTemplate>
        <DataTemplate>

            <TextBlock FontWeight="Bold"
                        Text="{Binding Size, StringFormat=Size \{0\} (bytes)}" />

        </DataTemplate>
    </DataGrid.RowDetailsTemplate>
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding Original}"
                            Header="File Name Before"
                            IsReadOnly="True" />
        <DataGridTextColumn Binding="{Binding New}"
                            Header="File Name After"
                            IsReadOnly="True" />
    </DataGrid.Columns>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseEnter" SourceObject="{Binding ElementName=dgPrimary}">
            <ei:ChangePropertyAction x:Name="cpaEnter" PropertyName="RowDetailsVisibilityMode">
                <ei:ChangePropertyAction.Value>
                    <DataGridRowDetailsVisibilityMode>Visible</DataGridRowDetailsVisibilityMode>
                </ei:ChangePropertyAction.Value>
            </ei:ChangePropertyAction>
        </i:EventTrigger>
        <i:EventTrigger EventName="MouseLeave">
            <ei:ChangePropertyAction x:Name="cpaLeave"
                PropertyName="RowDetailsVisibilityMode" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
Share

C#: WPF and Silverlight DataGrid Linq Binding Example Using Predefined Column Header Names in Xaml

iStock_000015057287XSmallThis snippet is from my archives and reminds me how to setup column names for a WPF/Silverlight datagrid and bind them to the data by not using AutoGenerateColumn feature. (See below for the visual end result.)

Ω Check out the related post Xaml: Adding Visibility Behaviors Using Blend to A DataGrid for WPF or Silverlight

DataGrid Column Names Setup in Xaml

In this example we will have two columns where the data will be filename strings. We will specify the columns in the Xaml to use the DataGridTextColumn and not to dynamically generate columns on our Datagrid:

<DataGrid x:Name="dgOperation" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="File Name Before" Binding="{Binding Path=Original}"/>
        <DataGridTextColumn Header="File Name After"  Binding="{Binding Path=New}"/>
    </DataGrid.Columns>
</DataGrid>

The result is that the header row will have two columns with the name “File Name Before” and “File Name After” will subsequently bind them to a data object with the property names of “Original” and “New”.

Actual Binding During the Loading of the Grid

We will read in a directory for the WPF example from the hard drive and fill the original file names to the first column and a generated name for the second column. Since we have specified in the Xaml that we are binding to “Original” and “New” properties the dynamic linq object created will have those properties.

dgOperation.ItemsSource = Directory.GetFiles( @"C:\" )
                                   .Select( ( nm, index ) => new
                                       {
                                          Original = System.IO.Path.GetFileName( nm ),
                                          New = string.Format( "{0}_{1}{2}", System.IO.Path.GetFileNameWithoutExtension( nm ),
                                                                             index,
                                                                             System.IO.Path.GetExtension( nm ) )
                                        } );

The result is as below:

DataGridBind

Note: Out of the box editing the rows will throw an exception. To fix that make each of the rows read only.

Share

C# WPF: Threading, Control Updating, Status Bar and Cancel Operations Example All In One

You're All Mine Bringing them together; this article shows how to do GUI processing and background work in WPF using C# with an eye on progress and the ability to cancel. This article demonstrates all these topics :

  • Create a background thread to do work away from GUI as to not slow down the user experience.
  • Pass data from the GUI thread to the background worker and subsequently to GUI to update appropriate controls.
  • Safely update screen data after work is done.
  • Invoke/Dispatch back to control GUI thread from worker as needed.
  • Associate threaded work with progress bar in WPF to provide status.
  • Allow the users to stop or cancel the process and handle it appropriately.
  • Enable and disable buttons so user is not kicking off superfluous actions while the work process is executing.
  • I write these articles to enlighten the development community as well as notes for myself as I work in the differing technologies going forward. For this article I saw many websites which would individually piece these topics together, but none of them showed the the whole process. This article covers the whole process.

    BackgroundWorker Not Just For Winforms Anymore

    The goal of the operation is to do the work, and that work is not done on the GUI thread where a user will notice the slow down, but on a background thread. To accomplish that, one of the best fixtures of .Net is the Background thread which was introduced for Winforms. Even though the BackgroundWorker is a nice drag and droppable component in the Winform arena it does not mean that it can’t moonlight in our WPF sandbox. We simply have to instantiate and initialize it ourselves. Here is the code to do that in our Window class:

    public partial class Window1 : Window
    {
        BackgroundWorker bcLoad = new BackgroundWorker();
    
        public Window1()
        {
            InitializeComponent();
    
            bcLoad.DoWork += new DoWorkEventHandler(bcLoad_DoWork);
            bcLoad.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bcLoad_RunWorkerCompleted);
    
            bcLoad.WorkerReportsProgress = true;
            bcLoad.ProgressChanged += new ProgressChangedEventHandler(bcLoad_ProgressChanged);
    
            bcLoad.WorkerSupportsCancellation = true;
        }

    Explanation

    • Line 3: We must declare and subsequently instantiate a background worker which will be used. This object and operations can be reused so this only needs to be done once and to keep temporality we declare it on the window class itself.
    • Line 9/10: We are subscribing to the events which will handle our work on the separate thread. The DoWork event is where the heavy lifting of the offloaded operations will occur. The RunWorkerCompleted event is the safe haven where we can pipe the results of DoWork to the GUI controls without having to invoke or dispatch back to the controls because the event code will be done on the GUI’s thread. Note intellisense is our friend one these lines. Type in += and intellisense offers to create the subscribing to the event handler with a Tab. A subsequent Tab key will create the method stub. Give it a try.
    • Line 12/13: Here is where we tell the background worker that we are going to do a progress operation back to the GUI for the user experience. Note if we don’t specify WorkReportsProgress we will get this exception:This BackgroundWorker states that it doesn’t report progress. Modify WorkerReportsProgress to state that it does report progress.
    • Line 15: Here is where we also inform the BackgroundWorker object that we will be handling cancellation.

    Xaml Mammal

    With the goal of providing a cancel and progress status bar as such:

    StatusBar

    Here is the code to add to our Xaml where it lives at the bottom of our screen. Below the code is the status bar and its child elements of a TextBlock, ProgessBar and button for the cancel.

    <StatusBar Name="stBarPrimary" Grid.Row="5" Grid.ColumnSpan="4" Background="AntiqueWhite" Margin="0,9.056,0,0">
    
        <StatusBarItem>
            <TextBlock Name="tbStatus">Status:</TextBlock>
        </StatusBarItem>
    
        <StatusBarItem>
            <ProgressBar Height="12" Width="400" Margin="20,0,5,1" Name="pBar1" Visibility="Hidden" VerticalAlignment="Bottom" />
        </StatusBarItem>
    
        <StatusBarItem>
            <Button Height="24" Width="80" Content="Cancel" Name="btnCancel" Visibility="Hidden" Click="btnCancel_Click" />
        </StatusBarItem>
    
    </StatusBar>

    The only thing to note that initially the Cancel button and progress bar visibility is set to hidden.

    Launching the Operation from a Button Click

    Here is the code where we launch the DoWork event operation, but more importantly we pass in data which the DoWork can use to perform its operations. The data here is special because it all resides on gui controls. We don’t want to access that data directly from our worker thread otherwise we will get this exception as reported in my previous article (C# WPF: Linq Fails in BackgroundWorker DoWork Event ) :

    The calling thread cannot access this object because a different thread owns it.

    private void btnAcquire_Click(object sender, RoutedEventArgs e)
    {
        btnAcquire.IsEnabled = false;
    
        tbStatus.Text = "Status :"; // Reset status if changed.
    
        btnCancel.Visibility =
        pBar1.Visibility = Visibility.Visible;
    
        bcLoad.RunWorkerAsync(new Dictionary<string, string>()
                    {
                        { "AccountID", tbAccount.Text },
                        { "CategoryID", tbCategory.Text },
                        { "SequenceNumber", tbSequenceNumber.Text }
                    });
    }

    The first thing we do is darken the button which launched the process so the user doesn’t click it twice. Then we make the cancel button and the progress bar visible to the user as the process is about to begin. Finally we extract data held in Gui controls exposed to the user. We cannot access that data once the thread is running and must get the data to to the DoWork process.

    Is It Done Yet? Move the Progress Bar.

    Here is our event which handles the moving of the progress bar. This is done on the GUI thread so no need to do checking of the dispatch. We get a numeric percentage which moves the bar along and incremental movements.

    void bcLoad_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        pBar1.Value = e.ProgressPercentage;
    }

    Cancelling the Operation from a Button Click

    The cancel is similar because we do the opposite of the initiation code above by hiding the controls and informing the user of the stop action. This cancel is initiated by the cancel button on click event shown in the Xaml above. We do all this on the cancel because a cancel event does not fire the RunWorkerCompleted event.;

    private void btnCancel_Click(object sender, RoutedEventArgs e)
    {
        bcLoad.CancelAsync();
    
        // Turn off from here the progress bar and cancel button and report that.
        tbStatus.Text = "Status : Canceled";
    
        btnCancel.Visibility =
        pBar1.Visibility = Visibility.Hidden;
    
        btnAcquire.IsEnabled = true;
    
    }

    Time to DoWork

    Ok, here is what we have been waiting to do, the actual work which will occur in the background on the separate thread. We add to the code for the DoWork Event.

    void bcLoad_DoWork(object sender, DoWorkEventArgs e)
    {
        try
        {
            List<string> Results = new List<string>();
    
            Dictionary<string, string> UserInputs = e.Argument as Dictionary<string, string>;
    
            if (UserInputs != null)
            {
                bcLoad.ReportProgress(33);
    
                DatabaseContext dbc = new DatabaseContext();
    
                var data = dbc.SystemData
                              .Where(ac => ac.Account_ID == UserInputs["AccountID"])
                              .Where(ac => ac.TimeStamp == 0)
                              .Where(ac => ac.Category_id == UserInputs["CategoryID"])
                              .Where(ac => (int)ac.Category_seq_nbr == int.Parse(UserInputs["SequenceNumber"]))
                              .Select(ac => acc.UnitCode);
    
                Results.Add("Unit Code: " + data.First());
    
                if (bcLoad.CancellationPending)
                {
                    e.Cancel = true;
                    return;
                }
    
                bcLoad.ReportProgress(60);
                Results.Add("Income Processing Types: " + string.Join(" ", Financial.GetIncomeProcessingTypes().ToArray()));
    
                if (bcLoad.CancellationPending)
                {
                    e.Cancel = true;
                    return;
                }
    
                bcLoad.ReportProgress(80);
    
                Results.Add("Stock Movement: " + string.Join(" ", Financial.GetStockMovementTypes().ToArray()));
    
                bcLoad.ReportProgress(100);
    
                e.Result = Results; // Pass the results to the completed events to process them accordingly.
            }
        }
        catch (Exception ex)
        {
            lbxData.Dispatcher.BeginInvoke(new Action(() => lbxData.Items.Add("Exception Caught: " + ex.Message)));
        }
    
    }

    Explanation

    • Line 5: We will return Results to the RunWorkerCompleted event and this line initializes it.
    • Line 7: This method was passed data. Here is where we unbox our present which is a Dictionary which contains our user input we are interested in processing.
    • Line 11: Here is where we pass our percentage. It will move the bar up 33 percent. You can dictate what percentage of the process is done. I have chosen 33 in this case, because the code does three separate work items and it will allow for a smother transitions.
    • Line 16 : Here is where we access the passed in dictionary which holds our user inputs.
    • Line 22: The first work unit will be executed on this line and the database call will be made. After this line we can bump up the progress bar.
    • Line 24: Our first important check to see if the user wants to cancel. If a cancel event has been fired, we simply exit this method. Cancellation means that the follow up function RunWorkerCompleted will not be fired.
    • Line 27: If we are here, bump up the progress bar.
    • Line 28-37: Repeat of the work, check cancel and move progress bar as shown above. Though once done, we bump the result to 100%! We are done with the work.
    • Line 39: Now we place the results on the DoWorkEvents Result property to be used in the RunWorkerCompleted.
    • Line 44: The exception actually reports the error to in this case a List box named lbxData. This is accomplished by using the controls Dispatcher invoke code. That invoke ensures that the action against the control is done safely on the GUI thread. We could have used this in our code instead of putting things onto a result. But I have chosen not to because if the user cancels, we don’t have half loaded data to unload from controls. By passing the result to the RunWorkerCompleted event we ensure that all data is properly populated and no complex back out of data during error situations is needed. But since this is a exceptional and unplanned situation, I have chosen to write to the control directly. You have the tools to do either, its your design. :-)

    Time to Display the Result to the User

    Finally our work is done and all is complete. The below code safely writes the result to the GUI thread’s controls.

    void bcLoad_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if ((e.Result != null) && (e.Cancel == false))
        {
            List<string> results = e.Result as List<string>;
    
            if (results != null)
                foreach (string item in results)
                    lbxData.Items.Add(item);
        }
        else // User has canceled or there is no data.
        {
           lbxData.Items.Clear();
        }
    
        btnAcquire.IsEnabled = true;
    
        btnCancel.Visibility =
        pBar1.Visibility = Visibility.Hidden;
    
    }

    Explanation

    • Line 3: We are expecting results, do a check for good measure.
    • Line 5: Unbox our present from the DoWork event.
    • Line 7: Load our ListBox with our found data!
    • Line 10: Turn on the button which started it all.
    • Line 12-13: Hide the progress bar and Cancel button.

    This is now done and I hope it helped.

    Miscellaneous

    I am including the Xaml which I used which shows the user input and input button for those who may be curious about the code.

    <Window x:Class="CSS_Research.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="360" Width="582">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="34*" />
                <RowDefinition Height="34*" />
                <RowDefinition Height="34*" />
                <RowDefinition Height="34*" />
                <RowDefinition Height="134*" />
                <RowDefinition Height="37*" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="170*" />
                <ColumnDefinition Width="216*" />
                <ColumnDefinition Width="16*" />
                <ColumnDefinition Width="155*" />
            </Grid.ColumnDefinitions>
            <Label Name="lbAccount" Content="Account" HorizontalAlignment="Center" VerticalAlignment="Center" />
            <TextBox Name="tbAccount" Grid.Column="1" VerticalContentAlignment="Center" VerticalAlignment="Center" Height="Auto" Margin="0">392702150</TextBox>
            <Label Margin="0" Name="label1" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center">Category</Label>
            <TextBox Margin="0" Name="tbCategory" VerticalContentAlignment="Center" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Center">ret</TextBox>
            <TextBox Grid.ColumnSpan="1" Margin="0" Name="tbSequenceNumber" VerticalContentAlignment="Center" Grid.Column="1" Grid.Row="2" HorizontalAlignment="Stretch" VerticalAlignment="Center" Grid.RowSpan="1">1</TextBox>
            <Label Grid.RowSpan="1" Margin="0" Name="label2" Grid.Row="2" HorizontalAlignment="Center" VerticalAlignment="Center">Category Sequence Number</Label>
            <Button Grid.Column="3" Margin="54.252,6.696,37,6.696" Name="btnAcquire" Click="btnAcquire_Click">Acquire</Button>
    
            <TabControl  Name="tabControl1" Grid.ColumnSpan="4" Grid.Row="4" Margin="12,0.367,0,32.597" Grid.RowSpan="2">
                <TabItem Header="General" Name="tabItem1">
                    <Grid>
                        <ListBox Name="lbxData" Grid.IsSharedSizeScope="True" />
                    </Grid>
                </TabItem>
                <TabItem Header="Data" Name="tbData">
                    <Grid>
    
                    </Grid>
    
                </TabItem>
            </TabControl>
    
            <StatusBar Name="stBarPrimary" Grid.Row="5" Grid.ColumnSpan="4" Background="AntiqueWhite" Margin="0,9.056,0,0">
    
                <StatusBarItem>
                    <TextBlock Name="tbStatus">Status:</TextBlock>
                </StatusBarItem>
    
                <StatusBarItem>
                    <ProgressBar Height="12" Width="400" Margin="20,0,5,1" Name="pBar1" Visibility="Hidden" VerticalAlignment="Bottom" />
                </StatusBarItem>
    
                <StatusBarItem>
                    <Button Height="24" Width="80" Content="Cancel" Name="btnCancel" Visibility="Hidden" Click="btnCancel_Click" />
                </StatusBarItem>
    
            </StatusBar>
    
        </Grid>
    </Window>
    Share

    C# WPF: Linq Fails in BackgroundWorker DoWork Event

    iStock_000010874966XSmall I write this because if I ran into this, someone else will. Now first off it wasn’t Linq that failed, but it looked like it and here is my story of failure I found in a WPF background worker threading code which can happen in other areas as well.

    The perennial advice to all people in the MSDN forums as well as others is never, never, never  write to a GUI control in a thread or the BackgroundWorker’s do work event. All GUI work must be done on a the GUI. I very well knew that…but the reverse (don’t read from a GUI in a different thread)  is also true and that bit me. Let me explain.

    Anecdotal Evidence

    Imagine my surprise when I created a WPF project at a new job and started getting this exception in the DoWork code of my BackgroundWorker when the first use of a Linq query’s delayed execution point was accessed :

    The calling thread cannot access this object because a different thread owns it.

    The code threw an exception, on line 16 below, where I was loading data from after the Linq call.  I began to think, does this have something to do with the Linq DataContext?

    void bcLoad_DoWork(object sender, DoWorkEventArgs e)
    {
        try
        {
            List<string> Results = new List<string>();
    
            DatabaseDataContext dbc = new DatabaseDataContext();
    
            var data = dbc.SystemData
                          .Where(ac => ac.Account_id == tbAccount.Text)
                          .Where(ac => ac.TimeStamp == 0)
                          .Where(ac => ac.Category_id == tbCategory.Text)
                          .Where(ac => (int)ac.Category_seq_nbr == int.Parse(tbSequenceNumber.Text))
                          .Select(ac => ac.Unit_Code);
    
            Results.Add("Unit Code: " + data.First());
    
            e.Result = Results;
        }
        catch (Exception ex)
        {
            lbxData.Dispatcher.BeginInvoke( new Action(() => lbxData.Items.Add( "Exception Caught: " + ex.Message )));
        } 
    }

    No the problem was within the setup of the Lambdas for the Linq query. All the highlighted lines above is where the actual problem originates and not on the final highlighted line.

    The problem was that I was accessing Gui controls data, and not changing; that was the nuance. For in my mind that was ok, it was a passive read action and not a direct writing one. Obviously not.

    Note: If you have come to this blog experiencing this problem but for the writing of items to a control, one method to solve it is to use the Dispatcher off the control on any thread not just BackgroundWorker. That code is shown in my exception catch blog above. That line is perfectly fine to do and is not another issue. The lbxData is a Listbox on the main Xaml and because of the immediacy of the exception, I write

    Resolution

    Since I was already using the plumbing of the DoWorkEventArgs, it seemed a natural choice to pass in the data using that object. I changed the call to pass in a Dictionary of values to extract the data as such:

    bcLoad.RunWorkerAsync(new Dictionary<string, string>() 
                            { 
                                { "AccountID",      tbAccount.Text }, 
                                { "CategoryID",     tbCategory.Text },
                                { "SequenceNumber", tbSequenceNumber.Text }
                            });

    Then to consume the Dictionary as such:

    void bcLoad_DoWork(object sender, DoWorkEventArgs e)
    {
        try
        {
            List<string> Results = new List<string>();
    
            Dictionary<string, string> UserInputs = e.Argument as Dictionary<string, string>;
    
            if (UserInputs != null)
            {
    
            DatabaseContext dbc = new DatabaseContext();
    
            var data = dbc.SystemData
                          .Where(ac => ac.Account_ID == UserInputs["AccountID"])
                          .Where(ac => ac.TimeStamp == 0)
                          .Where(ac => ac.Category_id == UserInputs["CategoryID"])
                          .Where(ac => (int)ac.Category_seq_nbr == int.Parse(UserInputs["SequenceNumber"]))
                          .Select(ac => acc.UnitCode);
    
            Results.Add("Unit Code: " + data.First());
    
            e.Result = Results; // Pass the results to the completed events to process them accordingly.
            }
        }
        catch (Exception ex)
        {
            lbxData.Dispatcher.BeginInvoke( new Action(() => lbxData.Items.Add( "Exception Caught: " + ex.Message )));
        }
    
    }

    I simply convert the object property of Argument to a Dictionary, as highlighted, and go do the work. One doesn’t have to use a Dictionary. One can pass in any object off of the Argument property. Hope This Helps

    Share