Saturday, 16 March 2013

wpf: datagrid with coloured bars for groupings and how to sort groups by items count inside the datagrid itself

In this post I'll show how to get a grouped datagrid with more than one group and with a template differently coloured for each group and how to sort each group and subgroup by the number of  items.

As usual I got inspiration from other sites and programmers ;-)


The final result will look like this screenshot:



First let's load an ItemsSource for the datagrid. I used a ListCollectionView from business objects of mine.
Then I add the sort descriptions to group my collection by three different properties.

   1 public MainWindow()
   2         {
   3             InitializeComponent();
   4 
   5             // create a List<MyBusinessObject>
   6             List<MyBusinessObject> MyObjects = new List<MyBusinessObject>();
   7 
   8             MyObjects.Add(new MyBusinessObject() { Age = 13, FirstName = "walter", Leaf = "3", Main = "A", Section = "Z" });
   9             MyObjects.Add(new MyBusinessObject() { Age = 45, FirstName = "gianni", Leaf = "3", Main = "A", Section = "Z" });
  10             MyObjects.Add(new MyBusinessObject() { Age = 23, FirstName = "anthony", Leaf = "4", Main = "A", Section = "Z" });
  11             MyObjects.Add(new MyBusinessObject() { Age = 56, FirstName = "michael", Leaf = "4", Main = "A", Section = "Z" });
  12             MyObjects.Add(new MyBusinessObject() { Age = 62, FirstName = "frank", Leaf = "4", Main = "A", Section = "Z" });
  13             MyObjects.Add(new MyBusinessObject() { Age = 23, FirstName = "anthony", Leaf = "1", Main = "B", Section = "Z" });
  14             MyObjects.Add(new MyBusinessObject() { Age = 56, FirstName = "michael", Leaf = "2", Main = "B", Section = "Z" });
  15             MyObjects.Add(new MyBusinessObject() { Age = 2, FirstName = "parsifal", Leaf = "2", Main = "B", Section = "Z" });
  16             MyObjects.Add(new MyBusinessObject() { Age = 76, FirstName = "joseph", Leaf = "9", Main = "B", Section = "Z" });
  17             MyObjects.Add(new MyBusinessObject() { Age = 90, FirstName = "carl", Leaf = "12", Main = "B", Section = "V" });
  18             MyObjects.Add(new MyBusinessObject() { Age = 41, FirstName = "fred", Leaf = "12", Main = "B", Section = "V" });
  19             MyObjects.Add(new MyBusinessObject() { Age = 36, FirstName = "brad", Leaf = "7", Main = "C", Section = "V" });
  20         
  21             // create and link the datacontext
  22             MyDataContext = new DT();
  23             this.DataContext = MyDataContext;
  24 
  25             // create ListCollectionView
  26             ListCollectionView GroupedCollection = new ListCollectionView(MyObjects);
  27 
  28             // add the groupings
  29             GroupedCollection.GroupDescriptions.Add(new PropertyGroupDescription("Main"));
  30             GroupedCollection.GroupDescriptions.Add(new PropertyGroupDescription("Section"));
  31             GroupedCollection.GroupDescriptions.Add(new PropertyGroupDescription("Leaf"));

Then I added a value for the CustomSort property to get every group ordered by its number of items.

I got inspiration from this blog post: http://www.mindscapehq.com/blog/index.php/2008/06/19/custom-sorting-wpf-collection-views-and-the-wpf-property-grid/

And then I added the ListCollectionView to my Data Context to get it binded by the datagrid in the xaml:

   1             // add the CustomSort
   2             GroupedCollection.CustomSort = new GroupedItemsSorter(MyObjects, ListSortDirection.Descending);
   3 
   4             // add the ItemsSource
   5             MyDataContext.MyItemsSource = GroupedCollection;        
   6         }
   7 
   8         private DT MyDataContext;
   9 
  10     }
  11 
  12     public class DT : ViewModelBase
  13     {
  14 
  15         private ListCollectionView myItemsSource;
  16         public ListCollectionView MyItemsSource
  17         {
  18             get
  19             {
  20                 return myItemsSource;
  21             }
  22             set
  23             {
  24                 myItemsSource = value;
  25                 RaisePropertyChanged("MyItemsSource");
  26             }
  27         }
  28 
  29     }

As you can see the CustomSort property is an object of a custom class. I pass to the constructor the Collection and the desired direction for the ordering.

