Skip to content

Commit 335b66a

Browse files
committed
fix possible nullref if no unitys available for new project, add ObservableDictionary fixes #16, set better default width for Platform column,
1 parent 41cf4fd commit 335b66a

File tree

5 files changed

+260
-5
lines changed

5 files changed

+260
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
// Licensed by Daniel Cazzulino under the MIT License : https://gist.github.com/kzu/cfe3cb6e4fe3efea6d24
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Collections.Specialized;
5+
using System.ComponentModel;
6+
using System.Diagnostics;
7+
8+
namespace UnityLauncherPro.Helpers
9+
{
10+
/// <summary>
11+
/// Provides a dictionary for use with data binding.
12+
/// </summary>
13+
/// <typeparam name="TKey">Specifies the type of the keys in this collection.</typeparam>
14+
/// <typeparam name="TValue">Specifies the type of the values in this collection.</typeparam>
15+
[DebuggerDisplay("Count={Count}")]
16+
public class ObservableDictionary<TKey, TValue> :
17+
ICollection<KeyValuePair<TKey, TValue>>, IDictionary<TKey, TValue>,
18+
INotifyCollectionChanged, INotifyPropertyChanged
19+
{
20+
readonly IDictionary<TKey, TValue> dictionary;
21+
22+
/// <summary>Event raised when the collection changes.</summary>
23+
public event NotifyCollectionChangedEventHandler CollectionChanged = (sender, args) => { };
24+
25+
/// <summary>Event raised when a property on the collection changes.</summary>
26+
public event PropertyChangedEventHandler PropertyChanged = (sender, args) => { };
27+
28+
/// <summary>
29+
/// Initializes an instance of the class.
30+
/// </summary>
31+
public ObservableDictionary()
32+
: this(new Dictionary<TKey, TValue>())
33+
{
34+
}
35+
36+
/// <summary>
37+
/// Initializes an instance of the class using another dictionary as
38+
/// the key/value store.
39+
/// </summary>
40+
public ObservableDictionary(IDictionary<TKey, TValue> dictionary)
41+
{
42+
this.dictionary = dictionary;
43+
}
44+
45+
void AddWithNotification(KeyValuePair<TKey, TValue> item)
46+
{
47+
AddWithNotification(item.Key, item.Value);
48+
}
49+
50+
void AddWithNotification(TKey key, TValue value)
51+
{
52+
dictionary.Add(key, value);
53+
54+
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,
55+
new KeyValuePair<TKey, TValue>(key, value)));
56+
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
57+
PropertyChanged(this, new PropertyChangedEventArgs("Keys"));
58+
PropertyChanged(this, new PropertyChangedEventArgs("Values"));
59+
}
60+
61+
bool RemoveWithNotification(TKey key)
62+
{
63+
TValue value;
64+
if (dictionary.TryGetValue(key, out value) && dictionary.Remove(key))
65+
{
66+
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,
67+
new KeyValuePair<TKey, TValue>(key, value)));
68+
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
69+
PropertyChanged(this, new PropertyChangedEventArgs("Keys"));
70+
PropertyChanged(this, new PropertyChangedEventArgs("Values"));
71+
72+
return true;
73+
}
74+
75+
return false;
76+
}
77+
78+
void UpdateWithNotification(TKey key, TValue value)
79+
{
80+
TValue existing;
81+
if (dictionary.TryGetValue(key, out existing))
82+
{
83+
dictionary[key] = value;
84+
85+
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace,
86+
new KeyValuePair<TKey, TValue>(key, value),
87+
new KeyValuePair<TKey, TValue>(key, existing)));
88+
PropertyChanged(this, new PropertyChangedEventArgs("Values"));
89+
}
90+
else
91+
{
92+
AddWithNotification(key, value);
93+
}
94+
}
95+
96+
/// <summary>
97+
/// Allows derived classes to raise custom property changed events.
98+
/// </summary>
99+
protected void RaisePropertyChanged(PropertyChangedEventArgs args)
100+
{
101+
PropertyChanged(this, args);
102+
}
103+
104+
#region IDictionary<TKey,TValue> Members
105+
106+
/// <summary>
107+
/// Adds an element with the provided key and value to the <see cref="T:System.Collections.Generic.IDictionary`2" />.
108+
/// </summary>
109+
/// <param name="key">The object to use as the key of the element to add.</param>
110+
/// <param name="value">The object to use as the value of the element to add.</param>
111+
public void Add(TKey key, TValue value)
112+
{
113+
AddWithNotification(key, value);
114+
}
115+
116+
/// <summary>
117+
/// Determines whether the <see cref="T:System.Collections.Generic.IDictionary`2" /> contains an element with the specified key.
118+
/// </summary>
119+
/// <param name="key">The key to locate in the <see cref="T:System.Collections.Generic.IDictionary`2" />.</param>
120+
/// <returns>
121+
/// true if the <see cref="T:System.Collections.Generic.IDictionary`2" /> contains an element with the key; otherwise, false.
122+
/// </returns>
123+
public bool ContainsKey(TKey key)
124+
{
125+
return dictionary.ContainsKey(key);
126+
}
127+
128+
/// <summary>
129+
/// Gets an <see cref="T:System.Collections.Generic.ICollection`1" /> containing the keys of the <see cref="T:System.Collections.Generic.IDictionary`2" />.
130+
/// </summary>
131+
/// <returns>An <see cref="T:System.Collections.Generic.ICollection`1" /> containing the keys of the object that implements <see cref="T:System.Collections.Generic.IDictionary`2" />.</returns>
132+
public ICollection<TKey> Keys
133+
{
134+
get { return dictionary.Keys; }
135+
}
136+
137+
/// <summary>
138+
/// Removes the element with the specified key from the <see cref="T:System.Collections.Generic.IDictionary`2" />.
139+
/// </summary>
140+
/// <param name="key">The key of the element to remove.</param>
141+
/// <returns>
142+
/// true if the element is successfully removed; otherwise, false. This method also returns false if <paramref name="key" /> was not found in the original <see cref="T:System.Collections.Generic.IDictionary`2" />.
143+
/// </returns>
144+
public bool Remove(TKey key)
145+
{
146+
return RemoveWithNotification(key);
147+
}
148+
149+
/// <summary>
150+
/// Gets the value associated with the specified key.
151+
/// </summary>
152+
/// <param name="key">The key whose value to get.</param>
153+
/// <param name="value">When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the <paramref name="value" /> parameter. This parameter is passed uninitialized.</param>
154+
/// <returns>
155+
/// true if the object that implements <see cref="T:System.Collections.Generic.IDictionary`2" /> contains an element with the specified key; otherwise, false.
156+
/// </returns>
157+
public bool TryGetValue(TKey key, out TValue value)
158+
{
159+
return dictionary.TryGetValue(key, out value);
160+
}
161+
162+
/// <summary>
163+
/// Gets an <see cref="T:System.Collections.Generic.ICollection`1" /> containing the values in the <see cref="T:System.Collections.Generic.IDictionary`2" />.
164+
/// </summary>
165+
/// <returns>An <see cref="T:System.Collections.Generic.ICollection`1" /> containing the values in the object that implements <see cref="T:System.Collections.Generic.IDictionary`2" />.</returns>
166+
public ICollection<TValue> Values
167+
{
168+
get { return dictionary.Values; }
169+
}
170+
171+
/// <summary>
172+
/// Gets or sets the element with the specified key.
173+
/// </summary>
174+
/// <param name="key">The key.</param>
175+
/// <returns></returns>
176+
public TValue this[TKey key]
177+
{
178+
get { return dictionary[key]; }
179+
set { UpdateWithNotification(key, value); }
180+
}
181+
182+
#endregion
183+
184+
#region ICollection<KeyValuePair<TKey,TValue>> Members
185+
186+
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
187+
{
188+
AddWithNotification(item);
189+
}
190+
191+
void ICollection<KeyValuePair<TKey, TValue>>.Clear()
192+
{
193+
((ICollection<KeyValuePair<TKey, TValue>>)dictionary).Clear();
194+
195+
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
196+
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
197+
PropertyChanged(this, new PropertyChangedEventArgs("Keys"));
198+
PropertyChanged(this, new PropertyChangedEventArgs("Values"));
199+
}
200+
201+
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
202+
{
203+
return ((ICollection<KeyValuePair<TKey, TValue>>)dictionary).Contains(item);
204+
}
205+
206+
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
207+
{
208+
((ICollection<KeyValuePair<TKey, TValue>>)dictionary).CopyTo(array, arrayIndex);
209+
}
210+
211+
int ICollection<KeyValuePair<TKey, TValue>>.Count
212+
{
213+
get { return ((ICollection<KeyValuePair<TKey, TValue>>)dictionary).Count; }
214+
}
215+
216+
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
217+
{
218+
get { return ((ICollection<KeyValuePair<TKey, TValue>>)dictionary).IsReadOnly; }
219+
}
220+
221+
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
222+
{
223+
return RemoveWithNotification(item.Key);
224+
}
225+
226+
#endregion
227+
228+
#region IEnumerable<KeyValuePair<TKey,TValue>> Members
229+
230+
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
231+
{
232+
return ((ICollection<KeyValuePair<TKey, TValue>>)dictionary).GetEnumerator();
233+
}
234+
235+
IEnumerator IEnumerable.GetEnumerator()
236+
{
237+
return ((ICollection<KeyValuePair<TKey, TValue>>)dictionary).GetEnumerator();
238+
}
239+
240+
#endregion
241+
242+
// https://github.com/unitycoder/UnityLauncherPro/issues/16 added Clear https://stackoverflow.com/q/5663395/5452781
243+
public void Clear()
244+
{
245+
dictionary.Clear();
246+
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
247+
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
248+
PropertyChanged(this, new PropertyChangedEventArgs("Keys"));
249+
PropertyChanged(this, new PropertyChangedEventArgs("Values"));
250+
}
251+
}
252+
}

