diff --git a/ProgramQueuer.sln b/ProgramQueuer.sln
new file mode 100755
index 0000000..469107f
--- /dev/null
+++ b/ProgramQueuer.sln
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProgramQueuer", "ProgramQueuer\ProgramQueuer.csproj", "{B1825A4D-32CB-45E2-9CFE-77622DCC1641}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x86 = Debug|x86
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B1825A4D-32CB-45E2-9CFE-77622DCC1641}.Debug|x86.ActiveCfg = Debug|x86
+ {B1825A4D-32CB-45E2-9CFE-77622DCC1641}.Debug|x86.Build.0 = Debug|x86
+ {B1825A4D-32CB-45E2-9CFE-77622DCC1641}.Release|x86.ActiveCfg = Release|x86
+ {B1825A4D-32CB-45E2-9CFE-77622DCC1641}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/ProgramQueuer/App.xaml b/ProgramQueuer/App.xaml
new file mode 100755
index 0000000..1aeb5f9
--- /dev/null
+++ b/ProgramQueuer/App.xaml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/ProgramQueuer/App.xaml.cs b/ProgramQueuer/App.xaml.cs
new file mode 100755
index 0000000..29aa2a3
--- /dev/null
+++ b/ProgramQueuer/App.xaml.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Linq;
+using System.Windows;
+
+namespace ProgramQueuer
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App : Application
+ {
+ }
+}
diff --git a/ProgramQueuer/Helpers/BoolToVisibility.cs b/ProgramQueuer/Helpers/BoolToVisibility.cs
new file mode 100755
index 0000000..48e02e6
--- /dev/null
+++ b/ProgramQueuer/Helpers/BoolToVisibility.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Windows;
+using System.Windows.Interop;
+using System.Windows.Data;
+using System.Windows.Media.Imaging;
+
+namespace ProgramQueuer.Helpers
+{
+ ///
+ /// Simple boolean value converter that converts it to a visbility value where true is visible and false is collapsed.
+ ///
+ [ValueConversion(typeof(bool), typeof(Visibility))]
+ public class BoolToVisibility : IValueConverter
+ {
+ ///
+ /// Convert from a boolean value of true to Visibility.Visible and false to Visibility.Collapsed.
+ ///
+ /// A boolean value to convert.
+ /// The type of the target value. This property is ignored.
+ /// Parameter to pass to the converter. This property is ignored.
+ /// A reference to the target CultureInfo. This property is ignored.
+ /// A Visibility value of either Visible or Collapsed depending on the value parameter.
+ public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ bool visible = (bool)value;
+ if (visible)
+ return Visibility.Visible;
+ return Visibility.Collapsed;
+ }
+
+ ///
+ /// Convert back from a Visibility value to a boolean value where Visibility.Visible converts to true and Visibility.Collapsed to false.
+ ///
+ /// The visibility value to convert back to a boolean value.
+ /// The type of the target value. This property is ignored.
+ /// Parameter to pass to the converter. This property is ignored.
+ /// A reference to the target CultureInfo. This property is ignored.
+ /// A boolean value representing the visibility of the control.
+ public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ var visible = (Visibility)value;
+ if (visible == Visibility.Collapsed)
+ return false;
+ return true;
+ }
+ }
+}
diff --git a/ProgramQueuer/Helpers/BooleanInverter.cs b/ProgramQueuer/Helpers/BooleanInverter.cs
new file mode 100755
index 0000000..25d1789
--- /dev/null
+++ b/ProgramQueuer/Helpers/BooleanInverter.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Windows;
+using System.Windows.Interop;
+using System.Windows.Data;
+using System.Windows.Media.Imaging;
+
+namespace ProgramQueuer.Helpers
+{
+ ///
+ /// A simple boolean converter that inverts the boolean value.
+ ///
+ [ValueConversion(typeof(bool), typeof(bool))]
+ public class BooleanInverter : IValueConverter
+ {
+ ///
+ /// Convert a boolean value to the inverted value. Seriously, if I have to explain this, then you are in the wrong business.
+ ///
+ /// The boolean value to invert.
+ /// The type of the target value. This property is ignored.
+ /// Parameter to pass to the converter. This property is ignored.
+ /// A reference to the target CultureInfo. This property is ignored.
+ /// An inverted boolean value.
+ public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ return !(bool)value;
+ }
+
+ ///
+ /// Convert an inverted boolean value to it's original value. Basically inverted.
+ ///
+ /// The boolean value to invert back.
+ /// The type of the target value. This property is ignored.
+ /// Parameter to pass to the converter. This property is ignored.
+ /// A reference to the target CultureInfo. This property is ignored.
+ /// An inverted boolean value.
+ public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ return !(bool)value;
+ }
+ }
+}
diff --git a/ProgramQueuer/Helpers/DragAdorner.cs b/ProgramQueuer/Helpers/DragAdorner.cs
new file mode 100755
index 0000000..53d7990
--- /dev/null
+++ b/ProgramQueuer/Helpers/DragAdorner.cs
@@ -0,0 +1,175 @@
+// Copyright (C) Josh Smith - January 2007
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Windows.Documents;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Shapes;
+using System.Windows.Media.Animation;
+using System.Windows.Controls;
+
+namespace WPF.JoshSmith.Adorners
+{
+ ///
+ /// Renders a visual which can follow the mouse cursor,
+ /// such as during a drag-and-drop operation.
+ ///
+ public class DragAdorner : Adorner
+ {
+ #region Data
+
+ private Rectangle child = null;
+ private double offsetLeft = 0;
+ private double offsetTop = 0;
+
+ #endregion // Data
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of DragVisualAdorner.
+ ///
+ /// The element being adorned.
+ /// The size of the adorner.
+ /// A brush to with which to paint the adorner.
+ public DragAdorner(UIElement adornedElement, Size size, Brush brush)
+ : base(adornedElement)
+ {
+ Rectangle rect = new Rectangle();
+ rect.Fill = brush;
+ rect.Width = size.Width;
+ rect.Height = size.Height;
+ rect.IsHitTestVisible = false;
+ this.child = rect;
+ }
+
+ #endregion // Constructor
+
+ #region Public Interface
+
+ #region GetDesiredTransform
+
+ ///
+ /// Override.
+ ///
+ ///
+ ///
+ public override GeneralTransform GetDesiredTransform(GeneralTransform transform)
+ {
+ GeneralTransformGroup result = new GeneralTransformGroup();
+ result.Children.Add(base.GetDesiredTransform(transform));
+ result.Children.Add(new TranslateTransform(this.offsetLeft, this.offsetTop));
+ return result;
+ }
+
+ #endregion // GetDesiredTransform
+
+ #region OffsetLeft
+
+ ///
+ /// Gets/sets the horizontal offset of the adorner.
+ ///
+ public double OffsetLeft
+ {
+ get { return this.offsetLeft; }
+ set
+ {
+ this.offsetLeft = value;
+ UpdateLocation();
+ }
+ }
+
+ #endregion // OffsetLeft
+
+ #region SetOffsets
+
+ ///
+ /// Updates the location of the adorner in one atomic operation.
+ ///
+ ///
+ ///
+ public void SetOffsets(double left, double top)
+ {
+ this.offsetLeft = left;
+ this.offsetTop = top;
+ this.UpdateLocation();
+ }
+
+ #endregion // SetOffsets
+
+ #region OffsetTop
+
+ ///
+ /// Gets/sets the vertical offset of the adorner.
+ ///
+ public double OffsetTop
+ {
+ get { return this.offsetTop; }
+ set
+ {
+ this.offsetTop = value;
+ UpdateLocation();
+ }
+ }
+
+ #endregion // OffsetTop
+
+ #endregion // Public Interface
+
+ #region Protected Overrides
+
+ ///
+ /// Override.
+ ///
+ ///
+ ///
+ protected override Size MeasureOverride(Size constraint)
+ {
+ this.child.Measure(constraint);
+ return this.child.DesiredSize;
+ }
+
+ ///
+ /// Override.
+ ///
+ ///
+ ///
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ this.child.Arrange(new Rect(finalSize));
+ return finalSize;
+ }
+
+ ///
+ /// Override.
+ ///
+ ///
+ ///
+ protected override Visual GetVisualChild(int index)
+ {
+ return this.child;
+ }
+
+ ///
+ /// Override. Always returns 1.
+ ///
+ protected override int VisualChildrenCount
+ {
+ get { return 1; }
+ }
+
+ #endregion // Protected Overrides
+
+ #region Private Helpers
+
+ private void UpdateLocation()
+ {
+ AdornerLayer adornerLayer = this.Parent as AdornerLayer;
+ if (adornerLayer != null)
+ adornerLayer.Update(this.AdornedElement);
+ }
+
+ #endregion // Private Helpers
+ }
+}
\ No newline at end of file
diff --git a/ProgramQueuer/Helpers/ListViewDragDropManager.cs b/ProgramQueuer/Helpers/ListViewDragDropManager.cs
new file mode 100755
index 0000000..11c7743
--- /dev/null
+++ b/ProgramQueuer/Helpers/ListViewDragDropManager.cs
@@ -0,0 +1,864 @@
+// Copyright (C) Josh Smith - January 2007
+using System;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Documents;
+using System.Windows.Media;
+using System.Windows.Input;
+using WPF.JoshSmith.Adorners;
+using WPF.JoshSmith.Controls.Utilities;
+
+namespace WPF.JoshSmith.ServiceProviders.UI
+{
+ #region ListViewDragDropManager
+
+ ///
+ /// Manages the dragging and dropping of ListViewItems in a ListView.
+ /// The ItemType type parameter indicates the type of the objects in
+ /// the ListView's items source. The ListView's ItemsSource must be
+ /// set to an instance of ObservableCollection of ItemType, or an
+ /// Exception will be thrown.
+ ///
+ /// The type of the ListView's items.
+ public class ListViewDragDropManager where ItemType : class
+ {
+ #region Data
+
+ bool canInitiateDrag;
+ DragAdorner dragAdorner;
+ double dragAdornerOpacity;
+ int indexToSelect;
+ bool isDragInProgress;
+ ItemType itemUnderDragCursor;
+ ListView listView;
+ Point ptMouseDown;
+ bool showDragAdorner;
+
+ #endregion // Data
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of ListViewDragManager.
+ ///
+ public ListViewDragDropManager()
+ {
+ this.canInitiateDrag = false;
+ this.dragAdornerOpacity = 0.7;
+ this.indexToSelect = -1;
+ this.showDragAdorner = true;
+ }
+
+ ///
+ /// Initializes a new instance of ListViewDragManager.
+ ///
+ ///
+ public ListViewDragDropManager(ListView listView)
+ : this()
+ {
+ this.ListView = listView;
+ }
+
+ ///
+ /// Initializes a new instance of ListViewDragManager.
+ ///
+ ///
+ ///
+ public ListViewDragDropManager(ListView listView, double dragAdornerOpacity)
+ : this(listView)
+ {
+ this.DragAdornerOpacity = dragAdornerOpacity;
+ }
+
+ ///
+ /// Initializes a new instance of ListViewDragManager.
+ ///
+ ///
+ ///
+ public ListViewDragDropManager(ListView listView, bool showDragAdorner)
+ : this(listView)
+ {
+ this.ShowDragAdorner = showDragAdorner;
+ }
+
+ #endregion // Constructors
+
+ #region Public Interface
+
+ #region DragAdornerOpacity
+
+ ///
+ /// Gets/sets the opacity of the drag adorner. This property has no
+ /// effect if ShowDragAdorner is false. The default value is 0.7
+ ///
+ public double DragAdornerOpacity
+ {
+ get { return this.dragAdornerOpacity; }
+ set
+ {
+ if (this.IsDragInProgress)
+ throw new InvalidOperationException("Cannot set the DragAdornerOpacity property during a drag operation.");
+
+ if (value < 0.0 || value > 1.0)
+ throw new ArgumentOutOfRangeException("DragAdornerOpacity", value, "Must be between 0 and 1.");
+
+ this.dragAdornerOpacity = value;
+ }
+ }
+
+ #endregion // DragAdornerOpacity
+
+ #region IsDragInProgress
+
+ ///
+ /// Returns true if there is currently a drag operation being managed.
+ ///
+ public bool IsDragInProgress
+ {
+ get { return this.isDragInProgress; }
+ private set { this.isDragInProgress = value; }
+ }
+
+ #endregion // IsDragInProgress
+
+ #region ListView
+
+ ///
+ /// Gets/sets the ListView whose dragging is managed. This property
+ /// can be set to null, to prevent drag management from occuring. If
+ /// the ListView's AllowDrop property is false, it will be set to true.
+ ///
+ public ListView ListView
+ {
+ get { return listView; }
+ set
+ {
+ if (this.IsDragInProgress)
+ throw new InvalidOperationException("Cannot set the ListView property during a drag operation.");
+
+ if (this.listView != null)
+ {
+ #region Unhook Events
+
+ this.listView.PreviewMouseLeftButtonDown -= listView_PreviewMouseLeftButtonDown;
+ this.listView.PreviewMouseMove -= listView_PreviewMouseMove;
+ this.listView.DragOver -= listView_DragOver;
+ this.listView.DragLeave -= listView_DragLeave;
+ this.listView.DragEnter -= listView_DragEnter;
+ this.listView.Drop -= listView_Drop;
+
+ #endregion // Unhook Events
+ }
+
+ this.listView = value;
+
+ if (this.listView != null)
+ {
+ if (!this.listView.AllowDrop)
+ this.listView.AllowDrop = true;
+
+ #region Hook Events
+
+ this.listView.PreviewMouseLeftButtonDown += listView_PreviewMouseLeftButtonDown;
+ this.listView.PreviewMouseMove += listView_PreviewMouseMove;
+ this.listView.DragOver += listView_DragOver;
+ this.listView.DragLeave += listView_DragLeave;
+ this.listView.DragEnter += listView_DragEnter;
+ this.listView.Drop += listView_Drop;
+
+ #endregion // Hook Events
+ }
+ }
+ }
+
+ #endregion // ListView
+
+ #region ProcessDrop [event]
+
+ ///
+ /// Raised when a drop occurs. By default the dropped item will be moved
+ /// to the target index. Handle this event if relocating the dropped item
+ /// requires custom behavior. Note, if this event is handled the default
+ /// item dropping logic will not occur.
+ ///
+ public event EventHandler> ProcessDrop;
+
+ #endregion // ProcessDrop [event]
+
+ #region ShowDragAdorner
+
+ ///
+ /// Gets/sets whether a visual representation of the ListViewItem being dragged
+ /// follows the mouse cursor during a drag operation. The default value is true.
+ ///
+ public bool ShowDragAdorner
+ {
+ get { return this.showDragAdorner; }
+ set
+ {
+ if (this.IsDragInProgress)
+ throw new InvalidOperationException("Cannot set the ShowDragAdorner property during a drag operation.");
+
+ this.showDragAdorner = value;
+ }
+ }
+
+ #endregion // ShowDragAdorner
+
+ #endregion // Public Interface
+
+ #region Event Handling Methods
+
+ #region listView_PreviewMouseLeftButtonDown
+
+ void listView_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+ {
+ if (this.IsMouseOverScrollbar)
+ {
+ // 4/13/2007 - Set the flag to false when cursor is over scrollbar.
+ this.canInitiateDrag = false;
+ return;
+ }
+
+ int index = this.IndexUnderDragCursor;
+ this.canInitiateDrag = index > -1;
+
+ if (this.canInitiateDrag)
+ {
+ // Remember the location and index of the ListViewItem the user clicked on for later.
+ this.ptMouseDown = MouseUtilities.GetMousePosition(this.listView);
+ this.indexToSelect = index;
+ }
+ else
+ {
+ this.ptMouseDown = new Point(-10000, -10000);
+ this.indexToSelect = -1;
+ }
+ }
+
+ #endregion // listView_PreviewMouseLeftButtonDown
+
+ #region listView_PreviewMouseMove
+
+ void listView_PreviewMouseMove(object sender, MouseEventArgs e)
+ {
+ if (!this.CanStartDragOperation)
+ return;
+
+ // Select the item the user clicked on.
+ if (this.listView.SelectedIndex != this.indexToSelect)
+ this.listView.SelectedIndex = this.indexToSelect;
+
+ // If the item at the selected index is null, there's nothing
+ // we can do, so just return;
+ if (this.listView.SelectedItem == null)
+ return;
+
+ ListViewItem itemToDrag = this.GetListViewItem(this.listView.SelectedIndex);
+ if (itemToDrag == null)
+ return;
+
+ AdornerLayer adornerLayer = this.ShowDragAdornerResolved ? this.InitializeAdornerLayer(itemToDrag) : null;
+
+ this.InitializeDragOperation(itemToDrag);
+ this.PerformDragOperation();
+ this.FinishDragOperation(itemToDrag, adornerLayer);
+ }
+
+ #endregion // listView_PreviewMouseMove
+
+ #region listView_DragOver
+
+ void listView_DragOver(object sender, DragEventArgs e)
+ {
+ e.Effects = DragDropEffects.Move;
+
+ if (this.ShowDragAdornerResolved)
+ this.UpdateDragAdornerLocation();
+
+ // Update the item which is known to be currently under the drag cursor.
+ int index = this.IndexUnderDragCursor;
+ this.ItemUnderDragCursor = index < 0 ? null : this.ListView.Items[index] as ItemType;
+ }
+
+ #endregion // listView_DragOver
+
+ #region listView_DragLeave
+
+ void listView_DragLeave(object sender, DragEventArgs e)
+ {
+ if (!this.IsMouseOver(this.listView))
+ {
+ if (this.ItemUnderDragCursor != null)
+ this.ItemUnderDragCursor = null;
+
+ if (this.dragAdorner != null)
+ this.dragAdorner.Visibility = Visibility.Collapsed;
+ }
+ }
+
+ #endregion // listView_DragLeave
+
+ #region listView_DragEnter
+
+ void listView_DragEnter(object sender, DragEventArgs e)
+ {
+ if (this.dragAdorner != null && this.dragAdorner.Visibility != Visibility.Visible)
+ {
+ // Update the location of the adorner and then show it.
+ this.UpdateDragAdornerLocation();
+ this.dragAdorner.Visibility = Visibility.Visible;
+ }
+ }
+
+ #endregion // listView_DragEnter
+
+ #region listView_Drop
+
+ void listView_Drop(object sender, DragEventArgs e)
+ {
+ if (this.ItemUnderDragCursor != null)
+ this.ItemUnderDragCursor = null;
+
+ e.Effects = DragDropEffects.None;
+
+ if (!e.Data.GetDataPresent(typeof(ItemType)))
+ return;
+
+ // Get the data object which was dropped.
+ ItemType data = e.Data.GetData(typeof(ItemType)) as ItemType;
+ if (data == null)
+ return;
+
+ // Get the ObservableCollection which contains the dropped data object.
+ ObservableCollection itemsSource = this.listView.ItemsSource as ObservableCollection;
+ if (itemsSource == null)
+ throw new Exception(
+ "A ListView managed by ListViewDragManager must have its ItemsSource set to an ObservableCollection.");
+
+ int oldIndex = itemsSource.IndexOf(data);
+ int newIndex = this.IndexUnderDragCursor;
+
+ if (newIndex < 0)
+ {
+ // The drag started somewhere else, and our ListView is empty
+ // so make the new item the first in the list.
+ if (itemsSource.Count == 0)
+ newIndex = 0;
+
+ // The drag started somewhere else, but our ListView has items
+ // so make the new item the last in the list.
+ else if (oldIndex < 0)
+ newIndex = itemsSource.Count;
+
+ // The user is trying to drop an item from our ListView into
+ // our ListView, but the mouse is not over an item, so don't
+ // let them drop it.
+ else
+ return;
+ }
+
+ // Dropping an item back onto itself is not considered an actual 'drop'.
+ if (oldIndex == newIndex)
+ return;
+
+ if (this.ProcessDrop != null)
+ {
+ // Let the client code process the drop.
+ ProcessDropEventArgs args = new ProcessDropEventArgs(itemsSource, data, oldIndex, newIndex, e.AllowedEffects);
+ this.ProcessDrop(this, args);
+ e.Effects = args.Effects;
+ }
+ else
+ {
+ // Move the dragged data object from it's original index to the
+ // new index (according to where the mouse cursor is). If it was
+ // not previously in the ListBox, then insert the item.
+ if (oldIndex > -1)
+ itemsSource.Move(oldIndex, newIndex);
+ else
+ itemsSource.Insert(newIndex, data);
+
+ // Set the Effects property so that the call to DoDragDrop will return 'Move'.
+ e.Effects = DragDropEffects.Move;
+ }
+ }
+
+ #endregion // listView_Drop
+
+ #endregion // Event Handling Methods
+
+ #region Private Helpers
+
+ #region CanStartDragOperation
+
+ bool CanStartDragOperation
+ {
+ get
+ {
+ if (Mouse.LeftButton != MouseButtonState.Pressed)
+ return false;
+
+ if (!this.canInitiateDrag)
+ return false;
+
+ if (this.indexToSelect == -1)
+ return false;
+
+ if (!this.HasCursorLeftDragThreshold)
+ return false;
+
+ return true;
+ }
+ }
+
+ #endregion // CanStartDragOperation
+
+ #region FinishDragOperation
+
+ void FinishDragOperation(ListViewItem draggedItem, AdornerLayer adornerLayer)
+ {
+ // Let the ListViewItem know that it is not being dragged anymore.
+ ListViewItemDragState.SetIsBeingDragged(draggedItem, false);
+
+ this.IsDragInProgress = false;
+
+ if (this.ItemUnderDragCursor != null)
+ this.ItemUnderDragCursor = null;
+
+ // Remove the drag adorner from the adorner layer.
+ if (adornerLayer != null)
+ {
+ adornerLayer.Remove(this.dragAdorner);
+ this.dragAdorner = null;
+ }
+ }
+
+ #endregion // FinishDragOperation
+
+ #region GetListViewItem
+
+ ListViewItem GetListViewItem(int index)
+ {
+ if (this.listView.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
+ return null;
+
+ return this.listView.ItemContainerGenerator.ContainerFromIndex(index) as ListViewItem;
+ }
+
+ ListViewItem GetListViewItem(ItemType dataItem)
+ {
+ if (this.listView.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
+ return null;
+
+ return this.listView.ItemContainerGenerator.ContainerFromItem(dataItem) as ListViewItem;
+ }
+
+ #endregion // GetListViewItem
+
+ #region HasCursorLeftDragThreshold
+
+ bool HasCursorLeftDragThreshold
+ {
+ get
+ {
+ if (this.indexToSelect < 0)
+ return false;
+
+ ListViewItem item = this.GetListViewItem(this.indexToSelect);
+ Rect bounds = VisualTreeHelper.GetDescendantBounds(item);
+ Point ptInItem = this.listView.TranslatePoint(this.ptMouseDown, item);
+
+ // In case the cursor is at the very top or bottom of the ListViewItem
+ // we want to make the vertical threshold very small so that dragging
+ // over an adjacent item does not select it.
+ double topOffset = Math.Abs(ptInItem.Y);
+ double btmOffset = Math.Abs(bounds.Height - ptInItem.Y);
+ double vertOffset = Math.Min(topOffset, btmOffset);
+
+ double width = SystemParameters.MinimumHorizontalDragDistance * 2;
+ double height = Math.Min(SystemParameters.MinimumVerticalDragDistance, vertOffset) * 2;
+ Size szThreshold = new Size(width, height);
+
+ Rect rect = new Rect(this.ptMouseDown, szThreshold);
+ rect.Offset(szThreshold.Width / -2, szThreshold.Height / -2);
+ Point ptInListView = MouseUtilities.GetMousePosition(this.listView);
+ return !rect.Contains(ptInListView);
+ }
+ }
+
+ #endregion // HasCursorLeftDragThreshold
+
+ #region IndexUnderDragCursor
+
+ ///
+ /// Returns the index of the ListViewItem underneath the
+ /// drag cursor, or -1 if the cursor is not over an item.
+ ///
+ int IndexUnderDragCursor
+ {
+ get
+ {
+ int index = -1;
+ for (int i = 0; i < this.listView.Items.Count; ++i)
+ {
+ ListViewItem item = this.GetListViewItem(i);
+ if (this.IsMouseOver(item))
+ {
+ index = i;
+ break;
+ }
+ }
+ return index;
+ }
+ }
+
+ #endregion // IndexUnderDragCursor
+
+ #region InitializeAdornerLayer
+
+ AdornerLayer InitializeAdornerLayer(ListViewItem itemToDrag)
+ {
+ // Create a brush which will paint the ListViewItem onto
+ // a visual in the adorner layer.
+ VisualBrush brush = new VisualBrush(itemToDrag);
+
+ // Create an element which displays the source item while it is dragged.
+ this.dragAdorner = new DragAdorner(this.listView, itemToDrag.RenderSize, brush);
+
+ // Set the drag adorner's opacity.
+ this.dragAdorner.Opacity = this.DragAdornerOpacity;
+
+ AdornerLayer layer = AdornerLayer.GetAdornerLayer(this.listView);
+ layer.Add(dragAdorner);
+
+ // Save the location of the cursor when the left mouse button was pressed.
+ this.ptMouseDown = MouseUtilities.GetMousePosition(this.listView);
+
+ return layer;
+ }
+
+ #endregion // InitializeAdornerLayer
+
+ #region InitializeDragOperation
+
+ void InitializeDragOperation(ListViewItem itemToDrag)
+ {
+ // Set some flags used during the drag operation.
+ this.IsDragInProgress = true;
+ this.canInitiateDrag = false;
+
+ // Let the ListViewItem know that it is being dragged.
+ ListViewItemDragState.SetIsBeingDragged(itemToDrag, true);
+ }
+
+ #endregion // InitializeDragOperation
+
+ #region IsMouseOver
+
+ bool IsMouseOver(Visual target)
+ {
+ // We need to use MouseUtilities to figure out the cursor
+ // coordinates because, during a drag-drop operation, the WPF
+ // mechanisms for getting the coordinates behave strangely.
+
+ Rect bounds = VisualTreeHelper.GetDescendantBounds(target);
+ Point mousePos = MouseUtilities.GetMousePosition(target);
+ return bounds.Contains(mousePos);
+ }
+
+ #endregion // IsMouseOver
+
+ #region IsMouseOverScrollbar
+
+ ///
+ /// Returns true if the mouse cursor is over a scrollbar in the ListView.
+ ///
+ bool IsMouseOverScrollbar
+ {
+ get
+ {
+ Point ptMouse = MouseUtilities.GetMousePosition(this.listView);
+ HitTestResult res = VisualTreeHelper.HitTest(this.listView, ptMouse);
+ if (res == null)
+ return false;
+
+ DependencyObject depObj = res.VisualHit;
+ while (depObj != null)
+ {
+ if (depObj is ScrollBar)
+ return true;
+
+ // VisualTreeHelper works with objects of type Visual or Visual3D.
+ // If the current object is not derived from Visual or Visual3D,
+ // then use the LogicalTreeHelper to find the parent element.
+ if (depObj is Visual || depObj is System.Windows.Media.Media3D.Visual3D)
+ depObj = VisualTreeHelper.GetParent(depObj);
+ else
+ depObj = LogicalTreeHelper.GetParent(depObj);
+ }
+
+ return false;
+ }
+ }
+
+ #endregion // IsMouseOverScrollbar
+
+ #region ItemUnderDragCursor
+
+ ItemType ItemUnderDragCursor
+ {
+ get { return this.itemUnderDragCursor; }
+ set
+ {
+ if (this.itemUnderDragCursor == value)
+ return;
+
+ // The first pass handles the previous item under the cursor.
+ // The second pass handles the new one.
+ for (int i = 0; i < 2; ++i)
+ {
+ if (i == 1)
+ this.itemUnderDragCursor = value;
+
+ if (this.itemUnderDragCursor != null)
+ {
+ ListViewItem listViewItem = this.GetListViewItem(this.itemUnderDragCursor);
+ if (listViewItem != null)
+ ListViewItemDragState.SetIsUnderDragCursor(listViewItem, i == 1);
+ }
+ }
+ }
+ }
+
+ #endregion // ItemUnderDragCursor
+
+ #region PerformDragOperation
+
+ void PerformDragOperation()
+ {
+ ItemType selectedItem = this.listView.SelectedItem as ItemType;
+ DragDropEffects allowedEffects = DragDropEffects.Move | DragDropEffects.Move | DragDropEffects.Link;
+ if (DragDrop.DoDragDrop(this.listView, selectedItem, allowedEffects) != DragDropEffects.None)
+ {
+ // The item was dropped into a new location,
+ // so make it the new selected item.
+ this.listView.SelectedItem = selectedItem;
+ }
+ }
+
+ #endregion // PerformDragOperation
+
+ #region ShowDragAdornerResolved
+
+ bool ShowDragAdornerResolved
+ {
+ get { return this.ShowDragAdorner && this.DragAdornerOpacity > 0.0; }
+ }
+
+ #endregion // ShowDragAdornerResolved
+
+ #region UpdateDragAdornerLocation
+
+ void UpdateDragAdornerLocation()
+ {
+ if (this.dragAdorner != null)
+ {
+ Point ptCursor = MouseUtilities.GetMousePosition(this.ListView);
+
+ double left = ptCursor.X - this.ptMouseDown.X;
+
+ // 4/13/2007 - Made the top offset relative to the item being dragged.
+ ListViewItem itemBeingDragged = this.GetListViewItem(this.indexToSelect);
+ Point itemLoc = itemBeingDragged.TranslatePoint(new Point(0, 0), this.ListView);
+ double top = itemLoc.Y + ptCursor.Y - this.ptMouseDown.Y;
+
+ this.dragAdorner.SetOffsets(left, top);
+ }
+ }
+
+ #endregion // UpdateDragAdornerLocation
+
+ #endregion // Private Helpers
+ }
+
+ #endregion // ListViewDragDropManager
+
+ #region ListViewItemDragState
+
+ ///
+ /// Exposes attached properties used in conjunction with the ListViewDragDropManager class.
+ /// Those properties can be used to allow triggers to modify the appearance of ListViewItems
+ /// in a ListView during a drag-drop operation.
+ ///
+ public static class ListViewItemDragState
+ {
+ #region IsBeingDragged
+
+ ///
+ /// Identifies the ListViewItemDragState's IsBeingDragged attached property.
+ /// This field is read-only.
+ ///
+ public static readonly DependencyProperty IsBeingDraggedProperty =
+ DependencyProperty.RegisterAttached(
+ "IsBeingDragged",
+ typeof(bool),
+ typeof(ListViewItemDragState),
+ new UIPropertyMetadata(false));
+
+ ///
+ /// Returns true if the specified ListViewItem is being dragged, else false.
+ ///
+ /// The ListViewItem to check.
+ public static bool GetIsBeingDragged(ListViewItem item)
+ {
+ return (bool)item.GetValue(IsBeingDraggedProperty);
+ }
+
+ ///
+ /// Sets the IsBeingDragged attached property for the specified ListViewItem.
+ ///
+ /// The ListViewItem to set the property on.
+ /// Pass true if the element is being dragged, else false.
+ internal static void SetIsBeingDragged(ListViewItem item, bool value)
+ {
+ item.SetValue(IsBeingDraggedProperty, value);
+ }
+
+ #endregion // IsBeingDragged
+
+ #region IsUnderDragCursor
+
+ ///
+ /// Identifies the ListViewItemDragState's IsUnderDragCursor attached property.
+ /// This field is read-only.
+ ///
+ public static readonly DependencyProperty IsUnderDragCursorProperty =
+ DependencyProperty.RegisterAttached(
+ "IsUnderDragCursor",
+ typeof(bool),
+ typeof(ListViewItemDragState),
+ new UIPropertyMetadata(false));
+
+ ///
+ /// Returns true if the specified ListViewItem is currently underneath the cursor
+ /// during a drag-drop operation, else false.
+ ///
+ /// The ListViewItem to check.
+ public static bool GetIsUnderDragCursor(ListViewItem item)
+ {
+ return (bool)item.GetValue(IsUnderDragCursorProperty);
+ }
+
+ ///
+ /// Sets the IsUnderDragCursor attached property for the specified ListViewItem.
+ ///
+ /// The ListViewItem to set the property on.
+ /// Pass true if the element is underneath the drag cursor, else false.
+ internal static void SetIsUnderDragCursor(ListViewItem item, bool value)
+ {
+ item.SetValue(IsUnderDragCursorProperty, value);
+ }
+
+ #endregion // IsUnderDragCursor
+ }
+
+ #endregion // ListViewItemDragState
+
+ #region ProcessDropEventArgs
+
+ ///
+ /// Event arguments used by the ListViewDragDropManager.ProcessDrop event.
+ ///
+ /// The type of data object being dropped.
+ public class ProcessDropEventArgs : EventArgs where ItemType : class
+ {
+ #region Data
+
+ ObservableCollection itemsSource;
+ ItemType dataItem;
+ int oldIndex;
+ int newIndex;
+ DragDropEffects allowedEffects = DragDropEffects.None;
+ DragDropEffects effects = DragDropEffects.None;
+
+ #endregion // Data
+
+ #region Constructor
+
+ internal ProcessDropEventArgs(
+ ObservableCollection itemsSource,
+ ItemType dataItem,
+ int oldIndex,
+ int newIndex,
+ DragDropEffects allowedEffects)
+ {
+ this.itemsSource = itemsSource;
+ this.dataItem = dataItem;
+ this.oldIndex = oldIndex;
+ this.newIndex = newIndex;
+ this.allowedEffects = allowedEffects;
+ }
+
+ #endregion // Constructor
+
+ #region Public Properties
+
+ ///
+ /// The items source of the ListView where the drop occurred.
+ ///
+ public ObservableCollection ItemsSource
+ {
+ get { return this.itemsSource; }
+ }
+
+ ///
+ /// The data object which was dropped.
+ ///
+ public ItemType DataItem
+ {
+ get { return this.dataItem; }
+ }
+
+ ///
+ /// The current index of the data item being dropped, in the ItemsSource collection.
+ ///
+ public int OldIndex
+ {
+ get { return this.oldIndex; }
+ }
+
+ ///
+ /// The target index of the data item being dropped, in the ItemsSource collection.
+ ///
+ public int NewIndex
+ {
+ get { return this.newIndex; }
+ }
+
+ ///
+ /// The drag drop effects allowed to be performed.
+ ///
+ public DragDropEffects AllowedEffects
+ {
+ get { return allowedEffects; }
+ }
+
+ ///
+ /// The drag drop effect(s) performed on the dropped item.
+ ///
+ public DragDropEffects Effects
+ {
+ get { return effects; }
+ set { effects = value; }
+ }
+
+ #endregion // Public Properties
+ }
+
+ #endregion // ProcessDropEventArgs
+}
\ No newline at end of file
diff --git a/ProgramQueuer/Helpers/MouseUtilities.cs b/ProgramQueuer/Helpers/MouseUtilities.cs
new file mode 100755
index 0000000..5e5655e
--- /dev/null
+++ b/ProgramQueuer/Helpers/MouseUtilities.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Runtime.InteropServices;
+using System.Windows;
+using System.Windows.Media;
+
+namespace WPF.JoshSmith.Controls.Utilities
+{
+ ///
+ /// Provides access to the mouse location by calling unmanaged code.
+ ///
+ ///
+ /// This class was written by Dan Crevier (Microsoft).
+ /// http://blogs.msdn.com/llobo/archive/2006/09/06/Scrolling-Scrollviewer-on-Mouse-Drag-at-the-boundaries.aspx
+ ///
+ public class MouseUtilities
+ {
+ [StructLayout(LayoutKind.Sequential)]
+ private struct Win32Point
+ {
+ public Int32 X;
+ public Int32 Y;
+ };
+
+ [DllImport("user32.dll")]
+ private static extern bool GetCursorPos(ref Win32Point pt);
+
+ [DllImport("user32.dll")]
+ private static extern bool ScreenToClient(IntPtr hwnd, ref Win32Point pt);
+
+ ///
+ /// Returns the mouse cursor location. This method is necessary during
+ /// a drag-drop operation because the WPF mechanisms for retrieving the
+ /// cursor coordinates are unreliable.
+ ///
+ /// The Visual to which the mouse coordinates will be relative.
+ public static Point GetMousePosition(Visual relativeTo)
+ {
+ Win32Point mouse = new Win32Point();
+ GetCursorPos(ref mouse);
+
+ // Using PointFromScreen instead of Dan Crevier's code (commented out below)
+ // is a bug fix created by William J. Roberts. Read his comments about the fix
+ // here: http://www.codeproject.com/useritems/ListViewDragDropManager.asp?msg=1911611#xx1911611xx
+ return relativeTo.PointFromScreen(new Point((double)mouse.X, (double)mouse.Y));
+
+ #region Commented Out
+ //System.Windows.Interop.HwndSource presentationSource =
+ // (System.Windows.Interop.HwndSource)PresentationSource.FromVisual( relativeTo );
+ //ScreenToClient( presentationSource.Handle, ref mouse );
+ //GeneralTransform transform = relativeTo.TransformToAncestor( presentationSource.RootVisual );
+ //Point offset = transform.Transform( new Point( 0, 0 ) );
+ //return new Point( mouse.X - offset.X, mouse.Y - offset.Y );
+ #endregion // Commented Out
+ }
+ }
+}
\ No newline at end of file
diff --git a/ProgramQueuer/Helpers/ValueConverterGroup.cs b/ProgramQueuer/Helpers/ValueConverterGroup.cs
new file mode 100755
index 0000000..16bdc26
--- /dev/null
+++ b/ProgramQueuer/Helpers/ValueConverterGroup.cs
@@ -0,0 +1,177 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Globalization;
+using System.Reflection;
+using System.Text;
+using System.Windows.Data;
+
+namespace ProgramQueuer.Helpers
+{
+ ///
+ /// A value converter which contains a list of IValueConverters and invokes their Convert or ConvertBack methods
+ /// in the order that they exist in the list. The output of one converter is piped into the next converter
+ /// allowing for modular value converters to be chained together. If the ConvertBack method is invoked, the
+ /// value converters are executed in reverse order (highest to lowest index). Do not leave an element in the
+ /// Converters property collection null, every element must reference a valid IValueConverter instance. If a
+ /// value converter's type is not decorated with the ValueConversionAttribute, an InvalidOperationException will be
+ /// thrown when the converter is added to the Converters collection.
+ ///
+ [System.Windows.Markup.ContentProperty("Converters")]
+ public class ValueConverterGroup : IValueConverter
+ {
+ #region Data
+
+ private readonly ObservableCollection converters = new ObservableCollection();
+ private readonly Dictionary cachedAttributes = new Dictionary();
+
+ #endregion // Data
+
+ #region Constructor
+
+ public ValueConverterGroup()
+ {
+ this.converters.CollectionChanged += this.OnConvertersCollectionChanged;
+ }
+
+ #endregion // Constructor
+
+ #region Converters
+
+ ///
+ /// Returns the list of IValueConverters contained in this converter.
+ ///
+ public ObservableCollection Converters
+ {
+ get { return this.converters; }
+ }
+
+ #endregion // Converters
+
+ #region IValueConverter Members
+
+ object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ object output = value;
+
+ for (int i = 0; i < this.Converters.Count; i++)
+ {
+ //Run the converter.
+ output = this.Converters[i].Convert(output, this.GetTargetType(i, targetType, true), parameter, culture);
+ if (output == Binding.DoNothing)
+ break;
+ }
+
+ return output;
+ }
+
+ object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ object output = value;
+
+ for (int i = this.Converters.Count - 1; i > -1; i--)
+ {
+ //Run the converter.
+ output = this.Converters[i].Convert(output, this.GetTargetType(i, targetType, true), parameter, culture);
+ if (output == Binding.DoNothing)
+ break;
+ }
+
+ return output;
+ }
+
+ #endregion // IValueConverter Members
+
+ #region Private Helpers
+
+ #region GetTargetType
+
+ ///
+ /// Returns the target type for a conversion operation.
+ ///
+ /// The index of the current converter about to be executed.
+ /// The 'targetType' argument passed into the conversion method.
+ /// Pass true if calling from the Convert method, or false if calling from ConvertBack.
+ protected virtual Type GetTargetType(int converterIndex, Type finalTargetType, bool convert)
+ {
+ // If the current converter is not the last/first in the list,
+ // get a reference to the next/previous converter.
+ IValueConverter nextConverter = null;
+ int nextIndex = converterIndex;
+
+ //Grab the index for the next converter.
+ if (convert && nextIndex < this.Converters.Count - 1)
+ nextIndex++;
+ else if (!convert && nextIndex > 0)
+ nextIndex--;
+
+ //Check to see if we have a new index.
+ if (nextIndex != converterIndex)
+ //Make sure the converter at our new index is not null.
+ if (this.Converters[nextIndex] == null)
+ throw new InvalidOperationException("The Converters collection of the ValueConverterGroup contains a null reference at index: " + nextIndex);
+ else
+ //Get our next converter.
+ nextConverter = this.Converters[nextIndex];
+
+ //Check if we have to go through another converter. If this (current) is the last converter then this will be null.
+ if (nextConverter != null)
+ {
+ var conversionAttribute = cachedAttributes[nextConverter];
+
+ // If the Convert method is going to be called, we need to use the SourceType of the next
+ // converter in the list. If ConvertBack is called, use the TargetType.
+ return convert ? conversionAttribute.SourceType : conversionAttribute.TargetType;
+ }
+
+ // If the current converter is the last one to be executed return the target type passed into the conversion method.
+ return finalTargetType;
+ }
+
+ #endregion // GetTargetType
+
+ #region OnConvertersCollectionChanged
+
+ void OnConvertersCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ // The 'Converters' collection has been modified, so validate that each value converter it now
+ // contains is decorated with ValueConversionAttribute and then cache the attribute value.
+
+ IList convertersToProcess = null;
+ if (e.Action == NotifyCollectionChangedAction.Add ||
+ e.Action == NotifyCollectionChangedAction.Replace)
+ {
+ convertersToProcess = e.NewItems;
+ }
+ else if (e.Action == NotifyCollectionChangedAction.Remove)
+ {
+ foreach (IValueConverter converter in e.OldItems)
+ this.cachedAttributes.Remove(converter);
+ }
+ else if (e.Action == NotifyCollectionChangedAction.Reset)
+ {
+ this.cachedAttributes.Clear();
+ convertersToProcess = this.converters;
+ }
+
+ if (convertersToProcess != null && convertersToProcess.Count > 0)
+ {
+ foreach (IValueConverter converter in convertersToProcess)
+ {
+ object[] attributes = converter.GetType().GetCustomAttributes(typeof(ValueConversionAttribute), false);
+
+ if (attributes.Length != 1)
+ throw new InvalidOperationException("All value converters added to a ValueConverterGroup must be decorated with the ValueConversionAttribute attribute exactly once.");
+
+ this.cachedAttributes.Add(converter, attributes[0] as ValueConversionAttribute);
+ }
+ }
+ }
+
+ #endregion // OnConvertersCollectionChanged
+
+ #endregion // Private Helpers
+ }
+}
diff --git a/ProgramQueuer/Helpers/ValueConverters.cs b/ProgramQueuer/Helpers/ValueConverters.cs
new file mode 100755
index 0000000..43665f9
--- /dev/null
+++ b/ProgramQueuer/Helpers/ValueConverters.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Windows.Data;
+using System.Windows;
+
+namespace ProgramQueuer.Helpers
+{
+ [ValueConversion(typeof(bool), typeof(Visibility))]
+ public class BoolToVisibililty : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ bool visible = (bool)value;
+ if (visible)
+ return Visibility.Visible;
+ return Visibility.Hidden;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ Visibility visible = (Visibility)value;
+ if (visible == Visibility.Hidden)
+ return false;
+ return true;
+ }
+ }
+
+ [ValueConversion(typeof(bool), typeof(bool))]
+ public class InvertBool : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ return !(bool)value;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ return !(bool)value;
+ }
+ }
+}
diff --git a/ProgramQueuer/MainWindow.xaml b/ProgramQueuer/MainWindow.xaml
new file mode 100755
index 0000000..14b5701
--- /dev/null
+++ b/ProgramQueuer/MainWindow.xaml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ProgramQueuer/MainWindow.xaml.cs b/ProgramQueuer/MainWindow.xaml.cs
new file mode 100755
index 0000000..9ba69c0
--- /dev/null
+++ b/ProgramQueuer/MainWindow.xaml.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+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;
+using ProgramQueuer.Queuer;
+using WPF.JoshSmith.ServiceProviders.UI;
+
+namespace ProgramQueuer
+{
+ ///
+ /// Interaction logic for MainWindow.xaml
+ ///
+ public partial class MainWindow : Window
+ {
+ EntryManager _manager;
+
+ public MainWindow()
+ {
+ InitializeComponent();
+
+ _manager = new EntryManager();
+ this.DataContext = _manager;
+ this.listPrograms.ItemsSource = _manager.QueueList;
+ }
+
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ new ListViewDragDropManager(this.listPrograms);
+ }
+
+ private void GridSplitter_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
+ {
+ ProgramQueuer.Properties.Settings.Default.split_height = (int)((sender as GridSplitter).Parent as Grid).RowDefinitions[2].Height.Value;
+ }
+
+ private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
+ {
+ if (_manager.Working)
+ if (MessageBox.Show("Are you sure you want to stop current worker and batch and exit application?", "You sure you want to exit?", MessageBoxButton.YesNo) == MessageBoxResult.No)
+ {
+ e.Cancel = true;
+ return;
+ }
+ ProgramQueuer.Properties.Settings.Default.Save();
+ }
+
+ private void ButtonExit_Click(object sender, RoutedEventArgs e)
+ {
+ this.Close();
+ }
+
+ private void ButtonWork_Click(object sender, RoutedEventArgs e)
+ {
+ _manager.RunQueuer();
+ }
+
+ private void ButtonStop_Click(object sender, RoutedEventArgs e)
+ {
+ if (MessageBox.Show("Are you sure you want to stop current batch?", "Stop worker", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
+ {
+ _manager.ForceStop();
+ }
+ }
+
+ private void ListView_DragEnter(object sender, DragEventArgs e)
+ {
+ if (e.Data.GetDataPresent(DataFormats.FileDrop))
+ e.Effects = DragDropEffects.Link;
+ else
+ e.Effects = DragDropEffects.None;
+ }
+
+ private void ListView_Drop(object sender, DragEventArgs e)
+ {
+ if (e.Data.GetDataPresent(DataFormats.FileDrop))
+ {
+ string[] files = (string[])e.Data.GetData(DataFormats.FileDrop, false);
+ foreach (string file in files)
+ {
+ _manager.QueueList.Add(new ProgramEntry { Name = file , Status = "Queued"});
+ }
+ }
+ }
+
+ private void buttonRemove_Click(object sender, RoutedEventArgs e)
+ {
+ _manager.QueueList.Remove((sender as Control).DataContext as ProgramEntry);
+ }
+
+ private void buttonStopCurrent_Click(object sender, RoutedEventArgs e)
+ {
+ if (MessageBox.Show("Are you sure you want to stop current running process and continue to next?", "Stop current worker?", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
+ {
+ _manager.ForceStopCurrent();
+ }
+ }
+
+ private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ textboxStatus.CaretIndex = textboxStatus.Text.Length;
+ textboxStatus.ScrollToEnd();
+ }
+
+ private void buttonAdd_Click(object sender, RoutedEventArgs e)
+ {
+
+ }
+ }
+}
diff --git a/ProgramQueuer/ProgramQueuer.csproj b/ProgramQueuer/ProgramQueuer.csproj
new file mode 100755
index 0000000..f0a3638
--- /dev/null
+++ b/ProgramQueuer/ProgramQueuer.csproj
@@ -0,0 +1,142 @@
+
+
+
+ Debug
+ x86
+ 8.0.30703
+ 2.0
+ {B1825A4D-32CB-45E2-9CFE-77622DCC1641}
+ WinExe
+ Properties
+ ProgramQueuer
+ ProgramQueuer
+ v3.5
+ 512
+ {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 4
+
+
+ x86
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ x86
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+ reports.ico
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+ Settings.xaml
+
+
+ MSBuild:Compile
+ Designer
+
+
+ App.xaml
+ Code
+
+
+ MainWindow.xaml
+ Code
+
+
+ Designer
+ MSBuild:Compile
+
+
+
+
+ Code
+
+
+ True
+ True
+ Resources.resx
+
+
+ True
+ Settings.settings
+ True
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ProgramQueuer/Properties/AssemblyInfo.cs b/ProgramQueuer/Properties/AssemblyInfo.cs
new file mode 100755
index 0000000..c18c9f5
--- /dev/null
+++ b/ProgramQueuer/Properties/AssemblyInfo.cs
@@ -0,0 +1,55 @@
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ProgramQueuer")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("ProgramQueuer")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2012")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+//In order to begin building localizable applications, set
+//CultureYouAreCodingWith in your .csproj file
+//inside a . For example, if you are using US english
+//in your source files, set the to en-US. Then uncomment
+//the NeutralResourceLanguage attribute below. Update the "en-US" in
+//the line below to match the UICulture setting in the project file.
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
+
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/ProgramQueuer/Properties/Resources.Designer.cs b/ProgramQueuer/Properties/Resources.Designer.cs
new file mode 100755
index 0000000..88d729b
--- /dev/null
+++ b/ProgramQueuer/Properties/Resources.Designer.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.1
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace ProgramQueuer.Properties
+{
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources
+ {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources()
+ {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if ((resourceMan == null))
+ {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ProgramQueuer.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
+ return resourceCulture;
+ }
+ set
+ {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/ProgramQueuer/Properties/Resources.resx b/ProgramQueuer/Properties/Resources.resx
new file mode 100755
index 0000000..ffecec8
--- /dev/null
+++ b/ProgramQueuer/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/ProgramQueuer/Properties/Settings.Designer.cs b/ProgramQueuer/Properties/Settings.Designer.cs
new file mode 100755
index 0000000..cb7f332
--- /dev/null
+++ b/ProgramQueuer/Properties/Settings.Designer.cs
@@ -0,0 +1,98 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.1
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace ProgramQueuer.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("500")]
+ public double width {
+ get {
+ return ((double)(this["width"]));
+ }
+ set {
+ this["width"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("500")]
+ public double height {
+ get {
+ return ((double)(this["height"]));
+ }
+ set {
+ this["height"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("200")]
+ public double split_height {
+ get {
+ return ((double)(this["split_height"]));
+ }
+ set {
+ this["split_height"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("300")]
+ public double columnWidth1 {
+ get {
+ return ((double)(this["columnWidth1"]));
+ }
+ set {
+ this["columnWidth1"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("50")]
+ public double columnWidth2 {
+ get {
+ return ((double)(this["columnWidth2"]));
+ }
+ set {
+ this["columnWidth2"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("200")]
+ public double columnWidth3 {
+ get {
+ return ((double)(this["columnWidth3"]));
+ }
+ set {
+ this["columnWidth3"] = value;
+ }
+ }
+ }
+}
diff --git a/ProgramQueuer/Properties/Settings.settings b/ProgramQueuer/Properties/Settings.settings
new file mode 100755
index 0000000..35c7044
--- /dev/null
+++ b/ProgramQueuer/Properties/Settings.settings
@@ -0,0 +1,24 @@
+
+
+
+
+
+ 500
+
+
+ 500
+
+
+ 200
+
+
+ 300
+
+
+ 50
+
+
+ 200
+
+
+
\ No newline at end of file
diff --git a/ProgramQueuer/Queuer/EntryManager.cs b/ProgramQueuer/Queuer/EntryManager.cs
new file mode 100755
index 0000000..4cada2e
--- /dev/null
+++ b/ProgramQueuer/Queuer/EntryManager.cs
@@ -0,0 +1,175 @@
+using System;
+using System.Diagnostics;
+using System.ComponentModel;
+using System.Collections.ObjectModel;
+using System.Collections.Generic;
+using System.Linq;
+using System.IO;
+using System.Text;
+
+namespace ProgramQueuer.Queuer
+{
+ public class EntryManager : INotifyPropertyChanged
+ {
+ bool _working;
+ bool _clearNext;
+ string _buffer;
+ ProgramEntry _currentEntry;
+ Process _currentProcess;
+ ProcessIOManager _processManager;
+
+ public EntryManager()
+ {
+ QueueList = new ObservableCollection();
+ _buffer = "";
+ _currentEntry = null;
+ _processManager = new ProcessIOManager();
+ _processManager.StderrTextRead += new StringReadEventHandler(_processManager_StdoutTextRead);
+ _processManager.StdoutTextRead += new StringReadEventHandler(_processManager_StdoutTextRead);
+ _currentProcess = new Process();
+ _currentProcess.StartInfo.UseShellExecute = false;
+ _currentProcess.StartInfo.RedirectStandardOutput = true;
+ _currentProcess.StartInfo.RedirectStandardError = true;
+ _currentProcess.StartInfo.RedirectStandardInput = true;
+ _currentProcess.StartInfo.CreateNoWindow = true;
+ _currentProcess.EnableRaisingEvents = true;
+ _currentProcess.Exited += new EventHandler(_currentProcess_Exited);
+ }
+
+ public ObservableCollection QueueList;
+ public event PropertyChangedEventHandler PropertyChanged = delegate { };
+
+ public ProgramEntry CurrentEntry
+ {
+ get { return _currentEntry; }
+ set
+ {
+ _currentEntry = value;
+ PropertyChanged(this, new PropertyChangedEventArgs("CurrentEntry"));
+ }
+ }
+
+ public bool Working
+ {
+ get { return _working; }
+ set
+ {
+ _working = value;
+ PropertyChanged(this, new PropertyChangedEventArgs("Working"));
+ }
+ }
+
+ public void RunQueuer()
+ {
+ if (QueueList.Count > 0)
+ {
+ Working = true;
+ RunProgram(QueueList[0]);
+ }
+ }
+
+ public void ForceStopCurrent()
+ {
+ _currentProcess.Kill();
+ }
+
+ public void ForceStop()
+ {
+ this.Working = false;
+ _currentProcess.Kill();
+ }
+
+ private void RunProgram(ProgramEntry entry)
+ {
+ try
+ {
+ _currentEntry = entry;
+ _currentEntry.Output = "";
+ _currentEntry.Working = true;
+ _currentEntry.Status = "Starting";
+ _currentProcess.StartInfo.FileName = _currentEntry.Name;
+ _currentProcess.StartInfo.WorkingDirectory = new FileInfo(_currentEntry.Name).DirectoryName;
+ _currentProcess.Start();
+ _processManager.RunningProcess = _currentProcess;
+ _processManager.StartProcessOutputRead();
+ }
+ catch (Exception e)
+ {
+ _currentEntry.Working = false;
+ _currentEntry.Finished = false;
+ _currentEntry.Output += string.Format("Error while starting {0}:\n\n{1}", _currentEntry.Name, e.ToString());
+ if (RunNext())
+ {
+ _currentEntry = null;
+ this.Working = false;
+ }
+ }
+ }
+
+ private bool RunNext()
+ {
+ _currentEntry.Status = "Finished";
+ if (QueueList.IndexOf(_currentEntry) >= 0 && QueueList.IndexOf(_currentEntry) < QueueList.Count - 1)
+ RunProgram(QueueList[QueueList.IndexOf(_currentEntry) + 1]);
+ else
+ return true;
+ return false;
+ }
+
+ void _currentProcess_Exited(object sender, EventArgs e)
+ {
+ _processManager.StopMonitoringProcessOutput();
+ _currentEntry.Working = false;
+ _currentEntry.Finished = true;
+ if (!this.Working)
+ return;
+ if (RunNext())
+ {
+ _currentEntry = null;
+ this.Working = false;
+ }
+ }
+
+ void _processManager_StdoutTextRead(string text)
+ {
+ string[] lines = text.Split('\r');
+ if (!text.EndsWith("\r") && !text.EndsWith("\n") && _clearNext)
+ {
+ _buffer += text;
+ return;
+ }
+ else
+ {
+ text = _buffer + text;
+ _buffer = "";
+ }
+
+
+ if (_clearNext && text == "\n")
+ _clearNext = false;
+
+ while (text.IndexOf('\b') >= 0)
+ {
+ if (_currentEntry.Output.Length > 0 && _currentEntry.Output[_currentEntry.Output.Length - 1] != '\n')
+ _currentEntry.Output = _currentEntry.Output.Remove(_currentEntry.Output.Length - 1);
+ text = text.Remove(text.IndexOf('\b'), 1);
+ }
+
+ if (_clearNext && text.Replace("\n", "").Replace("\r", "").Trim() != "")
+ if (_currentEntry.Output.LastIndexOf('\n') < _currentEntry.Output.Length - 1)
+ _currentEntry.Output = _currentEntry.Output.Remove(_currentEntry.Output.LastIndexOf('\n') + 1) + text;
+ else
+ _currentEntry.Output += text;
+ else
+ _currentEntry.Output += text;
+
+ if (text.Replace("\n", "").Trim() != "")
+ _currentEntry.Status = text.Replace("\n", "").Replace("\r", "");
+
+ if (lines.Length == 2 && lines[1] == "")
+ _clearNext = true;
+ else
+ _clearNext = false;
+ }
+ }
+}
diff --git a/ProgramQueuer/Queuer/ProcessIOManager.cs b/ProgramQueuer/Queuer/ProcessIOManager.cs
new file mode 100755
index 0000000..cabd5a8
--- /dev/null
+++ b/ProgramQueuer/Queuer/ProcessIOManager.cs
@@ -0,0 +1,342 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Text;
+using System.Threading;
+
+
+/***************************************************************************************
+* Author: Curt C.
+* Email : harpyeaglecp@aol.com
+*
+* Project File: "Solving Problems of Monitoring Standard Output and Error Streams Of A Running Process"
+* for http://www.codeproject.com
+*
+* This software is released under the Code Project Open License (CPOL)
+*
+* See official license description at: http://www.codeproject.com/info/cpol10.aspx
+*
+* This software is provided AS-IS without any warranty of any kind.
+*
+* >>> Please leave this header intact when using this file in other projects <<<
+***************************************************************************************/
+
+namespace ProgramQueuer.Queuer
+{
+ // Delegate definition used for events to receive notification
+ // of text read from stdout or stderr of a running process.
+ public delegate void StringReadEventHandler(string text);
+
+ ///
+ /// Class that manages the reading of the output produced by a given 'Process'
+ /// and reports the output via events.
+ /// Both standard error (stderr) and standard output (stdout) are
+ /// managed and reported.
+ /// The stdout and stderr monitoring and reading are each performed
+ /// by separate background threads. Each thread blocks on a Read()
+ /// method, waiting for text in the stream being monitored to become available.
+ ///
+ /// Note the Process.RedirectStandardOutput must be set to true in order
+ /// to read standard output from the process, and the Process.RedirectStandardError
+ /// must be set to true to read standard error.
+ ///
+ public class ProcessIOManager
+ {
+ #region Private_Fields
+ // Command line process that is executing and being
+ // monitored by this class for stdout/stderr output.
+ private Process runningProcess;
+
+ // Thread to monitor and read standard output (stdout)
+ private Thread stdoutThread;
+
+ // Thread to monitor and read standard error (stderr)
+ private Thread stderrThread;
+
+ // Buffer to hold characters read from either stdout, or stderr streams
+ private StringBuilder streambuffer;
+ #endregion
+
+ #region Public_Properties_And_Events
+ ///
+ /// Gets the process being monitored
+ ///
+ /// The running process.
+ public Process RunningProcess
+ {
+ get { return runningProcess; }
+ set { runningProcess = value; }
+ }
+
+ // Event to notify of a string read from stdout stream
+ public event StringReadEventHandler StdoutTextRead;
+
+ // Event to notify of a string read from stderr stream
+ public event StringReadEventHandler StderrTextRead;
+
+ #endregion
+
+ #region Constructor_And_Initialization
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public ProcessIOManager()
+ {
+ this.streambuffer = new StringBuilder(256);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The process.
+ ///
+ /// Does not automatically start listening for stdout/stderr.
+ /// Call StartProcessOutputRead() to begin listening for process output.
+ ///
+ ///
+ public ProcessIOManager(Process process)
+ {
+ if (process == null)
+ throw new Exception("ProcessIoManager unable to set running process - null value is supplied");
+
+ if (process.HasExited == true)
+ throw new Exception("ProcessIoManager unable to set running process - the process has already existed.");
+
+ this.runningProcess = process;
+ this.streambuffer = new StringBuilder(256);
+ }
+
+ #endregion
+
+ #region Public_Methods
+ ///
+ /// Starts the background threads reading any output produced (standard output, standard error)
+ /// that is produces by the running process.
+ ///
+ public void StartProcessOutputRead()
+ {
+ // Just to make sure there aren't previous threads running.
+ StopMonitoringProcessOutput();
+
+ // Make sure we have a valid, running process
+ CheckForValidProcess("Unable to start monitoring process output.", true);
+
+ // If the stdout is redirected for the process, then start
+ // the stdout thread, which will manage the reading of stdout from the
+ // running process, and report text read via supplied events.
+ if (runningProcess.StartInfo.RedirectStandardOutput == true)
+ {
+ stdoutThread = new Thread(new ThreadStart(ReadStandardOutputThreadMethod));
+ // Make thread a background thread - if it was foreground, then
+ // the thread could hang up the process from exiting. Background
+ // threads will be forced to stop on main thread termination.
+ stdoutThread.IsBackground = true;
+ stdoutThread.Start();
+ }
+
+ // If the stderr is redirected for the process, then start
+ // the stderr thread, which will manage the reading of stderr from the
+ // running process, and report text read via supplied events.
+ if (runningProcess.StartInfo.RedirectStandardError == true)
+ {
+ stderrThread = new Thread(new ThreadStart(ReadStandardErrorThreadMethod));
+ stderrThread.IsBackground = true;
+ stderrThread.Start();
+ }
+ }
+
+ ///
+ /// Writes the supplied text string to the standard input (stdin) of the running process
+ ///
+ /// In order to be able to write to the Process, the StartInfo.RedirectStandardInput must be set to true.
+ /// The text to write to running process input stream.
+ public void WriteStdin(string text)
+ {
+ // Make sure we have a valid, running process
+ CheckForValidProcess("Unable to write to process standard input.", true);
+ if (runningProcess.StartInfo.RedirectStandardInput == true)
+ runningProcess.StandardInput.WriteLine(text);
+ }
+ #endregion
+
+ #region Private_Methods
+ ///
+ /// Checks for valid (non-null Process), and optionally check to see if the process has exited.
+ /// Throws Exception if process is null, or if process has existed and checkForHasExited is true.
+ ///
+ /// The error message text to display if an exception is thrown.
+ /// if set to true [check if process has exited].
+ private void CheckForValidProcess(string errorMessageText, bool checkForHasExited)
+ {
+ errorMessageText = (errorMessageText == null ? "" : errorMessageText.Trim());
+ if (runningProcess == null)
+ throw new Exception(errorMessageText + " (Running process must be available)");
+
+ if (checkForHasExited && runningProcess.HasExited)
+ throw new Exception(errorMessageText + " (Process has exited)");
+ }
+
+ ///
+ /// Read characters from the supplied stream, and accumulate them in the
+ /// 'streambuffer' variable until there are no more characters to read.
+ ///
+ /// The first character that has already been read.
+ /// The stream reader to read text from.
+ /// if set to true the stream is assumed to be standard output, otherwise assumed to be standard error.
+ private void ReadStream(int firstCharRead, StreamReader streamReader, bool isstdout)
+ {
+ // One of the streams (stdout, stderr) has characters ready to be written
+ // Flush the supplied stream until no more characters remain to be read.
+ // The synchronized/ locked section of code to prevent the other thread from
+ // reading its stream at the same time, producing intermixed stderr/stdout results.
+ // If the threads were not synchronized, the threads
+ // could read from both stream simultaneously, and jumble up the text with
+ // stderr and stdout text intermixed.
+ lock (this)
+ {
+ // Single character read from either stdout or stderr
+ int ch;
+ // Clear the stream buffer to hold the text to be read
+ streambuffer.Length = 0;
+
+ //Console.WriteLine("CHAR=" + firstCharRead);
+ streambuffer.Append((char)firstCharRead);
+
+ // While there are more characters to be read
+ while (streamReader.Peek() > -1)
+ {
+ // Read the character in the queue
+ ch = streamReader.Read();
+
+ if (ch != '\n' && streambuffer.Length > 0 && streambuffer[streambuffer.Length - 1] == '\r')
+ NotifyAndFlushBufferText(streambuffer, isstdout);
+
+ // Accumulate the characters read in the stream buffer
+ streambuffer.Append((char)ch);
+
+ // Send text one line at a time - much more efficient than
+ // one character at a time
+ if (ch == '\n')
+ NotifyAndFlushBufferText(streambuffer, isstdout);
+ }
+ // Flush any remaining text in the buffer
+ NotifyAndFlushBufferText(streambuffer, isstdout);
+ } // End lock()
+ }
+
+ ///
+ /// Invokes the OnStdoutTextRead (if isstdout==true)/ OnStderrTextRead events
+ /// with the supplied streambuilder 'textbuffer', then clears
+ /// textbuffer after event is invoked.
+ ///
+ /// The textbuffer containing the text string to pass to events.
+ /// if set to true, the stdout event is invoked, otherwise stedrr event is invoked.
+ private void NotifyAndFlushBufferText(StringBuilder textbuffer, bool isstdout)
+ {
+ if (textbuffer.Length > 0)
+ {
+ try
+ {
+ if (isstdout == true && StdoutTextRead != null)
+ { // Send notificatin of text read from stdout
+ StdoutTextRead(textbuffer.ToString());
+ }
+ else if (isstdout == false && StderrTextRead != null)
+ { // Send notificatin of text read from stderr
+ StderrTextRead(textbuffer.ToString());
+ }
+ }
+ catch (Exception e)
+ {
+ }
+ // 'Clear' the text buffer
+ textbuffer.Length = 0;
+ }
+ }
+
+ ///
+ /// Method started in a background thread (stdoutThread) to manage the reading and reporting of
+ /// standard output text produced by the running process.
+ ///
+ private void ReadStandardOutputThreadMethod()
+ {
+ // Main entry point for thread - make sure the main entry method
+ // is surrounded with try catch, so an uncaught exception won't
+ // screw up the entire application
+ try
+ {
+ // character read from stdout
+ int ch;
+
+ // The Read() method will block until something is available
+ while (runningProcess != null && (ch = runningProcess.StandardOutput.Read()) > -1)
+ {
+ // a character has become available for reading
+ // block the other thread and process this stream's input.
+ ReadStream(ch, runningProcess.StandardOutput, true);
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("ProcessIoManager.ReadStandardOutputThreadMethod()- Exception caught:" +
+ ex.Message + "\nStack Trace:" + ex.StackTrace);
+ }
+ }
+
+ ///
+ /// Method started in a background thread (stderrThread) to manage the reading and reporting of
+ /// standard error text produced by the running process.
+ ///
+ private void ReadStandardErrorThreadMethod()
+ {
+ try
+ {
+ // Character read from stderr
+ int ch;
+ // The Read() method will block until something is available
+ while (runningProcess != null && (ch = runningProcess.StandardError.Read()) > -1)
+ ReadStream(ch, runningProcess.StandardError, false);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("ProcessIoManager.ReadStandardErrorThreadMethod()- Exception caught:" +
+ ex.Message + "\nStack Trace:" + ex.StackTrace);
+ }
+ }
+
+ ///
+ /// Stops both the standard input and stardard error background reader threads (via the Abort() method)
+ ///
+ public void StopMonitoringProcessOutput()
+ {
+ // Stop the stdout reader thread
+ try
+ {
+ if (stdoutThread != null)
+ stdoutThread.Abort();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("ProcessIoManager.StopReadThreads()-Exception caught on stopping stdout thread.\n" +
+ "Exception Message:\n" + ex.Message + "\nStack Trace:\n" + ex.StackTrace);
+ }
+
+ // Stop the stderr reader thread
+ try
+ {
+ if (stderrThread != null)
+ stderrThread.Abort();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("ProcessIoManager.StopReadThreads()-Exception caught on stopping stderr thread.\n" +
+ "Exception Message:\n" + ex.Message + "\nStack Trace:\n" + ex.StackTrace);
+ }
+ stdoutThread = null;
+ stderrThread = null;
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/ProgramQueuer/Queuer/ProgramEntry.cs b/ProgramQueuer/Queuer/ProgramEntry.cs
new file mode 100755
index 0000000..86752f7
--- /dev/null
+++ b/ProgramQueuer/Queuer/ProgramEntry.cs
@@ -0,0 +1,85 @@
+using System;
+using System.ComponentModel;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ProgramQueuer.Queuer
+{
+ public class ProgramEntry : INotifyPropertyChanged
+ {
+ bool _error;
+ bool _working;
+ bool _finished;
+ string _name;
+ string _output;
+ string _status;
+
+ public ProgramEntry()
+ {
+ Finished = false;
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged = delegate { };
+
+ public string Name
+ {
+ get { return _name; }
+ set
+ {
+ _name = value;
+ PropertyChanged(this, new PropertyChangedEventArgs("Name"));
+ }
+ }
+
+ public string Output
+ {
+ get { return _output; }
+ set
+ {
+ _output = value;
+ PropertyChanged(this, new PropertyChangedEventArgs("Output"));
+ }
+ }
+
+ public string Status
+ {
+ get { return _status; }
+ set
+ {
+ _status = value;
+ PropertyChanged(this, new PropertyChangedEventArgs("Status"));
+ }
+ }
+
+ public bool Finished
+ {
+ get { return _finished; }
+ set
+ {
+ _finished = value;
+ PropertyChanged(this, new PropertyChangedEventArgs("Finished"));
+ }
+ }
+
+ public bool Working
+ {
+ get { return _working; }
+ set
+ {
+ _working = value;
+ PropertyChanged(this, new PropertyChangedEventArgs("Working"));
+ }
+ }
+
+ public bool Error
+ {
+ get { return _error; }
+ set
+ {
+ _error = value;
+ PropertyChanged(this, new PropertyChangedEventArgs("Error"));
+ }
+ }
+ }
+}
diff --git a/ProgramQueuer/Resources/accept.png b/ProgramQueuer/Resources/accept.png
new file mode 100755
index 0000000..219bc71
Binary files /dev/null and b/ProgramQueuer/Resources/accept.png differ
diff --git a/ProgramQueuer/Resources/add.png b/ProgramQueuer/Resources/add.png
new file mode 100755
index 0000000..0a97508
Binary files /dev/null and b/ProgramQueuer/Resources/add.png differ
diff --git a/ProgramQueuer/Resources/application.png b/ProgramQueuer/Resources/application.png
new file mode 100755
index 0000000..e6ed513
Binary files /dev/null and b/ProgramQueuer/Resources/application.png differ
diff --git a/ProgramQueuer/Resources/play.png b/ProgramQueuer/Resources/play.png
new file mode 100755
index 0000000..c177242
Binary files /dev/null and b/ProgramQueuer/Resources/play.png differ
diff --git a/ProgramQueuer/Resources/remove.png b/ProgramQueuer/Resources/remove.png
new file mode 100755
index 0000000..7645cfa
Binary files /dev/null and b/ProgramQueuer/Resources/remove.png differ
diff --git a/ProgramQueuer/Resources/stop.png b/ProgramQueuer/Resources/stop.png
new file mode 100755
index 0000000..c0904aa
Binary files /dev/null and b/ProgramQueuer/Resources/stop.png differ
diff --git a/ProgramQueuer/Resources/warning.png b/ProgramQueuer/Resources/warning.png
new file mode 100755
index 0000000..77d3ff1
Binary files /dev/null and b/ProgramQueuer/Resources/warning.png differ
diff --git a/ProgramQueuer/Resources/window.png b/ProgramQueuer/Resources/window.png
new file mode 100755
index 0000000..494ccec
Binary files /dev/null and b/ProgramQueuer/Resources/window.png differ
diff --git a/ProgramQueuer/Settings.xaml b/ProgramQueuer/Settings.xaml
new file mode 100755
index 0000000..c4865de
--- /dev/null
+++ b/ProgramQueuer/Settings.xaml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
diff --git a/ProgramQueuer/Settings.xaml.cs b/ProgramQueuer/Settings.xaml.cs
new file mode 100755
index 0000000..3794ab3
--- /dev/null
+++ b/ProgramQueuer/Settings.xaml.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+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.Shapes;
+
+namespace ProgramQueuer
+{
+ ///
+ /// Interaction logic for Settings.xaml
+ ///
+ public partial class Settings : Window
+ {
+ public Settings()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/ProgramQueuer/app.config b/ProgramQueuer/app.config
new file mode 100755
index 0000000..b549024
--- /dev/null
+++ b/ProgramQueuer/app.config
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+ 500
+
+
+ 500
+
+
+ 200
+
+
+ 300
+
+
+ 50
+
+
+ 200
+
+
+
+
\ No newline at end of file
diff --git a/ProgramQueuer/reports.ico b/ProgramQueuer/reports.ico
new file mode 100755
index 0000000..7969ccb
Binary files /dev/null and b/ProgramQueuer/reports.ico differ