Let's see the code of the custom class:

   1 class GroupedItemsSorter : IComparer
   2     {
   3         public GroupedItemsSorter(List<MyBusinessObject> Items,  ListSortDirection Direction)
   4         {
   5             this.Items = Items;
   6             this.Direction = Direction;
   7         }
   8 
   9         public int Compare(object x, object y)
  10         {
  11             String Main_x = (x as MyBusinessObject).Main;
  12             String Main_y = (y as MyBusinessObject).Main;
  13 
  14             String Section_x = (x as MyBusinessObject).Section;
  15             String Section_y = (y as MyBusinessObject).Section;
  16 
  17             String Leaf_x = (x as MyBusinessObject).Leaf;
  18             String Leaf_y = (y as MyBusinessObject).Leaf;
  19 
  20             int SameMainFor_x = Items.Where(it => it.Main.Equals(Main_x)).Count();
  21             int SameMainFor_y = Items.Where(it => it.Main.Equals(Main_y)).Count();
  22 
  23             if (SameMainFor_x != SameMainFor_y)
  24                 return (Direction == ListSortDirection.Ascending ? 1 : -1) * Math.Sign(SameMainFor_x - SameMainFor_y);
  25 
  26 
  27             int SameSectionFor_x = Items.Where(it => it.Section.Equals(Section_x)).Count();
  28             int SameSectionFor_y = Items.Where(it => it.Section.Equals(Section_y)).Count();
  29 
  30             if (SameSectionFor_x != SameSectionFor_y)
  31                 return (Direction == ListSortDirection.Ascending ? 1 : -1) * Math.Sign(SameSectionFor_x - SameSectionFor_y);
  32 
  33             int SameLeafFor_x = Items.Where(it => it.Leaf.Equals(Leaf_x)).Count();
  34             int SameLeafFor_y = Items.Where(it => it.Leaf.Equals(Leaf_y)).Count();
  35 
  36             if (SameLeafFor_x != SameLeafFor_y)
  37                 return (Direction == ListSortDirection.Ascending ? 1 : -1) * Math.Sign(SameLeafFor_x - SameLeafFor_y);
  38 
  39             return 0;
  40 
  41         }
  42 
  43         private List<MyBusinessObject> Items { get; set; }
  44         private ListSortDirection Direction { get; set; }
  45     }

First the class must implement the IComparer interface. The Collection uses the method Compare to know which must come first between two objects. The method must return an integer ,1,-1 or 0 respectively if x is greater,  smaller or equal to y.

I implemented the Compare method in this way. I count how many items share the same value for the property 'Main', that is the first property I have grouped by, for the objectx x and y.

Than if the two counts are different the greatest object of the two is the one who have the greatest count.
And so I return the sign of the counts difference multiplied by the desired Direction (ascending or descending).

If the two counts are equal I repeat the same code for the Section property that is the second property I have grouped by and so on.

With some effort I think it is possible to create a generic comparer for every kind of business object and for any number of grouping properties.