UnityLauncherPro/MainWindow.xaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -485,10 +485,10 @@
485485
<!--<DataGridComboBoxColumn x:Name="cmbPlatform" CellStyle="{StaticResource NoFocusCellStyle}" ClipboardContentBinding="{x:Null}" Header="Platform" IsReadOnly="True" Width="100"/>-->
486486
<!--<DataGridComboBoxColumn Header="Platform" IsReadOnly="False" SelectedItemBinding="{Binding TargetPlatform}" ItemsSource="{Binding Source={StaticResource platformEnum}}" />-->
487487

488-
<DataGridTemplateColumn Header="Platform">
488+
<DataGridTemplateColumn Header="Platform" Width="90">
489489
<DataGridTemplateColumn.CellTemplate >
490490
<DataTemplate >
491-
<ComboBox x:Name="cmbPlatformSelection" OverridesDefaultStyle="True" VerticalAlignment="Center" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" HorizontalContentAlignment="Stretch" IsEnabled="{Binding ElementName=chkEnablePlatformSelection, Path=IsChecked}" ItemsSource="{Binding TargetPlatforms}" SelectedValue="{Binding TargetPlatform}" Height="18" Foreground="{DynamicResource ThemeButtonForeground}" DropDownClosed="CmbPlatformSelection_DropDownClosed"/>
491+
<ComboBox x:Name="cmbPlatformSelection" OverridesDefaultStyle="True" VerticalAlignment="Center" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" HorizontalContentAlignment="Stretch" IsEnabled="{Binding ElementName=chkEnablePlatformSelection, Path=IsChecked}" ItemsSource="{Binding TargetPlatforms}" SelectedValue="{Binding TargetPlatform}" Height="18" Foreground="{DynamicResource ThemeButtonForeground}" DropDownClosed="CmbPlatformSelection_DropDownClosed" MinWidth="80"/>
492492
</DataTemplate>
493493
</DataGridTemplateColumn.CellTemplate>
494494
</DataGridTemplateColumn>

