├── .gitignore
├── README.md
├── RELEASING
├── TODO
├── WINDOWS PATH.txt
├── WindowsPathEditor.sln
├── WindowsPathEditor
├── AnnotatedPathEntry.cs
├── App.xaml
├── App.xaml.cs
├── AutoCompleteBox.xaml
├── AutoCompleteBox.xaml.cs
├── DiffPath.cs
├── DiffWindow.xaml
├── DiffWindow.xaml.cs
├── DirectoryList.xaml
├── DirectoryList.xaml.cs
├── DragDropListBox
│ ├── DragDropHelper.cs
│ ├── DraggedAdorner.cs
│ ├── InsertionAdorner.cs
│ └── Utilities.cs
├── ExtensionMethods.cs
├── FolderBrowserDialogEx.cs
├── IReportProgress.cs
├── InvertBooleanConverter.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── ObservableCollectionEx.cs
├── PathChecker.cs
├── PathEntry.cs
├── PathMatch.cs
├── PathRegistry.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ └── Settings.settings
├── Resources
│ ├── accept.png
│ ├── add.png
│ ├── cross.png
│ ├── delete.png
│ ├── error.png
│ ├── folder_explore.png
│ └── view_text.ico
├── ScanningWindow.xaml
├── ScanningWindow.xaml.cs
├── SearchOperation.cs
├── SelectablePath.cs
├── UAC.cs
├── ValueConverterGroup.cs
├── WindowsPathEditor.csproj
└── packages.config
└── screenshot.png
/.gitignore:
--------------------------------------------------------------------------------
1 | Debug
2 | Release
3 | *.user
4 | *.suo
5 | *.sdf
6 | packages
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Windows Path Editor
2 | ===================
3 |
4 | This tool helps you manage your PATH on Windows.
5 |
6 | [Download Latest Version (1.7)](https://github.com/rix0rrr/WindowsPathEditor/releases/download/1.7/windowspatheditor-1.7.zip)
7 |
8 | Introduction
9 | -----------
10 |
11 | In a fit of horrible irony, on Windows you'll both have the most need to edit
12 | your PATH (since all applications insist on creating their own `bin`
13 | directories instead of installing to a global `bin` directory like on Unices),
14 | and you're also equipped with the absolute worst tools to deal with this. The
15 | default environment editor dialog where you get to see 30 characters at once if
16 | you're lucky? Yuck.
17 |
18 | *Windows Path Editor* (a horribly creative name, I know) gives you a
19 | better overview and easier ways to manipulate your path settings.
20 |
21 | Features
22 | -----------
23 |
24 | - Edit your path using drag and drop.
25 | - Detect conflicts between directories on your path (diagnose issues like the
26 | wrong executable being launched or the wrong DLL being loaded).
27 | - Remove bogus entries from your path with a single click.
28 | - Scan your disk for tools that have a `bin` directory and automatically add
29 | them to your path.
30 | - UAC aware.
31 |
32 | 
33 |
--------------------------------------------------------------------------------
/RELEASING:
--------------------------------------------------------------------------------
1 | - Increment version number in AssemblyInfo.cs
2 | - Build as Debug (for the purposes of Debug.Print)
3 | - Zip contents of bin\Release to windowspatheditor-x.x.zip (don't include vshost.*)
4 | - Upload to GitHub releases, URL will be https://github.com/rix0rrr/WindowsPathEditor/releases/download/x.x/windowspatheditor-x.x.zip
5 | - Update README.md w/ new version link
6 | - Commit, Tag
7 | - Switch to gh-pages branch
8 | - Edit index.html, link to new version (in 2 places!)
9 | - Push (both branches!)
10 |
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rix0rrr/WindowsPathEditor/aa82da59f26ab662f0d5a9906c333ba9619bcc24/TODO
--------------------------------------------------------------------------------
/WINDOWS PATH.txt:
--------------------------------------------------------------------------------
1 | Precedence: first SYSTEM then USER
2 |
--------------------------------------------------------------------------------
/WindowsPathEditor.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 11.00
3 | # Visual Studio 2010
4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsPathEditor", "WindowsPathEditor\WindowsPathEditor.csproj", "{27BB9DC6-B4E7-4600-98E1-9C855720DF69}"
5 | EndProject
6 | Global
7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
8 | Debug|x86 = Debug|x86
9 | Release|x86 = Release|x86
10 | EndGlobalSection
11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
12 | {27BB9DC6-B4E7-4600-98E1-9C855720DF69}.Debug|x86.ActiveCfg = Debug|x86
13 | {27BB9DC6-B4E7-4600-98E1-9C855720DF69}.Debug|x86.Build.0 = Debug|x86
14 | {27BB9DC6-B4E7-4600-98E1-9C855720DF69}.Release|x86.ActiveCfg = Release|x86
15 | {27BB9DC6-B4E7-4600-98E1-9C855720DF69}.Release|x86.Build.0 = Release|x86
16 | EndGlobalSection
17 | GlobalSection(SolutionProperties) = preSolution
18 | HideSolutionNode = FALSE
19 | EndGlobalSection
20 | EndGlobal
21 |
--------------------------------------------------------------------------------
/WindowsPathEditor/AnnotatedPathEntry.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.ComponentModel;
6 |
7 | namespace WindowsPathEditor
8 | {
9 | ///
10 | /// Mutable wrapper for a PathEntry that can have issues added to i
11 | ///
12 | public class AnnotatedPathEntry : INotifyPropertyChanged
13 | {
14 | public event PropertyChangedEventHandler PropertyChanged;
15 | private List issues = new List();
16 |
17 | public AnnotatedPathEntry(PathEntry path)
18 | {
19 | Path = path;
20 | SeriousError = false;
21 | }
22 |
23 | public PathEntry Path { get; private set; }
24 |
25 | ///
26 | /// Return the alert level (0, 1 or 2) depending on whether everything is ok, the dirty has issues or is missing
27 | ///
28 | public int AlertLevel
29 | {
30 | get
31 | {
32 | if (SeriousError) return 2;
33 | if (issues.Count() > 0) return 1;
34 | return 0;
35 | }
36 | }
37 |
38 | public bool SeriousError { get; set; }
39 |
40 | public bool Exists { get { return Path.Exists; } }
41 |
42 | public string SymbolicPath { get { return Path.SymbolicPath; } }
43 |
44 |
45 | ///
46 | /// Return all issues with the PathEntry
47 | ///
48 | public IEnumerable Issues
49 | {
50 | get
51 | {
52 | lock (issues)
53 | {
54 | return issues.ToList();
55 | }
56 | }
57 | }
58 |
59 | ///
60 | /// Add an issue
61 | ///
62 | public void AddIssue(string issue)
63 | {
64 | lock (issues) issues.Add(issue);
65 | PropertyChanged.Notify(() => Issues);
66 | PropertyChanged.Notify(() => AlertLevel);
67 | }
68 |
69 | internal void ClearIssues()
70 | {
71 | lock(issues) issues.Clear();
72 | PropertyChanged.Notify(() => Issues);
73 | PropertyChanged.Notify(() => AlertLevel);
74 | }
75 |
76 | public override string ToString()
77 | {
78 | return Path.ToString();
79 | }
80 |
81 | public static AnnotatedPathEntry FromPath(PathEntry p)
82 | {
83 | return new AnnotatedPathEntry(p);
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/WindowsPathEditor/App.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/WindowsPathEditor/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Windows;
7 |
8 | namespace WindowsPathEditor
9 | {
10 | ///
11 | /// Interaction logic for App.xaml
12 | ///
13 | public partial class App : Application
14 | {
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/WindowsPathEditor/AutoCompleteBox.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
18 |
23 |
24 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/WindowsPathEditor/AutoCompleteBox.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Windows;
6 | using System.Windows.Controls;
7 | using System.Windows.Data;
8 | using System.Windows.Documents;
9 | using System.Windows.Input;
10 | using System.Windows.Media;
11 | using System.Windows.Media.Imaging;
12 | using System.Windows.Navigation;
13 | using System.Windows.Shapes;
14 | using System.Reactive.Linq;
15 | using System.Reactive.Concurrency;
16 |
17 | namespace WindowsPathEditor
18 | {
19 | ///
20 | /// Interaction logic for AutoCompleteBox.xaml
21 | ///
22 | public partial class AutoCompleteBox
23 | {
24 | private IDisposable subscription;
25 |
26 | public AutoCompleteBox()
27 | {
28 | InitializeComponent();
29 | }
30 |
31 | public void SetCompleteProvider(Func> provider)
32 | {
33 | if (subscription != null) subscription.Dispose();
34 |
35 | var changes = Observable.FromEventPattern(textBox, "TextChanged")
36 | .Select(e => ((TextBox)e.Sender).Text)
37 | .Where(txt => txt != "");
38 |
39 | var search = Observable.ToAsync>(provider);
40 |
41 | var results = from s in changes
42 | from r in search(s).TakeUntil(changes)
43 | select r;
44 |
45 | subscription = results.ObserveOnDispatcher()
46 | .Subscribe(res =>
47 | {
48 | popup.IsOpen = true;
49 | if (res.Count() > 0)
50 | suggestionList.ItemsSource = res;
51 | else
52 | suggestionList.ItemsSource = new string[] { "(no matches)" };
53 | });
54 | }
55 |
56 | private void textBox_KeyUp(object sender, KeyEventArgs e)
57 | {
58 | if (e.Key == Key.Escape) { textBox.Text = ""; popup.IsOpen = false; }
59 | if (textBox.Text == "") popup.IsOpen = false;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/WindowsPathEditor/DiffPath.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace WindowsPathEditor
7 | {
8 | public class DiffPath
9 | {
10 | public DiffPath(PathEntry path, bool added)
11 | {
12 | Path = path;
13 | IsAdded = added;
14 | }
15 |
16 | public PathEntry Path { get; private set; }
17 |
18 | public bool IsAdded { get; private set; }
19 |
20 | public string SymbolicPath { get { return Path.SymbolicPath; } }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/WindowsPathEditor/DiffWindow.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/WindowsPathEditor/DiffWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Windows;
6 | using System.Windows.Controls;
7 | using System.Windows.Data;
8 | using System.Windows.Documents;
9 | using System.Windows.Input;
10 | using System.Windows.Media;
11 | using System.Windows.Media.Imaging;
12 | using System.Windows.Shapes;
13 | using System.Collections.ObjectModel;
14 |
15 | namespace WindowsPathEditor
16 | {
17 | ///
18 | /// Interaction logic for DiffWindow.xaml
19 | ///
20 | public partial class DiffWindow : Window
21 | {
22 | public DiffWindow()
23 | {
24 | InitializeComponent();
25 |
26 | Changes = new ObservableCollection();
27 | }
28 |
29 | #region Dependency Properties
30 |
31 | public ObservableCollection Changes
32 | {
33 | get { return (ObservableCollection)GetValue(ChangesProperty); }
34 | set { SetValue(ChangesProperty, value); }
35 | }
36 |
37 | public static readonly DependencyProperty ChangesProperty =
38 | DependencyProperty.Register("Changes",
39 | typeof(ObservableCollection),
40 | typeof(DiffWindow));
41 |
42 | #endregion
43 |
44 | private void okButton_Click(object sender, RoutedEventArgs e)
45 | {
46 | DialogResult = true;
47 | Close();
48 | }
49 |
50 | private void cancelButton_Click(object sender, RoutedEventArgs e)
51 | {
52 | DialogResult = false;
53 | Close();
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/WindowsPathEditor/DirectoryList.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/WindowsPathEditor/DirectoryList.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Windows;
6 | using System.Windows.Controls;
7 | using System.Windows.Data;
8 | using System.Windows.Documents;
9 | using System.Windows.Input;
10 | using System.Windows.Media;
11 | using System.Windows.Media.Imaging;
12 | using System.Windows.Navigation;
13 | using System.Windows.Shapes;
14 |
15 | namespace WindowsPathEditor
16 | {
17 | ///
18 | /// Interaction logic for DirectoryList.xaml
19 | ///
20 | public partial class DirectoryList
21 | {
22 | public DirectoryList()
23 | {
24 | InitializeComponent();
25 | }
26 |
27 | public bool ShowIssues
28 | {
29 | get { return (bool)GetValue(ShowIssuesProperty); }
30 | set { SetValue(ShowIssuesProperty, value); }
31 | }
32 |
33 | // Using a DependencyProperty as the backing store for ShowIssues. This enables animation, styling, binding, etc...
34 | public static readonly DependencyProperty ShowIssuesProperty =
35 | DependencyProperty.Register("ShowIssues", typeof(bool), typeof(DirectoryList), new UIPropertyMetadata(true));
36 |
37 |
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/WindowsPathEditor/DragDropListBox/DragDropHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Windows;
6 | using System.Windows.Input;
7 | using System.Windows.Media;
8 | using System.Windows.Controls;
9 | using System.Collections;
10 | using System.Windows.Documents;
11 | using System.Reflection;
12 |
13 | namespace DragDropListBox
14 | {
15 | public class DragDropHelper
16 | {
17 | // source and target
18 | private DataFormat format = DataFormats.GetDataFormat("DragDropItemsControl");
19 | private Point initialMousePosition;
20 | private Vector initialMouseOffset;
21 | private object draggedData;
22 | private DraggedAdorner draggedAdorner;
23 | private InsertionAdorner insertionAdorner;
24 | private Window topWindow;
25 | // source
26 | private ItemsControl sourceItemsControl;
27 | private FrameworkElement sourceItemContainer;
28 | // target
29 | private ItemsControl targetItemsControl;
30 | private FrameworkElement targetItemContainer;
31 | private bool hasVerticalOrientation;
32 | private int insertionIndex;
33 | private bool isInFirstHalf;
34 | // singleton
35 | private static DragDropHelper instance;
36 | private static DragDropHelper Instance
37 | {
38 | get
39 | {
40 | if(instance == null)
41 | {
42 | instance = new DragDropHelper();
43 | }
44 | return instance;
45 | }
46 | }
47 |
48 | public static bool GetIsDragSource(DependencyObject obj)
49 | {
50 | return (bool)obj.GetValue(IsDragSourceProperty);
51 | }
52 |
53 | public static void SetIsDragSource(DependencyObject obj, bool value)
54 | {
55 | obj.SetValue(IsDragSourceProperty, value);
56 | }
57 |
58 | public static readonly DependencyProperty IsDragSourceProperty =
59 | DependencyProperty.RegisterAttached("IsDragSource", typeof(bool), typeof(DragDropHelper), new UIPropertyMetadata(false, IsDragSourceChanged));
60 |
61 |
62 | public static bool GetIsDropTarget(DependencyObject obj)
63 | {
64 | return (bool)obj.GetValue(IsDropTargetProperty);
65 | }
66 |
67 | public static void SetIsDropTarget(DependencyObject obj, bool value)
68 | {
69 | obj.SetValue(IsDropTargetProperty, value);
70 | }
71 |
72 | public static readonly DependencyProperty IsDropTargetProperty =
73 | DependencyProperty.RegisterAttached("IsDropTarget", typeof(bool), typeof(DragDropHelper), new UIPropertyMetadata(false, IsDropTargetChanged));
74 |
75 | public static DataTemplate GetDragDropTemplate(DependencyObject obj)
76 | {
77 | return (DataTemplate)obj.GetValue(DragDropTemplateProperty);
78 | }
79 |
80 | public static void SetDragDropTemplate(DependencyObject obj, DataTemplate value)
81 | {
82 | obj.SetValue(DragDropTemplateProperty, value);
83 | }
84 |
85 | public static readonly DependencyProperty DragDropTemplateProperty =
86 | DependencyProperty.RegisterAttached("DragDropTemplate", typeof(DataTemplate), typeof(DragDropHelper), new UIPropertyMetadata(null));
87 |
88 | public static Func GetExternalDropConverter(DependencyObject obj)
89 | {
90 | return (Func)obj.GetValue(ExternalDropConverterProperty);
91 | }
92 |
93 | public static void SetExternalDropConverter(DependencyObject obj, Func value)
94 | {
95 | obj.SetValue(ExternalDropConverterProperty, value);
96 | }
97 |
98 | // Using a DependencyProperty as the backing store for ExternalDropConverter. This enables animation, styling, binding, etc...
99 | public static readonly DependencyProperty ExternalDropConverterProperty =
100 | DependencyProperty.RegisterAttached("ExternalDropConverter", typeof(Func), typeof(DragDropHelper), new UIPropertyMetadata(null));
101 |
102 | private static void IsDragSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
103 | {
104 | var dragSource = obj as ItemsControl;
105 | if (dragSource != null)
106 | {
107 | if (Object.Equals(e.NewValue, true))
108 | {
109 | dragSource.PreviewMouseLeftButtonDown += Instance.DragSource_PreviewMouseLeftButtonDown;
110 | dragSource.PreviewMouseLeftButtonUp += Instance.DragSource_PreviewMouseLeftButtonUp;
111 | dragSource.PreviewMouseMove += Instance.DragSource_PreviewMouseMove;
112 | }
113 | else
114 | {
115 | dragSource.PreviewMouseLeftButtonDown -= Instance.DragSource_PreviewMouseLeftButtonDown;
116 | dragSource.PreviewMouseLeftButtonUp -= Instance.DragSource_PreviewMouseLeftButtonUp;
117 | dragSource.PreviewMouseMove -= Instance.DragSource_PreviewMouseMove;
118 | }
119 | }
120 | }
121 |
122 | private static void IsDropTargetChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
123 | {
124 | var dropTarget = obj as ItemsControl;
125 | if (dropTarget != null)
126 | {
127 | if (Object.Equals(e.NewValue, true))
128 | {
129 | dropTarget.AllowDrop = true;
130 | dropTarget.PreviewDrop += Instance.DropTarget_PreviewDrop;
131 | dropTarget.PreviewDragEnter += Instance.DropTarget_PreviewDrag;
132 | dropTarget.PreviewDragOver += Instance.DropTarget_PreviewDrag;
133 | dropTarget.PreviewDragLeave += Instance.DropTarget_PreviewDragLeave;
134 | }
135 | else
136 | {
137 | dropTarget.AllowDrop = false;
138 | dropTarget.PreviewDrop -= Instance.DropTarget_PreviewDrop;
139 | dropTarget.PreviewDragEnter -= Instance.DropTarget_PreviewDrag;
140 | dropTarget.PreviewDragOver -= Instance.DropTarget_PreviewDrag;
141 | dropTarget.PreviewDragLeave -= Instance.DropTarget_PreviewDragLeave;
142 | }
143 | }
144 | }
145 |
146 | // DragSource
147 |
148 | private void DragSource_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
149 | {
150 | this.sourceItemsControl = (ItemsControl)sender;
151 | Visual visual = e.OriginalSource as Visual;
152 |
153 | this.topWindow = Window.GetWindow(this.sourceItemsControl);
154 | this.initialMousePosition = e.GetPosition(this.topWindow);
155 |
156 | this.sourceItemContainer = sourceItemsControl.ContainerFromElement(visual) as FrameworkElement;
157 | if (this.sourceItemContainer != null)
158 | {
159 | this.draggedData = this.sourceItemContainer.DataContext;
160 | }
161 | }
162 |
163 | // Drag = mouse down + move by a certain amount
164 | private void DragSource_PreviewMouseMove(object sender, MouseEventArgs e)
165 | {
166 | try
167 | {
168 | if (this.draggedData != null)
169 | {
170 | // Only drag when user moved the mouse by a reasonable amount.
171 | if (Utilities.IsMovementBigEnough(this.initialMousePosition, e.GetPosition(this.topWindow)))
172 | {
173 | this.initialMouseOffset = this.initialMousePosition - this.sourceItemContainer.TranslatePoint(new Point(0, 0), this.topWindow);
174 |
175 | DataObject data = new DataObject(this.format.Name, this.draggedData);
176 |
177 | // Adding events to the window to make sure dragged adorner comes up when mouse is not over a drop target.
178 | bool previousAllowDrop = this.topWindow.AllowDrop;
179 | this.topWindow.AllowDrop = true;
180 | this.topWindow.DragEnter += TopWindow_DragEnter;
181 | this.topWindow.DragOver += TopWindow_DragOver;
182 | this.topWindow.DragLeave += TopWindow_DragLeave;
183 |
184 | DragDropEffects effects = DragDrop.DoDragDrop((DependencyObject)sender, data, DragDropEffects.Move);
185 |
186 | // Without this call, there would be a bug in the following scenario: Click on a data item, and drag
187 | // the mouse very fast outside of the window. When doing this really fast, for some reason I don't get
188 | // the Window leave event, and the dragged adorner is left behind.
189 | // With this call, the dragged adorner will disappear when we release the mouse outside of the window,
190 | // which is when the DoDragDrop synchronous method returns.
191 | RemoveDraggedAdorner();
192 |
193 | this.topWindow.AllowDrop = previousAllowDrop;
194 | this.topWindow.DragEnter -= TopWindow_DragEnter;
195 | this.topWindow.DragOver -= TopWindow_DragOver;
196 | this.topWindow.DragLeave -= TopWindow_DragLeave;
197 |
198 | this.draggedData = null;
199 | }
200 | }
201 | }
202 | catch (Exception)
203 | {
204 | // This explodes sometimes, dunno why
205 | }
206 | }
207 |
208 | private void DragSource_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
209 | {
210 | this.draggedData = null;
211 | }
212 |
213 | private object FindDraggedItem(DragEventArgs e)
214 | {
215 | object draggedItem = e.Data.GetData(this.format.Name);
216 | if (draggedItem == null && GetExternalDropConverter((DependencyObject)e.Source) != null)
217 | {
218 | draggedItem = GetExternalDropConverter((DependencyObject)e.Source)(e.Data);
219 | }
220 | return draggedItem;
221 | }
222 |
223 | private void DropTarget_PreviewDrag(object sender, DragEventArgs e)
224 | {
225 | this.targetItemsControl = (ItemsControl)sender;
226 | object draggedItem = FindDraggedItem(e);
227 |
228 | DecideDropTarget(e);
229 | if (draggedItem != null)
230 | {
231 | if (topWindow == null) topWindow = Window.GetWindow(this.targetItemsControl);
232 |
233 | ShowDraggedAdorner(e.GetPosition(topWindow));
234 | ShowInsertionAdorner();
235 | }
236 | e.Handled = true;
237 | }
238 |
239 | private void DropTarget_PreviewDrop(object sender, DragEventArgs e)
240 | {
241 | object draggedItem = FindDraggedItem(e);
242 | int indexRemoved = -1;
243 |
244 | if (draggedItem != null)
245 | {
246 | if (sourceItemsControl != null)
247 | {
248 | if ((e.Effects & DragDropEffects.Move) != 0)
249 | {
250 | indexRemoved = Utilities.RemoveItemFromItemsControl(this.sourceItemsControl, draggedItem);
251 | }
252 | // This happens when we drag an item to a later position within the same ItemsControl.
253 | if (indexRemoved != -1 && this.sourceItemsControl == this.targetItemsControl && indexRemoved < this.insertionIndex)
254 | {
255 | this.insertionIndex--;
256 | }
257 | }
258 | Utilities.InsertItemInItemsControl(this.targetItemsControl, draggedItem, this.insertionIndex);
259 |
260 | RemoveDraggedAdorner();
261 | RemoveInsertionAdorner();
262 | }
263 | e.Handled = true;
264 | }
265 |
266 | private void DropTarget_PreviewDragLeave(object sender, DragEventArgs e)
267 | {
268 | // Dragged Adorner is only created once on DragEnter + every time we enter the window.
269 | // It's only removed once on the DragDrop, and every time we leave the window. (so no need to remove it here)
270 | object draggedItem = FindDraggedItem(e);
271 | RemoveInsertionAdorner();
272 | e.Handled = true;
273 | }
274 |
275 | // If the types of the dragged data and ItemsControl's source are compatible,
276 | // there are 3 situations to have into account when deciding the drop target:
277 | // 1. mouse is over an items container
278 | // 2. mouse is over the empty part of an ItemsControl, but ItemsControl is not empty
279 | // 3. mouse is over an empty ItemsControl.
280 | // The goal of this method is to decide on the values of the following properties:
281 | // targetItemContainer, insertionIndex and isInFirstHalf.
282 | private void DecideDropTarget(DragEventArgs e)
283 | {
284 | int targetItemsControlCount = targetItemsControl != null ? this.targetItemsControl.Items.Count : 0;
285 | object draggedItem = FindDraggedItem(e);
286 |
287 | if (IsDropDataTypeAllowed(draggedItem))
288 | {
289 | if (targetItemsControlCount > 0)
290 | {
291 | this.hasVerticalOrientation = Utilities.HasVerticalOrientation(this.targetItemsControl.ItemContainerGenerator.ContainerFromIndex(0) as FrameworkElement);
292 | this.targetItemContainer = targetItemsControl.ContainerFromElement((DependencyObject)e.OriginalSource) as FrameworkElement;
293 |
294 | if (this.targetItemContainer != null)
295 | {
296 | Point positionRelativeToItemContainer = e.GetPosition(this.targetItemContainer);
297 | this.isInFirstHalf = Utilities.IsInFirstHalf(this.targetItemContainer, positionRelativeToItemContainer, this.hasVerticalOrientation);
298 | this.insertionIndex = this.targetItemsControl.ItemContainerGenerator.IndexFromContainer(this.targetItemContainer);
299 |
300 | if (!this.isInFirstHalf)
301 | {
302 | this.insertionIndex++;
303 | }
304 | }
305 | else
306 | {
307 | this.targetItemContainer = this.targetItemsControl.ItemContainerGenerator.ContainerFromIndex(targetItemsControlCount - 1) as FrameworkElement;
308 | this.isInFirstHalf = false;
309 | this.insertionIndex = targetItemsControlCount;
310 | }
311 | }
312 | else
313 | {
314 | this.targetItemContainer = null;
315 | this.insertionIndex = 0;
316 | }
317 | }
318 | else
319 | {
320 | this.targetItemContainer = null;
321 | this.insertionIndex = -1;
322 | e.Effects = DragDropEffects.None;
323 | }
324 | }
325 |
326 | // Can the dragged data be added to the destination collection?
327 | // It can if destination is bound to IList, IList or not data bound.
328 | private bool IsDropDataTypeAllowed(object draggedItem)
329 | {
330 | bool isDropDataTypeAllowed;
331 | IEnumerable collectionSource = targetItemsControl != null ? this.targetItemsControl.ItemsSource : Enumerable.Empty