Thi is all concerning the data model. Now let's see the xaml for the datagrid:


   1 <Window x:Class="ColouredGroupedGrid.MainWindow"
   2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:my="clr-namespace:ColouredGroupedGrid"
   4         xmlns:cm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
   5         Title="MainWindow" Height="350" Width="525">
   6 
   7     <Window.Resources>
   8 
   9         <my:ConverterBackgroundHeader x:Key="ConverterBackgroundHeader" />
  10 
  11     </Window.Resources>
  12 
  13     <Grid>
  14 
  15         <Grid>
  16 
  17             <Grid.ColumnDefinitions>
  18                 <ColumnDefinition Width="*"  />
  19             </Grid.ColumnDefinitions>
  20             <Grid.RowDefinitions>
  21                 <RowDefinition Height="*"  />
  22             </Grid.RowDefinitions>
  23 
  24             <DataGrid AutoGenerateColumns="False" Name="dataGrid1" 
  25                       ScrollViewer.CanContentScroll="True" 
  26                       ScrollViewer.VerticalScrollBarVisibility="Auto"
  27                       ScrollViewer.HorizontalScrollBarVisibility="Auto" 
  28                       Foreground="Black"  CanUserAddRows="False" IsReadOnly="True" RowBackground="#FFD3E8E0" 
  29                       AlternatingRowBackground="White" HorizontalScrollBarVisibility="Visible" ItemsSource="{Binding MyItemsSource}"
  30                       VerticalScrollBarVisibility="Visible" SelectionMode="Single" RowHeaderStyle="{DynamicResource DataGridRowHeaderStyle1}"    >
  31                 <DataGrid.GroupStyle>
  32                     <GroupStyle>
  33                         <GroupStyle.ContainerStyle>
  34                             <Style TargetType="{x:Type GroupItem}">
  35                                 <Setter Property="Template">
  36                                     <Setter.Value>
  37                                         <ControlTemplate TargetType="{x:Type GroupItem}">
  38                                             <Expander Margin="5,0,0,0" Background="{Binding .,Converter={StaticResource ConverterBackgroundHeader}}">
  39                                                 <Expander.Header   >
  40                                                     <StackPanel Orientation="Horizontal" >
  41                                                         <TextBlock Text="{Binding Path=Name}" />
  42                                                         <TextBlock Text=" => "/>
  43                                                         <TextBlock Text="{Binding Path=ItemCount}"/>
  44                                                         <TextBlock Text=" items"/>
  45                                                     </StackPanel>
  46                                                 </Expander.Header>
  47                                                 <ItemsPresenter />
  48                                             </Expander>
  49                                         </ControlTemplate>
  50                                     </Setter.Value>
  51                                 </Setter>
  52                             </Style>
  53                         </GroupStyle.ContainerStyle>
  54                     </GroupStyle>
  55                 </DataGrid.GroupStyle>
  56                 <DataGrid.Columns>
  57                     <DataGridTextColumn Header="Main" Binding="{Binding Main}"   />
  58                     <DataGridTextColumn Header="Section" Binding="{Binding Section}"  />
  59                     <DataGridTextColumn Header="Leaf" Binding="{Binding Leaf}"  />
  60                     <DataGridTextColumn Header="First Name" Binding="{Binding FirstName}"  />
  61                     <DataGridTextColumn Header="Age" Binding="{Binding Age}"  />
  62                 </DataGrid.Columns>
  63 
  64             </DataGrid>
  65         </Grid>
  66 
  67     </Grid>
  68 </Window>
  69 

Here you can see that I added a template for the GroupStyle property of the datagrid. The template is an expander and I read an example of this here: http://wpftutorial.net/DataGrid.html

I just added a left margin of 5 pixels to get an offset for every child group in the hierarchy.

The Expander background is linked to a converter where I managed to get a different colour for each group in the hierarchy.

Background="{Binding .,Converter={StaticResource ConverterBackgroundHeader}}">

The code for the converter is:


   1 public class ConverterBackgroundHeader : IValueConverter
   2     {
   3 
   4         #region IValueConverter Members
   5 
   6         
   7         public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
   8         {
   9             int level = 0;
  10             
  11             CollectionViewGroup obj = value as CollectionViewGroup;
  12 
  13             if (obj == null) return value;
  14 
  15             while ((obj.Items[0] as CollectionViewGroup) != null)
  16             {
  17                 level++;
  18                 obj = obj.Items[0] as CollectionViewGroup;
  19             }
  20 
  21             BrushConverter bc = new BrushConverter();
  22 
  23             switch (level)
  24             {
  25                 case 2:
  26                     return (Brush)bc.ConvertFrom("#E9E8FF");                   
  27                 case 1:
  28                     return (Brush)bc.ConvertFrom("#E3FFFE");
  29                 case 0:
  30                     return (Brush)bc.ConvertFrom("#F8FFE3");
  31                    
  32             }
  33 
  34             return value;
  35            
  36         }
  37 
  38         // ConvertBack is not implemented for a OneWay binding.
  39         public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  40         {
  41             throw new NotImplementedException();
  42         }
  43 
  44         #endregion
  45     }  

The converter receives a CollectionViewGroup. I need to know at what level of the hierarchy the group is.
Since the last group, the group formed by mean of the last sort description of the ListCollectionView, has one or more instances of my business objects as its Items and since, instead, the other groups have other  CollectionViewGroup objects as their Items, I just iterate through the hierarchy incrementing a variable until I find an instance of my business objects. Then I simply return a different color for each value I find from the while loop.