UnityLauncherPro/MainWindow.xaml.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
using System.Drawing; // for notifyicon
1010
using System.IO;
1111
using System.Runtime.InteropServices;
12-
using System.Text;
1312
using System.Threading;
1413
using System.Threading.Tasks;
1514
using System.Windows;
@@ -29,7 +28,7 @@ public partial class MainWindow : Window
2928
public static bool useHumanFriendlyDateFormat = false;
3029
public static List<Project> projectsSource;
3130
public static UnityInstallation[] unityInstallationsSource;
32-
public static Dictionary<string, string> unityInstalledVersions = new Dictionary<string, string>(); // versionID and installation folder
31+
public static ObservableDictionary<string, string> unityInstalledVersions = new ObservableDictionary<string, string>(); // versionID and installation folder
3332
public static readonly string launcherArgumentsFile = "LauncherArguments.txt";
3433
public static string preferredVersion = "none";
3534

@@ -473,6 +472,7 @@ void UpdateUnityInstallationsList()
473472

474473
// also make dictionary of installed unitys, to search faster
475474
unityInstalledVersions.Clear();
475+
476476
for (int i = 0, len = unityInstallationsSource.Length; i < len; i++)
477477
{
478478
var version = unityInstallationsSource[i].Version;

UnityLauncherPro/NewProject.xaml.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public NewProject(string unityVersion, string suggestedName, string targetFolder
2525
lblNewProjectFolder.Content = targetFolder;
2626

2727
// fill available versions, TODO could show which modules are installed
28-
gridAvailableVersions.ItemsSource = MainWindow.unityInstalledVersions;
28+
if (gridAvailableVersions.ItemsSource == null) gridAvailableVersions.ItemsSource = MainWindow.unityInstalledVersions;
2929

3030
// we dont have that version installed, TODO show info or pick closest?, for now picks the first item
3131
if (MainWindow.unityInstalledVersions.ContainsKey(unityVersion) == true)
@@ -157,6 +157,8 @@ private void GridAvailableVersions_Loaded(object sender, RoutedEventArgs e)
157157
{
158158
// set initial default row color
159159
DataGridRow row = (DataGridRow)gridAvailableVersions.ItemContainerGenerator.ContainerFromIndex(gridAvailableVersions.SelectedIndex);
160+
// if now unitys available
161+
if (row == null) return;
160162
//row.Background = Brushes.Green;
161163
row.Foreground = Brushes.White;
162164
row.FontWeight = FontWeights.Bold;

UnityLauncherPro/UnityLauncherPro.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
<Compile Include="GetProjects.cs" />
8383
<Compile Include="GetUnityInstallations.cs" />
8484
<Compile Include="GetUnityUpdates.cs" />
85+
<Compile Include="Helpers\ObservableDictionary.cs" />
8586
<Compile Include="Helpers\ProcessHandler.cs" />
8687
<Compile Include="Libraries\ExtractTarGz.cs" />
8788
<Compile Include="NewProject.xaml.cs">

0 commit comments

Comments
 (0)