The last thing I want to show is the RowHeaderStyle="{DynamicResource DataGridRowHeaderStyle1}" used in the datagrid.

   1 <Application x:Class="ColouredGroupedGrid.App"
   2              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4              xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
   5              StartupUri="MainWindow.xaml">
   6     
   7         <Application.Resources>
   8 
   9             <BooleanToVisibilityConverter x:Key="bool2VisibilityConverter"/>
  10             <Style x:Key="RowHeaderGripperStyle" TargetType="{x:Type Thumb}">
  11                 <Setter Property="Height" Value="8"/>
  12                 <Setter Property="Background" Value="Transparent"/>
  13                 <Setter Property="Cursor" Value="SizeNS"/>
  14                 <Setter Property="Template">
  15                     <Setter.Value>
  16                         <ControlTemplate TargetType="{x:Type Thumb}">
  17                             <Border Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}"/>
  18                         </ControlTemplate>
  19                     </Setter.Value>
  20                 </Setter>
  21             </Style>
  22             <Style x:Key="DataGridRowHeaderStyle1" TargetType="{x:Type DataGridRowHeader}">
  23                 <Setter Property="Template">
  24                     <Setter.Value>
  25                         <ControlTemplate TargetType="{x:Type DataGridRowHeader}">
  26                             <Grid>
  27                                 <Microsoft_Windows_Themes:DataGridHeaderBorder Background="Transparent"  BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="0" IsPressed="{TemplateBinding IsPressed}" IsHovered="{TemplateBinding IsMouseOver}" IsSelected="{TemplateBinding IsRowSelected}" Orientation="Horizontal" Padding="0" SeparatorBrush="{TemplateBinding SeparatorBrush}" SeparatorVisibility="{TemplateBinding SeparatorVisibility}">
  28                                     <StackPanel Orientation="Horizontal">
  29                                         <ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center"/>
  30                                         <Control SnapsToDevicePixels="false" Template="{Binding ValidationErrorTemplate, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}" Visibility="{Binding (Validation.HasError), Converter={StaticResource bool2VisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}"/>
  31                                     </StackPanel>
  32                                 </Microsoft_Windows_Themes:DataGridHeaderBorder>
  33                                 <Thumb x:Name="PART_TopHeaderGripper" Style="{StaticResource RowHeaderGripperStyle}" VerticalAlignment="Top"/>
  34                                 <Thumb x:Name="PART_BottomHeaderGripper" Style="{StaticResource RowHeaderGripperStyle}" VerticalAlignment="Bottom"/>
  35                             </Grid>
  36                         </ControlTemplate>
  37                     </Setter.Value>
  38                 </Setter>
  39             </Style>
  40 
  41         </Application.Resources>
  42 
  43     
  44 </Application>
  45 

This is just the standard template that comes up with Expression Blend if you tell it to modify the default RowHeaderStyle. I just changed the Background of the DataGridHeaderBorder to Transparent to avoid an annoying border that shows up when you resize the window and the horizontal bar appears.


The complete example is available here.

Tuesday, 18 December 2012

Compile gedit from git repository and jhbuild on ubuntu 12.10 64 bit

Here are some tips that helped me to compile gedit on ubuntu 12.10 64 bit.

I wanted to use jhbuild that allows to compile gnome related software (among others)  in separate folders than system ones.

First I read this manual and executed the suggested operations:
http://developer.gnome.org/jhbuild/stable/getting-started.html.en

Then I tried to compile gedit with the command

jhbuild buildone gedit 

but configure complained that these packets were not been installed:

No package 'libxml-2.0' found
No package 'glib-2.0' found
No package 'gio-2.0' found
No package 'gtk+-3.0' found
No package 'gtksourceview-3.0' found


actually they were already installed but jhedit couldn't find them  because I didn't set the PKG_CONFIG_PATH environment variable.

In the PKG_CONFIG_PATH I set the paths where are installed the libraries searched by jhbuild.

To know the name of the package to install or, if it is already installed, to know the path to add to the PKG_CONFIG_PATH variable you can run the command

apt-file search missingfilename.pc 

where missingfilename comes from the configure message error. As an example:

apt-file search gtksourceview-3.0.pc returns
libgtksourceview-3.0-dev: /usr/lib/x86_64-linux-gnu/pkgconfig/gtksourceview-3.0.pc
which means that the package that must be present is libgtksourceview-3.0-dev and that, actually, it is already installed at the path  /usr/lib/x86_64-linux-gnu/pkgconfig/.

At the end I had to set the PKG_CONFIG_PATH variable in the following way:
PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig/:/usr/lib/pkgconfig/:/usr/share/pkgconfig/
export PKG_CONFIG_PATH  


After this I was able to run without errors the command jhbuild buildone gedit.


To run the gedit just compiled from the repository instead of the system one you can run:
 jhbuild run gedit. 

Tuesday, 28 February 2012

Read values from Iskraemeco Mt830 and Mt831 from Rs 485

I had to read values from  Iskraemeco Mt830 and Mt831 meters from the rs-485 interface provide by the MK-f38-3 module.

The protocol used is IEC 62056-21 mode C.

Here you can download a simple (and not optimal) Winform application useful to test the meter:
https://sites.google.com/site/walterslog/WinFormIskraMt831.zip?attredirects=0&d=1

Just insert the name of the rs-485 COM port (COM5 e.g.), the serial number of the meter, click 'Get data' and wait approximately 60 seconds.

Let's see the commands I used to get the data. Pay attention to the Thread.Sleep commands, they are used to send commands at the right time. The milliseconds I used are a mix from what I learnt from the IEC standard and what I found working well for this kind of meters.

First open the COM Port:

SerialPort Port = new SerialPort("COM5", 9600, Parity.Even, 7, StopBits.One);
Port.Open();

Then send the "wake-up" sequence, eight NULL chars, then wait 1600 milliseconds:

String NUL = Convert.ToChar(0x0).ToString();

for (int i = 0; i < 8; i++)
            {
                Port.Write(NUL);
                Thread.Sleep(50);
            }        


Thread.Sleep(1600);  


Send a command containg the Serial Number of the meter, then wait:

String SerialNumber = "56251230";
Port.Write("/?" +  SerialNumber   + "!\r\n");

Thread.Sleep(1600);

After this command the meter will reply with a message containing the model of the meter.

Send the command that will activate the "Readout mode":

String ACK = Convert.ToChar(0x06).ToString();
Port.Write(ACK + "050\r\n");


The two zeros in the "050" sequence set Readout mode, the five means the new baud rate (9600 bps) at which the COM Port will operate. With the rs-485 interface of the Iskraemeco meter the communication starts and ends at 9600 bps  but with the optical probe it starts with 300 bps and switches to 9600 bps.

Then the meter should reply with all the obis codes with their values in less than a minute.
You can read the data with this code (the "!" char indicates to the receiving unit where the reply ends) :

 String results = "";


 DateTime EndTime = DateTime.Now.AddSeconds(60);
       
 while (DateTime.Now <= EndTime && results.Contains("!")== false)
  {
        if (Port.BytesToRead == 0) continue;


        byte[] ResponseFrame = new byte[Port.BytesToRead];
                   
         Port.Read(ResponseFrame, 0, ResponseFrame.Length);


         results += FrameToString(ResponseFrame);                   
  }       

Then close the COM port:

Port.Close();

Surely you will notice that this code is awful :-)
Its only purpose is to show the right commands and timing and it's not suitable for production code.





Friday, 18 November 2011

Process.WaitForExit (Int32) hangs problem

I recently encountered this problem while using a method I use to call commands from the shell via the cmd.exe process.

The code looks like almost this:



code 1:
   1 using System;
   2 using System.Collections.Generic;
   3 using System.Linq;
   4 using System.Text;
   5 using System.Diagnostics;
   6 
   7 namespace TestWaitForExit
   8 {
   9     class Program
  10     {
  11         static int Main(string[] args)
  12         {
  13             CommandResult Result = ExecuteShellCommandSync(@"ping -t 127.0.0.1", 1000);
  14 
  15             return 0;
  16         }
  17 
  18         public static CommandResult ExecuteShellCommandSync(String CommandString, int Timeout)
  19         {
  20             CommandResult ToReturn = new CommandResult();
  21 
  22             Process process = new Process();
  23         
  24             System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
  25                     
  26             startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
  27                     
  28             startInfo.UseShellExecute = false;
  29             startInfo.RedirectStandardError = true;
  30             startInfo.RedirectStandardOutput = true;
  31         
  32             startInfo.FileName = "cmd.exe";
  33         
  34             String MyCommand = @"/C " + CommandString;
  35        
  36             startInfo.Arguments = MyCommand;
  37 
  38             process.StartInfo = startInfo;
  39         
  40             process.Start();
  41 
  42             String Output = "";
  43             String Error = "";
  44             try
  45             {            
  46                 Output = process.StandardOutput.ReadToEnd();
  47                 Error = process.StandardError.ReadToEnd();
  48             }
  49             catch
  50             {
  51                 Output = "Can't read stdout";
  52                 Error = "Can't read stderr";
  53             }
  54 
  55             process.WaitForExit(Timeout);       
  56                     
  57             int Exit = 0;
  58 
  59             if (process.HasExited == true)
  60             {
  61                 ToReturn.ProcessNotExited = false;
  62                 Exit = process.ExitCode;
  63             }
  64             else
  65             {
  66                 ToReturn.ProcessNotExited = true;
  67             }
  68         
  69             process.Dispose();
  70 
  71             ToReturn.ReturnedCode = Exit;
  72             ToReturn.OutputString = Output;
  73             ToReturn.ErrorString = Error;
  74 
  75             return ToReturn;        
  76         }
  77     }
  78 
  79     public class CommandResult
  80     {
  81         public int ReturnedCode { get; set; }
  82         public String OutputString { get; set; }
  83         public String ErrorString { get; set; }
  84         public Boolean ProcessNotExited { get; set; }
  85     }
  86 }

As you can see, the code starts a "cmd.exe" process and passes to it the command I want to be executed.


I redirect StandardError and StandarOutput in order to read them from the code.
The code reads them before the process.WaitForExit(Timeout) call as recommended by Microsoft (more on this later).
The problem arises if the command I send to "cmd.exe" never terminates or hangs indefinitely.
In the code I used the command ping -t 8.8.8.8 which, because of the -t option, pings the host without stopping. What happens? The "cmd.exe" process along with the ping -t command never exits and never closes the stdout stream and so our code hangs at the Output = process.StandardOutput.ReadToEnd(); line because it can't succeed reading all the stream.


The same happens also if a command in a batch file hangs for any reason and so the above code could work continuously for years and then hang suddenly without any apparent reason.

Before I wrote that it's recommended to read redirected streams before the process.WaitForExit(Timeout) call, well this is especially true if you use the WaitForExit signature without the Timeout.

If you call process.WaitForExit() before reading the redirected streams:

code 2:
   1 process.Start();
   2 process.WaitForExit();  
   3 
   4 String Output = "";
   5 String Error = "";
   6 
   7 try
   8 {                
   9 Output = process.StandardOutput.ReadToEnd();
  10 Error = process.StandardError.ReadToEnd();
  11 }
  12 catch
  13 {
  14 Output = "Can't read stdout";
  15 Error = "Can't read stderr";
  16 }

 you can experience a deadlock if  the command you attach to "cmd.exe" or the process you are calling fills the standard output or standard error. This because our code can't reach the lines
Output = process.StandardOutput.ReadToEnd();
     Error = process.StandardError.ReadToEnd();.
As a matter of fact the child process (the ping command or a batch file or whatever process you are executing) can't go on if our program doesn't read the filled buffers of the streams and this can't happen because the code is hanging at the line with process.WaitForExit() which will wait forever for the child project to exit.


The default size of both streams is 4096 bytes. You can test this two sizes with these batch files:

code 3:
   1 @ECHO OFF
   2 for /l %%X in (1,1,409) do ( echo|set /p=0123456789)
   3 echo 0123

code 4:
   1 @ECHO OFF
   2 for /l %%X in (1,1,409) do (echo|set /p=0123456789)  1>&2
   3 (echo 0123) 1>&2

The first script writes 4096 bytes to standard output and the second to standard error.

Save one of these into "C:\testbuffsize.bat" and run our program calling process.WaitForExit() before 
Output = process.StandardOutput.ReadToEnd();
     Error = process.StandardError.ReadToEnd();
as in code 2, you can do it writing
CommandResult Result = ExecuteShellCommandSync(@"c:\testbuffsize.bat", 1000);
at line 13 of code 1.

 The code won't hang but if you write one more byte in any of the two streams it will overflow the buffer size making the program hang.

If you need to redirect and read the standard output or standar error the best solution is to read them asynchronously. An excellent way to do this is proposed by Mark Byers in this stackoverflow thread

As the last thing please notice that if the child process exits only because you use the process.WaitForExit(Timeout) signature and it actually goes in timeout you should kill the "cmd.exe" process and its possible children.