Teacher editor
This commit is contained in:
@@ -12,6 +12,20 @@ namespace TimetableDesigner.Core
|
||||
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public IDictionary<TimetableDay, TimetableSpanCollection> AvailabilityHours { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public Teacher()
|
||||
{
|
||||
Name = string.Empty;
|
||||
Description = string.Empty;
|
||||
AvailabilityHours = new Dictionary<TimetableDay, TimetableSpanCollection>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace TimetableDesigner.Core
|
||||
{
|
||||
public struct TimetableDay
|
||||
[Serializable]
|
||||
public class TimetableDay
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace TimetableDesigner.Core
|
||||
{
|
||||
[Serializable]
|
||||
public struct TimetableSlot
|
||||
public class TimetableSpan
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace TimetableDesigner.Core
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public TimetableSlot(TimeOnly from, TimeOnly to)
|
||||
public TimetableSpan(TimeOnly from, TimeOnly to)
|
||||
{
|
||||
if (to <= from)
|
||||
{
|
||||
@@ -33,29 +33,29 @@ namespace TimetableDesigner.Core
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
internal TimetableSlotsCollision CheckCollision(TimetableSlot slot)
|
||||
internal TimetableSpansCollision CheckCollision(TimetableSpan slot)
|
||||
{
|
||||
if (slot.To <= this.From)
|
||||
{
|
||||
return TimetableSlotsCollision.CheckedSlotBefore;
|
||||
return TimetableSpansCollision.CheckedSlotBefore;
|
||||
}
|
||||
else if (this.To <= slot.From)
|
||||
{
|
||||
return TimetableSlotsCollision.CheckedSlotAfter;
|
||||
return TimetableSpansCollision.CheckedSlotAfter;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.From < slot.From && slot.To < this.To)
|
||||
{
|
||||
return TimetableSlotsCollision.CheckedSlotIn;
|
||||
return TimetableSpansCollision.CheckedSlotIn;
|
||||
}
|
||||
else if (this.From < slot.From && slot.From < this.To && this.To < slot.To)
|
||||
{
|
||||
return TimetableSlotsCollision.CheckedSlotFromIn;
|
||||
return TimetableSpansCollision.CheckedSlotFromIn;
|
||||
}
|
||||
else if (slot.From < this.From && this.From < slot.To && slot.To < this.To)
|
||||
{
|
||||
return TimetableSlotsCollision.CheckedSlotToIn;
|
||||
return TimetableSpansCollision.CheckedSlotToIn;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -64,7 +64,7 @@ namespace TimetableDesigner.Core
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj) => obj is TimetableSlot slot && From == slot.From && To == slot.To;
|
||||
public override bool Equals(object? obj) => obj is TimetableSpan slot && From == slot.From && To == slot.To;
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(From, To);
|
||||
|
||||
75
TimetableDesigner.Core/TimetableSpanCollection.cs
Normal file
75
TimetableDesigner.Core/TimetableSpanCollection.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace TimetableDesigner.Core
|
||||
{
|
||||
public class TimetableSpanCollection : ICollection<TimetableSpan>
|
||||
{
|
||||
#region FIELDS
|
||||
|
||||
private IList<TimetableSpan> _list;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public int Count => _list.Count;
|
||||
public bool IsReadOnly => _list.IsReadOnly;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public TimetableSpanCollection()
|
||||
{
|
||||
_list = new List<TimetableSpan>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public void Add(TimetableSpan item)
|
||||
{
|
||||
int i = 0;
|
||||
if (Count > 0)
|
||||
{
|
||||
bool done = false;
|
||||
while (i < Count && !done)
|
||||
{
|
||||
switch (item.CheckCollision(_list.ElementAt(i)))
|
||||
{
|
||||
case TimetableSpansCollision.CheckedSlotBefore: i++; break;
|
||||
case TimetableSpansCollision.CheckedSlotAfter: done ^= true; break;
|
||||
default: throw new ArgumentException("Slot collide with another slot");
|
||||
}
|
||||
}
|
||||
}
|
||||
_list.Insert(i, item);
|
||||
}
|
||||
|
||||
public void Clear() => _list.Clear();
|
||||
|
||||
public bool Contains(TimetableSpan item) => _list.Contains(item);
|
||||
|
||||
public void CopyTo(TimetableSpan[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex);
|
||||
|
||||
public IEnumerator<TimetableSpan> GetEnumerator() => _list.GetEnumerator();
|
||||
|
||||
public bool Remove(TimetableSpan item) => _list.Remove(item);
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_list).GetEnumerator();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace TimetableDesigner.Core
|
||||
{
|
||||
internal enum TimetableSlotsCollision
|
||||
internal enum TimetableSpansCollision
|
||||
{
|
||||
CheckedSlotBefore,
|
||||
CheckedSlotAfter,
|
||||
@@ -12,7 +12,7 @@ namespace TimetableDesigner.Core
|
||||
#region FIELDS
|
||||
|
||||
private List<TimetableDay> _days;
|
||||
private List<TimetableSlot> _slots;
|
||||
private TimetableSpanCollection _slots;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace TimetableDesigner.Core
|
||||
#region PROPERTIES
|
||||
|
||||
public IEnumerable<TimetableDay> Days => _days;
|
||||
public IEnumerable<TimetableSlot> Slots => _slots;
|
||||
public IEnumerable<TimetableSpan> Slots => _slots;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace TimetableDesigner.Core
|
||||
public TimetableTemplate()
|
||||
{
|
||||
_days = new List<TimetableDay>();
|
||||
_slots = new List<TimetableSlot>();
|
||||
_slots = new TimetableSpanCollection();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -41,39 +41,13 @@ namespace TimetableDesigner.Core
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public void AddDay(TimetableDay name)
|
||||
{
|
||||
_days.Add(name);
|
||||
}
|
||||
public void AddDay(TimetableDay name) => _days.Add(name);
|
||||
|
||||
public bool RemoveDay(TimetableDay day)
|
||||
{
|
||||
return _days.Remove(day);
|
||||
}
|
||||
public bool RemoveDay(TimetableDay day) => _days.Remove(day);
|
||||
|
||||
public void AddSlot(TimetableSlot slot)
|
||||
{
|
||||
int i = 0;
|
||||
if (_slots.Count > 0)
|
||||
{
|
||||
bool done = false;
|
||||
while (i < _slots.Count && !done)
|
||||
{
|
||||
switch (slot.CheckCollision(_slots[i]))
|
||||
{
|
||||
case TimetableSlotsCollision.CheckedSlotBefore: i++; break;
|
||||
case TimetableSlotsCollision.CheckedSlotAfter: done ^= true; break;
|
||||
default: throw new ArgumentException("Slot collide with another slot");
|
||||
}
|
||||
}
|
||||
}
|
||||
_slots.Insert(i, slot);
|
||||
}
|
||||
public void AddSlot(TimetableSpan slot) => _slots.Add(slot);
|
||||
|
||||
public bool RemoveSlot(TimetableSlot slot)
|
||||
{
|
||||
return _slots.Remove(slot);
|
||||
}
|
||||
public bool RemoveSlot(TimetableSpan slot) => _slots.Remove(slot);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
153
TimetableDesigner.Customs/Collections/ObservableDictionary.cs
Normal file
153
TimetableDesigner.Customs/Collections/ObservableDictionary.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace TimetableDesigner.Customs.Collections
|
||||
{
|
||||
public class ObservableDictionary<TKey, TValue> : ObservableCollection<ObservableKeyValuePair<TKey, TValue>>, IDictionary<TKey, TValue>
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
public ICollection<TKey> Keys => Items.Select(p => p.Key).ToList();
|
||||
public ICollection<TValue> Values => Items.Select(p => p.Value).ToList();
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region INDEXERS
|
||||
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!TryGetValue(key, out TValue result))
|
||||
{
|
||||
throw new ArgumentException("Key not found");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (ContainsKey(key))
|
||||
{
|
||||
GetKeyValuePairByTheKey(key).Value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Add(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public ObservableDictionary() : base()
|
||||
{ }
|
||||
|
||||
public ObservableDictionary(IDictionary<TKey, TValue> dictionary) : base()
|
||||
{
|
||||
foreach (KeyValuePair<TKey, TValue> pair in dictionary)
|
||||
{
|
||||
this.Add(pair);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
if (ContainsKey(key))
|
||||
{
|
||||
throw new ArgumentException("The dictionary already contains the key");
|
||||
}
|
||||
Add(new ObservableKeyValuePair<TKey, TValue>(key, value));
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
|
||||
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
ObservableKeyValuePair<TKey, TValue> pair = GetKeyValuePairByTheKey(item.Key);
|
||||
if (Equals(pair, default(ObservableKeyValuePair<TKey, TValue>)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return Equals(pair.Value, item.Value);
|
||||
}
|
||||
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
ObservableKeyValuePair<TKey, TValue> pair = ((ObservableCollection<ObservableKeyValuePair<TKey, TValue>>)this).FirstOrDefault((i) => Equals(key, i.Key));
|
||||
|
||||
return !Equals(default(ObservableKeyValuePair<TKey, TValue>), pair);
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
List<ObservableKeyValuePair<TKey, TValue>> remove = ((ObservableCollection<ObservableKeyValuePair<TKey, TValue>>)this).Where(pair => Equals(key, pair.Key)).ToList();
|
||||
foreach (ObservableKeyValuePair<TKey, TValue> pair in remove)
|
||||
{
|
||||
Remove(pair);
|
||||
}
|
||||
return remove.Count > 0;
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
ObservableKeyValuePair<TKey, TValue> pair = GetKeyValuePairByTheKey(item.Key);
|
||||
if (Equals(pair, default(ObservableKeyValuePair<TKey, TValue>)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!Equals(pair.Value, item.Value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return Remove(pair);
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
|
||||
{
|
||||
value = default;
|
||||
var pair = GetKeyValuePairByTheKey(key);
|
||||
if (Equals(pair, default(ObservableKeyValuePair<TKey, TValue>)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
value = pair.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() => ((ObservableCollection<ObservableKeyValuePair<TKey, TValue>>)this).Select(i => new KeyValuePair<TKey, TValue>(i.Key, i.Value)).GetEnumerator();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private ObservableKeyValuePair<TKey, TValue> GetKeyValuePairByTheKey(TKey key) => ((ObservableCollection<ObservableKeyValuePair<TKey, TValue>>)this).FirstOrDefault(i => i.Key.Equals(key));
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace TimetableDesigner.Customs.Collections
|
||||
{
|
||||
public class ObservableKeyValuePair<TKey, TValue> : INotifyPropertyChanged
|
||||
{
|
||||
#region FIELDS
|
||||
|
||||
private TKey _key;
|
||||
private TValue _value;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public TKey Key
|
||||
{
|
||||
get => _key;
|
||||
set
|
||||
{
|
||||
_key = value;
|
||||
NotifyPropertyChanged(nameof(Key));
|
||||
}
|
||||
}
|
||||
public TValue Value
|
||||
{
|
||||
get => _value;
|
||||
set
|
||||
{
|
||||
_value = value;
|
||||
NotifyPropertyChanged(nameof(Value));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public ObservableKeyValuePair() : this(default, default)
|
||||
{ }
|
||||
|
||||
public ObservableKeyValuePair(TKey key, TValue value)
|
||||
{
|
||||
_key = key;
|
||||
_value = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private void NotifyPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region EVENTS
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -10,14 +10,14 @@ namespace TimetableDesigner.Tests
|
||||
[TestMethod]
|
||||
public void CreateValidSlotTest()
|
||||
{
|
||||
TimetableSlot slot = new TimetableSlot(new TimeOnly(8, 0), new TimeOnly(9, 0));
|
||||
TimetableSpan slot = new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public void CreateInvalidSlotTest()
|
||||
{
|
||||
TimetableSlot slot = new TimetableSlot(new TimeOnly(9, 0), new TimeOnly(8, 0));
|
||||
TimetableSpan slot = new TimetableSpan(new TimeOnly(9, 0), new TimeOnly(8, 0));
|
||||
|
||||
Assert.Fail();
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace TimetableDesigner.Tests
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public void CreateSlotWithZeroLengthTest()
|
||||
{
|
||||
TimetableSlot slot = new TimetableSlot(new TimeOnly(8, 0), new TimeOnly(8, 0));
|
||||
TimetableSpan slot = new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(8, 0));
|
||||
|
||||
Assert.Fail();
|
||||
}
|
||||
@@ -58,12 +58,12 @@ namespace TimetableDesigner.Tests
|
||||
{
|
||||
TimetableTemplate model = new TimetableTemplate();
|
||||
|
||||
TimetableSlot slot = new TimetableSlot(new TimeOnly(8, 0), new TimeOnly(9, 0));
|
||||
TimetableSpan slot = new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 0));
|
||||
|
||||
model.AddSlot(slot);
|
||||
|
||||
Assert.AreEqual(1, model.Slots.Count());
|
||||
Assert.AreEqual(new TimetableSlot(new TimeOnly(8, 0), new TimeOnly(9, 0)), model.Slots.ToList()[0]);
|
||||
Assert.AreEqual(new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 0)), model.Slots.ToList()[0]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -71,15 +71,15 @@ namespace TimetableDesigner.Tests
|
||||
{
|
||||
TimetableTemplate model = new TimetableTemplate();
|
||||
|
||||
TimetableSlot slot1 = new TimetableSlot(new TimeOnly(8, 15), new TimeOnly(9, 0));
|
||||
TimetableSlot slot2 = new TimetableSlot(new TimeOnly(9, 15), new TimeOnly(10, 0));
|
||||
TimetableSpan slot1 = new TimetableSpan(new TimeOnly(8, 15), new TimeOnly(9, 0));
|
||||
TimetableSpan slot2 = new TimetableSpan(new TimeOnly(9, 15), new TimeOnly(10, 0));
|
||||
|
||||
model.AddSlot(slot1);
|
||||
model.AddSlot(slot2);
|
||||
|
||||
Assert.AreEqual(2, model.Slots.Count());
|
||||
Assert.AreEqual(new TimetableSlot(new TimeOnly(8, 15), new TimeOnly(9, 0)), model.Slots.ToList()[0]);
|
||||
Assert.AreEqual(new TimetableSlot(new TimeOnly(9, 15), new TimeOnly(10, 0)), model.Slots.ToList()[1]);
|
||||
Assert.AreEqual(new TimetableSpan(new TimeOnly(8, 15), new TimeOnly(9, 0)), model.Slots.ToList()[0]);
|
||||
Assert.AreEqual(new TimetableSpan(new TimeOnly(9, 15), new TimeOnly(10, 0)), model.Slots.ToList()[1]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -87,15 +87,15 @@ namespace TimetableDesigner.Tests
|
||||
{
|
||||
TimetableTemplate model = new TimetableTemplate();
|
||||
|
||||
TimetableSlot slot1 = new TimetableSlot(new TimeOnly(8, 0), new TimeOnly(9, 0));
|
||||
TimetableSlot slot2 = new TimetableSlot(new TimeOnly(9, 0), new TimeOnly(10, 0));
|
||||
TimetableSpan slot1 = new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 0));
|
||||
TimetableSpan slot2 = new TimetableSpan(new TimeOnly(9, 0), new TimeOnly(10, 0));
|
||||
|
||||
model.AddSlot(slot1);
|
||||
model.AddSlot(slot2);
|
||||
|
||||
Assert.AreEqual(2, model.Slots.Count());
|
||||
Assert.AreEqual(new TimetableSlot(new TimeOnly(8, 0), new TimeOnly(9, 0)), model.Slots.ToList()[0]);
|
||||
Assert.AreEqual(new TimetableSlot(new TimeOnly(9, 0), new TimeOnly(10, 0)), model.Slots.ToList()[1]);
|
||||
Assert.AreEqual(new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 0)), model.Slots.ToList()[0]);
|
||||
Assert.AreEqual(new TimetableSpan(new TimeOnly(9, 0), new TimeOnly(10, 0)), model.Slots.ToList()[1]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -104,8 +104,8 @@ namespace TimetableDesigner.Tests
|
||||
{
|
||||
TimetableTemplate model = new TimetableTemplate();
|
||||
|
||||
TimetableSlot slot1 = new TimetableSlot(new TimeOnly(8, 0), new TimeOnly(9, 30));
|
||||
TimetableSlot slot2 = new TimetableSlot(new TimeOnly(8, 30), new TimeOnly(10, 0));
|
||||
TimetableSpan slot1 = new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 30));
|
||||
TimetableSpan slot2 = new TimetableSpan(new TimeOnly(8, 30), new TimeOnly(10, 0));
|
||||
|
||||
model.AddSlot(slot1);
|
||||
model.AddSlot(slot2);
|
||||
@@ -119,9 +119,9 @@ namespace TimetableDesigner.Tests
|
||||
{
|
||||
TimetableTemplate model = new TimetableTemplate();
|
||||
|
||||
TimetableSlot slot1 = new TimetableSlot(new TimeOnly(8, 0), new TimeOnly(9, 0));
|
||||
TimetableSlot slot2 = new TimetableSlot(new TimeOnly(10, 0), new TimeOnly(11, 0));
|
||||
TimetableSlot slot3 = new TimetableSlot(new TimeOnly(8, 59), new TimeOnly(10, 1));
|
||||
TimetableSpan slot1 = new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 0));
|
||||
TimetableSpan slot2 = new TimetableSpan(new TimeOnly(10, 0), new TimeOnly(11, 0));
|
||||
TimetableSpan slot3 = new TimetableSpan(new TimeOnly(8, 59), new TimeOnly(10, 1));
|
||||
|
||||
model.AddSlot(slot1);
|
||||
model.AddSlot(slot2);
|
||||
@@ -135,13 +135,13 @@ namespace TimetableDesigner.Tests
|
||||
{
|
||||
TimetableTemplate model = new TimetableTemplate();
|
||||
|
||||
TimetableSlot slot1 = new TimetableSlot(new TimeOnly(12, 0), new TimeOnly(13, 0));
|
||||
TimetableSlot slot2 = new TimetableSlot(new TimeOnly(8, 0), new TimeOnly(9, 0));
|
||||
TimetableSlot slot3 = new TimetableSlot(new TimeOnly(10, 0), new TimeOnly(11, 0));
|
||||
TimetableSlot slot4 = new TimetableSlot(new TimeOnly(14, 0), new TimeOnly(15, 0));
|
||||
TimetableSlot slot5 = new TimetableSlot(new TimeOnly(13, 0), new TimeOnly(14, 0));
|
||||
TimetableSlot slot6 = new TimetableSlot(new TimeOnly(9, 0), new TimeOnly(10, 0));
|
||||
TimetableSlot slot7 = new TimetableSlot(new TimeOnly(11, 0), new TimeOnly(12, 0));
|
||||
TimetableSpan slot1 = new TimetableSpan(new TimeOnly(12, 0), new TimeOnly(13, 0));
|
||||
TimetableSpan slot2 = new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 0));
|
||||
TimetableSpan slot3 = new TimetableSpan(new TimeOnly(10, 0), new TimeOnly(11, 0));
|
||||
TimetableSpan slot4 = new TimetableSpan(new TimeOnly(14, 0), new TimeOnly(15, 0));
|
||||
TimetableSpan slot5 = new TimetableSpan(new TimeOnly(13, 0), new TimeOnly(14, 0));
|
||||
TimetableSpan slot6 = new TimetableSpan(new TimeOnly(9, 0), new TimeOnly(10, 0));
|
||||
TimetableSpan slot7 = new TimetableSpan(new TimeOnly(11, 0), new TimeOnly(12, 0));
|
||||
|
||||
model.AddSlot(slot1);
|
||||
model.AddSlot(slot2);
|
||||
@@ -151,9 +151,9 @@ namespace TimetableDesigner.Tests
|
||||
model.AddSlot(slot6);
|
||||
model.AddSlot(slot7);
|
||||
|
||||
List<TimetableSlot> slots = model.Slots.ToList();
|
||||
List<TimetableSpan> slots = model.Slots.ToList();
|
||||
|
||||
TimetableSlot testSlot = slots[0];
|
||||
TimetableSpan testSlot = slots[0];
|
||||
for (int i = 1; i < slots.Count; i++)
|
||||
{
|
||||
if (testSlot.To > slots[i].From)
|
||||
|
||||
@@ -9,7 +9,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TimetableDesigner.Core", "T
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TimetableDesigner.Tests", "TimetableDesigner.Tests\TimetableDesigner.Tests.csproj", "{A9B4DB89-A007-472A-9C80-B6340458AC1B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimetableDesigner.MessageBox", "TimetableDesigner.MessageBox\TimetableDesigner.MessageBox.csproj", "{95992646-6D81-4FF4-885E-8F0BE2312D54}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TimetableDesigner.MessageBox", "TimetableDesigner.MessageBox\TimetableDesigner.MessageBox.csproj", "{95992646-6D81-4FF4-885E-8F0BE2312D54}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimetableDesigner.Customs", "TimetableDesigner.Customs\TimetableDesigner.Customs.csproj", "{BCA4CD04-FD49-4C28-9743-F4F6C1888E7E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -51,6 +53,14 @@ Global
|
||||
{95992646-6D81-4FF4-885E-8F0BE2312D54}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{95992646-6D81-4FF4-885E-8F0BE2312D54}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{95992646-6D81-4FF4-885E-8F0BE2312D54}.Release|x64.Build.0 = Release|Any CPU
|
||||
{BCA4CD04-FD49-4C28-9743-F4F6C1888E7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BCA4CD04-FD49-4C28-9743-F4F6C1888E7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BCA4CD04-FD49-4C28-9743-F4F6C1888E7E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{BCA4CD04-FD49-4C28-9743-F4F6C1888E7E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{BCA4CD04-FD49-4C28-9743-F4F6C1888E7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BCA4CD04-FD49-4C28-9743-F4F6C1888E7E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BCA4CD04-FD49-4C28-9743-F4F6C1888E7E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{BCA4CD04-FD49-4C28-9743-F4F6C1888E7E}.Release|x64.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace TimetableDesigner.Converters
|
||||
{ typeof(WelcomeTabViewModel), typeof(WelcomeTabView) },
|
||||
{ typeof(ProjectSettingsTabViewModel), typeof(ProjectSettingsTabView) },
|
||||
{ typeof(ClassroomEditTabViewModel), typeof(ClassroomEditTabView) },
|
||||
{ typeof(TeacherEditTabViewModel), typeof(TeacherEditTabView) },
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
81
TimetableDesigner/Properties/Resources.Designer.cs
generated
81
TimetableDesigner/Properties/Resources.Designer.cs
generated
@@ -114,6 +114,15 @@ namespace TimetableDesigner.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to New teacher.
|
||||
/// </summary>
|
||||
public static string Global_DefaultTeacherName {
|
||||
get {
|
||||
return ResourceManager.GetString("Global.DefaultTeacherName", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to no project loaded.
|
||||
/// </summary>
|
||||
@@ -321,6 +330,24 @@ namespace TimetableDesigner.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Teachers.
|
||||
/// </summary>
|
||||
public static string Main_Treeview_Teachers {
|
||||
get {
|
||||
return ResourceManager.GetString("Main.Treeview.Teachers", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Edit teacher.
|
||||
/// </summary>
|
||||
public static string Main_Treeview_Teachers_ContextMenu_Edit {
|
||||
get {
|
||||
return ResourceManager.GetString("Main.Treeview.Teachers.ContextMenu.Edit", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Error.
|
||||
/// </summary>
|
||||
@@ -429,6 +456,15 @@ namespace TimetableDesigner.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Teacher editing.
|
||||
/// </summary>
|
||||
public static string Tabs_TeacherEdit {
|
||||
get {
|
||||
return ResourceManager.GetString("Tabs.TeacherEdit", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Welcome.
|
||||
/// </summary>
|
||||
@@ -437,5 +473,50 @@ namespace TimetableDesigner.Properties {
|
||||
return ResourceManager.GetString("Tabs.Welcome", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Availability hours.
|
||||
/// </summary>
|
||||
public static string TeacherEdit_AvailabilityHours {
|
||||
get {
|
||||
return ResourceManager.GetString("TeacherEdit.AvailabilityHours", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Description.
|
||||
/// </summary>
|
||||
public static string TeacherEdit_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("TeacherEdit.Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to "From" value is higher or equal to "To" value.
|
||||
/// </summary>
|
||||
public static string TeacherEdit_Message_FromHigherThanTo {
|
||||
get {
|
||||
return ResourceManager.GetString("TeacherEdit.Message.FromHigherThanTo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Hour collide with another hour.
|
||||
/// </summary>
|
||||
public static string TeacherEdit_Message_HourCollision {
|
||||
get {
|
||||
return ResourceManager.GetString("TeacherEdit.Message.HourCollision", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Name.
|
||||
/// </summary>
|
||||
public static string TeacherEdit_Name {
|
||||
get {
|
||||
return ResourceManager.GetString("TeacherEdit.Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +135,9 @@
|
||||
<data name="Global.DefaultProjectName" xml:space="preserve">
|
||||
<value>New project</value>
|
||||
</data>
|
||||
<data name="Global.DefaultTeacherName" xml:space="preserve">
|
||||
<value>New teacher</value>
|
||||
</data>
|
||||
<data name="Global.NoProjectLoadedTitle" xml:space="preserve">
|
||||
<value>no project loaded</value>
|
||||
</data>
|
||||
@@ -204,6 +207,12 @@
|
||||
<data name="Main.Treeview.ContextMenu.Remove" xml:space="preserve">
|
||||
<value>Remove</value>
|
||||
</data>
|
||||
<data name="Main.Treeview.Teachers" xml:space="preserve">
|
||||
<value>Teachers</value>
|
||||
</data>
|
||||
<data name="Main.Treeview.Teachers.ContextMenu.Edit" xml:space="preserve">
|
||||
<value>Edit teacher</value>
|
||||
</data>
|
||||
<data name="MessageBox.Error" xml:space="preserve">
|
||||
<value>Error</value>
|
||||
</data>
|
||||
@@ -240,7 +249,25 @@
|
||||
<data name="Tabs.ProjectSettings" xml:space="preserve">
|
||||
<value>Project settings</value>
|
||||
</data>
|
||||
<data name="Tabs.TeacherEdit" xml:space="preserve">
|
||||
<value>Teacher editing</value>
|
||||
</data>
|
||||
<data name="Tabs.Welcome" xml:space="preserve">
|
||||
<value>Welcome</value>
|
||||
</data>
|
||||
<data name="TeacherEdit.AvailabilityHours" xml:space="preserve">
|
||||
<value>Availability hours</value>
|
||||
</data>
|
||||
<data name="TeacherEdit.Description" xml:space="preserve">
|
||||
<value>Description</value>
|
||||
</data>
|
||||
<data name="TeacherEdit.Message.FromHigherThanTo" xml:space="preserve">
|
||||
<value>"From" value is higher or equal to "To" value</value>
|
||||
</data>
|
||||
<data name="TeacherEdit.Message.HourCollision" xml:space="preserve">
|
||||
<value>Hour collide with another hour</value>
|
||||
</data>
|
||||
<data name="TeacherEdit.Name" xml:space="preserve">
|
||||
<value>Name</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -8,9 +8,12 @@
|
||||
<BitmapImage x:Key="ProjectSettingsImage" UriSource="Images/ProjectSettings.png"/>
|
||||
<BitmapImage x:Key="ClassroomImage" UriSource="Images/Classroom.png"/>
|
||||
<BitmapImage x:Key="ClassroomAddImage" UriSource="Images/ClassroomAdd.png"/>
|
||||
<BitmapImage x:Key="TeacherImage" UriSource="Images/Teacher.png"/>
|
||||
<BitmapImage x:Key="TeacherAddImage" UriSource="Images/TeacherAdd.png"/>
|
||||
<BitmapImage x:Key="OpenImage" UriSource="Images/Open.png"/>
|
||||
<BitmapImage x:Key="OpenRecentImage" UriSource="Images/OpenRecent.png"/>
|
||||
<BitmapImage x:Key="SaveImage" UriSource="Images/Save.png"/>
|
||||
<BitmapImage x:Key="SaveAsImage" UriSource="Images/SaveAs.png"/>
|
||||
<BitmapImage x:Key="NewImage" UriSource="Images/New.png"/>
|
||||
<BitmapImage x:Key="RightArrowImage" UriSource="Images/RightArrow.png"/>
|
||||
</ResourceDictionary>
|
||||
BIN
TimetableDesigner/Resources/Images/RightArrow.png
Normal file
BIN
TimetableDesigner/Resources/Images/RightArrow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 223 B |
BIN
TimetableDesigner/Resources/Images/Teacher.png
Normal file
BIN
TimetableDesigner/Resources/Images/Teacher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
BIN
TimetableDesigner/Resources/Images/TeacherAdd.png
Normal file
BIN
TimetableDesigner/Resources/Images/TeacherAdd.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
@@ -20,8 +20,11 @@
|
||||
<None Remove="Resources\Images\Project.png" />
|
||||
<None Remove="Resources\Images\ProjectSettings.png" />
|
||||
<None Remove="Resources\Images\Remove.png" />
|
||||
<None Remove="Resources\Images\RightArrow.png" />
|
||||
<None Remove="Resources\Images\Save.png" />
|
||||
<None Remove="Resources\Images\SaveAs.png" />
|
||||
<None Remove="Resources\Images\Teacher.png" />
|
||||
<None Remove="Resources\Images\TeacherAdd.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -52,12 +55,21 @@
|
||||
<Content Include="Resources\Images\Remove.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Images\RightArrow.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Images\Save.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Images\SaveAs.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Images\Teacher.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Images\TeacherAdd.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -67,6 +79,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TimetableDesigner.Core\TimetableDesigner.Core.csproj" />
|
||||
<ProjectReference Include="..\TimetableDesigner.Customs\TimetableDesigner.Customs.csproj" />
|
||||
<ProjectReference Include="..\TimetableDesigner.MessageBox\TimetableDesigner.MessageBox.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace TimetableDesigner.ViewModels
|
||||
public ClassroomEditTabViewModel() : this(new ClassroomViewModel(new Core.Classroom()))
|
||||
{ }
|
||||
|
||||
public ClassroomEditTabViewModel(ClassroomViewModel classroom)
|
||||
public ClassroomEditTabViewModel(ClassroomViewModel classroom) : base()
|
||||
{
|
||||
_classroom = classroom;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,9 @@ namespace TimetableDesigner.ViewModels
|
||||
public ICommand NewClassroomCommand { get; set; }
|
||||
public ICommand EditClassroomCommand { get; set; }
|
||||
public ICommand RemoveClassroomCommand { get; set; }
|
||||
public ICommand NewTeacherCommand { get; set; }
|
||||
public ICommand EditTeacherCommand { get; set; }
|
||||
public ICommand RemoveTeacherCommand { get; set; }
|
||||
|
||||
public ObservableCollection<BaseTabViewModel> Tabs
|
||||
{
|
||||
@@ -95,6 +98,9 @@ namespace TimetableDesigner.ViewModels
|
||||
NewClassroomCommand = new RelayCommand<object>(param => NewClassroom());
|
||||
EditClassroomCommand = new RelayCommand<ClassroomViewModel>(EditClassroom);
|
||||
RemoveClassroomCommand = new RelayCommand<ClassroomViewModel>(DeleteClassroom);
|
||||
NewTeacherCommand = new RelayCommand<object>(param => NewTeacher());
|
||||
EditTeacherCommand = new RelayCommand<TeacherViewModel>(EditTeacher);
|
||||
RemoveTeacherCommand = new RelayCommand<TeacherViewModel>(DeleteTeacher);
|
||||
|
||||
_tabs = new ObservableCollection<BaseTabViewModel>
|
||||
{
|
||||
@@ -189,10 +195,8 @@ namespace TimetableDesigner.ViewModels
|
||||
|
||||
private void EditClassroom(ClassroomViewModel classroomViewModel)
|
||||
{
|
||||
Debug.WriteLine("textedit");
|
||||
ClassroomEditTabViewModel classroomEdit = new ClassroomEditTabViewModel()
|
||||
ClassroomEditTabViewModel classroomEdit = new ClassroomEditTabViewModel(classroomViewModel)
|
||||
{
|
||||
Classroom = classroomViewModel,
|
||||
TabTitle = $"{Resources.Tabs_ClassroomEdit}: {classroomViewModel.Name}",
|
||||
IsTabClosable = true
|
||||
};
|
||||
@@ -202,13 +206,50 @@ namespace TimetableDesigner.ViewModels
|
||||
|
||||
private void DeleteClassroom(ClassroomViewModel classroomViewModel)
|
||||
{
|
||||
Debug.WriteLine("textdelete");
|
||||
if (Project is not null)
|
||||
{
|
||||
Project.Classrooms.Remove(classroomViewModel);
|
||||
}
|
||||
}
|
||||
|
||||
private void NewTeacher()
|
||||
{
|
||||
if (Project is not null)
|
||||
{
|
||||
Teacher teacher = new Teacher()
|
||||
{
|
||||
Name = Resources.Global_DefaultTeacherName
|
||||
};
|
||||
TeacherViewModel teacherVM = new TeacherViewModel(teacher);
|
||||
Project.Teachers.Add(teacherVM);
|
||||
EditTeacher(teacherVM);
|
||||
}
|
||||
}
|
||||
|
||||
private void EditTeacher(TeacherViewModel teacherViewModel)
|
||||
{
|
||||
if (Project is not null)
|
||||
{
|
||||
TeacherEditTabViewModel teacherEdit = new TeacherEditTabViewModel(teacherViewModel, Project.TimetableTemplate)
|
||||
{
|
||||
Teacher = teacherViewModel,
|
||||
TabTitle = $"{Resources.Tabs_TeacherEdit}: {teacherViewModel.Name}",
|
||||
IsTabClosable = true
|
||||
};
|
||||
Tabs.Add(teacherEdit);
|
||||
SelectedTab = teacherEdit;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void DeleteTeacher(TeacherViewModel teacherViewModel)
|
||||
{
|
||||
if (Project is not null)
|
||||
{
|
||||
Project.Teachers.Remove(teacherViewModel);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ namespace TimetableDesigner.ViewModels.Models
|
||||
}
|
||||
public TimetableTemplateViewModel TimetableTemplate { get; set; }
|
||||
public ObservableCollection<ClassroomViewModel> Classrooms { get; set; }
|
||||
public ObservableCollection<TeacherViewModel> Teachers { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -76,6 +77,9 @@ namespace TimetableDesigner.ViewModels.Models
|
||||
|
||||
Classrooms = new ObservableCollection<ClassroomViewModel>();
|
||||
Classrooms.CollectionChanged += Classrooms_CollectionChanged;
|
||||
|
||||
Teachers = new ObservableCollection<TeacherViewModel>();
|
||||
Teachers.CollectionChanged += Teachers_CollectionChanged;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -106,6 +110,28 @@ namespace TimetableDesigner.ViewModels.Models
|
||||
}
|
||||
}
|
||||
|
||||
private void Teachers_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
IList<TeacherViewModel>? added = e.NewItems as IList<TeacherViewModel>;
|
||||
IList<TeacherViewModel>? removed = e.OldItems as IList<TeacherViewModel>;
|
||||
|
||||
if (removed is not null)
|
||||
{
|
||||
foreach (TeacherViewModel vm in removed)
|
||||
{
|
||||
_project.Teachers.Remove(vm.Teacher);
|
||||
}
|
||||
}
|
||||
|
||||
if (added is not null)
|
||||
{
|
||||
foreach (TeacherViewModel vm in added)
|
||||
{
|
||||
_project.Teachers.Add(vm.Teacher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using TimetableDesigner.Core;
|
||||
using TimetableDesigner.Customs.Collections;
|
||||
using TimetableDesigner.ViewModels.Base;
|
||||
|
||||
namespace TimetableDesigner.ViewModels.Models
|
||||
@@ -46,6 +48,7 @@ namespace TimetableDesigner.ViewModels.Models
|
||||
}
|
||||
}
|
||||
}
|
||||
public ObservableDictionary<TimetableDay, ObservableCollection<TimetableSpan>> AvailabilityHours => new ObservableDictionary<TimetableDay, ObservableCollection<TimetableSpan>>(Teacher.AvailabilityHours.ToDictionary(i => i.Key, i => new ObservableCollection<TimetableSpan>(i.Value)));
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -59,5 +62,44 @@ namespace TimetableDesigner.ViewModels.Models
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public void AddDay(TimetableDay day)
|
||||
{
|
||||
if (day is not null)
|
||||
{
|
||||
Teacher.AvailabilityHours.Add(day, new TimetableSpanCollection());
|
||||
NotifyPropertyChanged(nameof(AvailabilityHours));
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveDay(TimetableDay day)
|
||||
{
|
||||
Teacher.AvailabilityHours.Remove(day);
|
||||
NotifyPropertyChanged(nameof(AvailabilityHours));
|
||||
}
|
||||
|
||||
public void AddHours(TimetableDay day, TimetableSpan hours)
|
||||
{
|
||||
if (day is not null && hours is not null)
|
||||
{
|
||||
Teacher.AvailabilityHours[day].Add(hours);
|
||||
NotifyPropertyChanged(nameof(AvailabilityHours));
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveHours(TimetableDay day, TimetableSpan hours)
|
||||
{
|
||||
if (day is not null)
|
||||
{
|
||||
Teacher.AvailabilityHours[day].Remove(hours);
|
||||
NotifyPropertyChanged(nameof(AvailabilityHours));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace TimetableDesigner.ViewModels.Models
|
||||
public TimetableTemplate TimetableTemplate => _timetableTemplate;
|
||||
|
||||
public ObservableCollection<TimetableDay> Days => new ObservableCollection<TimetableDay>(_timetableTemplate.Days);
|
||||
public ObservableCollection<TimetableSlot> Slots => new ObservableCollection<TimetableSlot>(_timetableTemplate.Slots);
|
||||
public ObservableCollection<TimetableSpan> Slots => new ObservableCollection<TimetableSpan>(_timetableTemplate.Slots);
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -55,13 +55,13 @@ namespace TimetableDesigner.ViewModels.Models
|
||||
NotifyPropertyChanged(nameof(Days));
|
||||
}
|
||||
|
||||
public void AddSlot(TimetableSlot slot)
|
||||
public void AddSlot(TimetableSpan slot)
|
||||
{
|
||||
_timetableTemplate.AddSlot(slot);
|
||||
NotifyPropertyChanged(nameof(Slots));
|
||||
}
|
||||
|
||||
public void RemoveSlot(TimetableSlot slot)
|
||||
public void RemoveSlot(TimetableSpan slot)
|
||||
{
|
||||
_timetableTemplate.RemoveSlot(slot);
|
||||
NotifyPropertyChanged(nameof(Slots));
|
||||
|
||||
@@ -73,14 +73,14 @@ namespace TimetableDesigner.ViewModels
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public ProjectSettingsTabViewModel()
|
||||
public ProjectSettingsTabViewModel() : base()
|
||||
{
|
||||
Project = new ProjectViewModel(new Project());
|
||||
|
||||
AddDayCommand = new RelayCommand<object>(param => AddDay());
|
||||
AddSlotCommand = new RelayCommand<object>(param => AddSlot());
|
||||
RemoveDayCommand = new RelayCommand<TimetableDay>(RemoveDay);
|
||||
RemoveSlotCommand = new RelayCommand<TimetableSlot>(RemoveSlot);
|
||||
RemoveSlotCommand = new RelayCommand<TimetableSpan>(RemoveSlot);
|
||||
|
||||
_newDayName = string.Empty;
|
||||
_newSlotFrom = new DateTime(1, 1, 1, 8, 0, 0);
|
||||
@@ -118,7 +118,7 @@ namespace TimetableDesigner.ViewModels
|
||||
|
||||
try
|
||||
{
|
||||
Project.TimetableTemplate.AddSlot(new TimetableSlot(from, to));
|
||||
Project.TimetableTemplate.AddSlot(new TimetableSpan(from, to));
|
||||
|
||||
double delta = (to - from).TotalMinutes;
|
||||
DateTime newFrom = NewSlotTo.Value;
|
||||
@@ -133,7 +133,7 @@ namespace TimetableDesigner.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveSlot(TimetableSlot slot) => Project.TimetableTemplate.RemoveSlot(slot);
|
||||
private void RemoveSlot(TimetableSpan slot) => Project.TimetableTemplate.RemoveSlot(slot);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,13 +1,188 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using TimetableDesigner.Commands;
|
||||
using TimetableDesigner.Core;
|
||||
using TimetableDesigner.MessageBox;
|
||||
using TimetableDesigner.Properties;
|
||||
using TimetableDesigner.ViewModels.Base;
|
||||
using TimetableDesigner.ViewModels.Models;
|
||||
|
||||
namespace TimetableDesigner.ViewModels
|
||||
{
|
||||
public class TeacherEditTabViewModel : BaseViewModel
|
||||
public class TeacherEditTabViewModel : BaseTabViewModel
|
||||
{
|
||||
#region FIELDS
|
||||
|
||||
private TeacherViewModel _teacher;
|
||||
private TimetableTemplateViewModel _timetableTemplate;
|
||||
|
||||
private TimetableDay _selectedDay;
|
||||
|
||||
private TimetableDay _selectedNewDay;
|
||||
private DateTime? _newHourFrom;
|
||||
private DateTime? _newHourTo;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public TeacherViewModel Teacher
|
||||
{
|
||||
get => _teacher;
|
||||
set
|
||||
{
|
||||
if (value != _teacher)
|
||||
{
|
||||
_teacher = value;
|
||||
NotifyPropertyChanged(nameof(Teacher));
|
||||
}
|
||||
}
|
||||
}
|
||||
public TimetableTemplateViewModel TimetableTemplate
|
||||
{
|
||||
get => _timetableTemplate;
|
||||
set
|
||||
{
|
||||
if (value != _timetableTemplate)
|
||||
{
|
||||
_timetableTemplate = value;
|
||||
NotifyPropertyChanged(nameof(TimetableTemplate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand AddDayCommand { get; set; }
|
||||
public ICommand RemoveDayCommand { get; set; }
|
||||
public ICommand AddHourCommand { get; set; }
|
||||
public ICommand RemoveHourCommand { get; set; }
|
||||
|
||||
public TimetableDay SelectedDay
|
||||
{
|
||||
get => _selectedDay;
|
||||
set
|
||||
{
|
||||
if (_selectedDay != value)
|
||||
{
|
||||
_selectedDay = value;
|
||||
NotifyPropertyChanged(nameof(SelectedDay));
|
||||
NotifyPropertyChanged(nameof(SelectedDayHours));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TimetableDay SelectedNewDay
|
||||
{
|
||||
get => _selectedNewDay;
|
||||
set
|
||||
{
|
||||
if (_selectedNewDay != value)
|
||||
{
|
||||
_selectedNewDay = value;
|
||||
NotifyPropertyChanged(nameof(SelectedNewDay));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime? NewHourFrom
|
||||
{
|
||||
get => _newHourFrom;
|
||||
set
|
||||
{
|
||||
_newHourFrom = value;
|
||||
NotifyPropertyChanged(nameof(NewHourFrom));
|
||||
}
|
||||
}
|
||||
public DateTime? NewHourTo
|
||||
{
|
||||
get => _newHourTo;
|
||||
set
|
||||
{
|
||||
_newHourTo = value;
|
||||
NotifyPropertyChanged(nameof(NewHourTo));
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<TimetableSpan> SelectedDayHours => SelectedDay is not null ? Teacher.AvailabilityHours[SelectedDay] : new ObservableCollection<TimetableSpan>();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public TeacherEditTabViewModel() : this(new TeacherViewModel(new Core.Teacher()), new TimetableTemplateViewModel(new Core.TimetableTemplate()))
|
||||
{ }
|
||||
|
||||
public TeacherEditTabViewModel(TeacherViewModel teacher, TimetableTemplateViewModel timetableTemplate) : base()
|
||||
{
|
||||
_teacher = teacher;
|
||||
_timetableTemplate = timetableTemplate;
|
||||
|
||||
AddDayCommand = new RelayCommand<object>(param => AddDay());
|
||||
RemoveDayCommand = new RelayCommand<TimetableDay>(RemoveDay);
|
||||
AddHourCommand = new RelayCommand<object>(param => AddHour());
|
||||
RemoveHourCommand = new RelayCommand<TimetableSpan>(RemoveHour);
|
||||
|
||||
_newHourFrom = new DateTime(1, 1, 1, 8, 0, 0);
|
||||
_newHourTo = new DateTime(1, 1, 1, 16, 0, 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private void AddDay()
|
||||
{
|
||||
if (!Teacher.AvailabilityHours.ContainsKey(SelectedNewDay))
|
||||
{
|
||||
Teacher.AddDay(SelectedNewDay);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveDay(TimetableDay day)
|
||||
{
|
||||
Teacher.RemoveDay(day);
|
||||
}
|
||||
|
||||
private void AddHour()
|
||||
{
|
||||
if (NewHourFrom.HasValue && NewHourTo.HasValue)
|
||||
{
|
||||
TimeOnly from = new TimeOnly(NewHourFrom.Value.Hour, NewHourFrom.Value.Minute);
|
||||
TimeOnly to = new TimeOnly(NewHourTo.Value.Hour, NewHourTo.Value.Minute);
|
||||
if (from >= to)
|
||||
{
|
||||
MessageBoxService.ShowError(Resources.TeacherEdit_Message_FromHigherThanTo);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Teacher.AddHours(SelectedDay, new TimetableSpan(from, to));
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
MessageBoxService.ShowError(Resources.TeacherEdit_Message_HourCollision);
|
||||
}
|
||||
}
|
||||
NotifyPropertyChanged(nameof(SelectedDayHours));
|
||||
}
|
||||
|
||||
private void RemoveHour(TimetableSpan hour)
|
||||
{
|
||||
Teacher.RemoveHours(SelectedDay, hour);
|
||||
NotifyPropertyChanged(nameof(SelectedDayHours));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
</rib:RibbonGroupBox>
|
||||
<rib:RibbonGroupBox Header="{x:Static p:Resources.Main_Ribbon_Project_New}">
|
||||
<rib:Button Header="{x:Static p:Resources.Main_Ribbon_Project_New_NewClassroom}" Icon="{StaticResource ClassroomAddImage}" Command="{Binding NewClassroomCommand}"/>
|
||||
<rib:Button Header="{x:Static p:Resources.Main_Ribbon_Project_New_NewTeacher}"/>
|
||||
<rib:Button Header="{x:Static p:Resources.Main_Ribbon_Project_New_NewTeacher}" Icon="{StaticResource TeacherAddImage}" Command="{Binding NewTeacherCommand}"/>
|
||||
<rib:Button Header="{x:Static p:Resources.Main_Ribbon_Project_New_NewGroup}"/>
|
||||
<rib:Button Header="{x:Static p:Resources.Main_Ribbon_Project_New_NewSubgroup}"/>
|
||||
<rib:Button Header="{x:Static p:Resources.Main_Ribbon_Project_New_NewClass}"/>
|
||||
@@ -76,9 +76,7 @@
|
||||
<DataTemplate>
|
||||
<TreeViewItem Header="{Binding Name}" Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=rib:RibbonWindow}, Path=DataContext}">
|
||||
<TreeViewItem.InputBindings>
|
||||
<MouseBinding Gesture="LeftDoubleClick"
|
||||
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=rib:RibbonWindow}, Path=DataContext.EditClassroomCommand}"
|
||||
CommandParameter="{Binding}"/>
|
||||
<MouseBinding Gesture="LeftDoubleClick"/>
|
||||
</TreeViewItem.InputBindings>
|
||||
<TreeViewItem.ContextMenu>
|
||||
<ContextMenu>
|
||||
@@ -96,6 +94,35 @@
|
||||
</DataTemplate>
|
||||
</TreeViewItem.ItemTemplate>
|
||||
</TreeViewItem>
|
||||
<TreeViewItem ItemsSource="{Binding Teachers}">
|
||||
<TreeViewItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="{StaticResource TeacherImage}" Width="18" Height="18"/>
|
||||
<Label Content="{x:Static p:Resources.Main_Treeview_Teachers}"/>
|
||||
</StackPanel>
|
||||
</TreeViewItem.Header>
|
||||
<TreeViewItem.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TreeViewItem Header="{Binding Name}" Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=rib:RibbonWindow}, Path=DataContext}">
|
||||
<TreeViewItem.InputBindings>
|
||||
<MouseBinding Gesture="LeftDoubleClick"/>
|
||||
</TreeViewItem.InputBindings>
|
||||
<TreeViewItem.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="{x:Static p:Resources.Main_Treeview_ContextMenu_EditTimetable}"/>
|
||||
<MenuItem Header="{x:Static p:Resources.Main_Treeview_Teachers_ContextMenu_Edit}"
|
||||
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget.Tag.EditTeacherCommand}"
|
||||
CommandParameter="{Binding}"/>
|
||||
<Separator/>
|
||||
<MenuItem Header="{x:Static p:Resources.Main_Treeview_ContextMenu_Remove}"
|
||||
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget.Tag.RemoveTeacherCommand}"
|
||||
CommandParameter="{Binding}"/>
|
||||
</ContextMenu>
|
||||
</TreeViewItem.ContextMenu>
|
||||
</TreeViewItem>
|
||||
</DataTemplate>
|
||||
</TreeViewItem.ItemTemplate>
|
||||
</TreeViewItem>
|
||||
</TreeViewItem>
|
||||
</TreeView>
|
||||
<GridSplitter Grid.Row="1" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Stretch" ShowsPreview="True" Width="2"/>
|
||||
|
||||
@@ -3,10 +3,117 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:TimetableDesigner.Views"
|
||||
xmlns:p = "clr-namespace:TimetableDesigner.Properties"
|
||||
xmlns:vm="clr-namespace:TimetableDesigner.ViewModels"
|
||||
xmlns:local="clr-namespace:TimetableDesigner.Views" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
|
||||
</Grid>
|
||||
<UserControl.DataContext>
|
||||
<vm:TeacherEditTabViewModel/>
|
||||
</UserControl.DataContext>
|
||||
<ScrollViewer>
|
||||
<StackPanel>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Label Grid.Column="0" Grid.Row="0" Content="{x:Static p:Resources.TeacherEdit_Name}"/>
|
||||
<TextBox Grid.Column="1" Grid.Row="0" Margin="5" Text="{Binding Teacher.Name}" MaxLines="1"/>
|
||||
<Label Grid.Column="0" Grid.Row="1" Content="{x:Static p:Resources.TeacherEdit_Description}"/>
|
||||
<TextBox Grid.Column="1" Grid.Row="1" Margin="5" MinLines="3" Text="{Binding Teacher.Description}"/>
|
||||
</Grid>
|
||||
<StackPanel>
|
||||
<Label Content="{x:Static p:Resources.TeacherEdit_AvailabilityHours}"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Border Grid.Column="0" Grid.Row="0" Margin="5" BorderThickness="1" BorderBrush="DarkGray">
|
||||
<ListBox MinHeight="100" ItemsSource="{Binding Teacher.AvailabilityHours.Keys}" SelectedItem="{Binding SelectedDay}" HorizontalContentAlignment="Stretch">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid HorizontalAlignment="Stretch" Margin="5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label Grid.Column="0" HorizontalAlignment="Stretch" Content="{Binding Name}"/>
|
||||
<Button Grid.Column="1"
|
||||
Width="26"
|
||||
Height="26"
|
||||
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=DataContext.RemoveDayCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=DataContext}">
|
||||
<Image Source="{StaticResource RemoveImage}" Width="20" Height="20"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
<Image Grid.Column="1" Grid.Row="0" VerticalAlignment="Center" Source="{StaticResource RightArrowImage}" Width="20" Height="20"/>
|
||||
<Border Grid.Column="2" Grid.Row="0" Margin="5" BorderThickness="1" BorderBrush="DarkGray">
|
||||
<ListBox ItemsSource="{Binding SelectedDayHours}" HorizontalContentAlignment="Stretch">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid HorizontalAlignment="Stretch" Margin="5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" HorizontalAlignment="Stretch" Orientation="Horizontal">
|
||||
<Label Content="{Binding From}"/>
|
||||
<Label Content=" - "/>
|
||||
<Label Content="{Binding To}"/>
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1"
|
||||
Width="26"
|
||||
Height="26"
|
||||
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=DataContext.RemoveHourCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=DataContext}">
|
||||
<Image Source="{StaticResource RemoveImage}" Width="20" Height="20"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
<Grid Grid.Column="0" Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ComboBox Grid.Column="0" Margin="5" ItemsSource="{Binding TimetableTemplate.Days}" SelectedItem="{Binding SelectedNewDay}" DisplayMemberPath="Name"/>
|
||||
<Button Grid.Column="1" Margin="5" Width="26" Height="26" Command="{Binding AddDayCommand}">
|
||||
<Image Source="{StaticResource AddImage}" Width="20" Height="20"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Column="2" Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<xctk:DateTimeUpDown Grid.Column="0" Margin="5" Format="ShortTime" Value="{Binding NewHourFrom}"/>
|
||||
<Label Grid.Column="1" Margin="5" Content="-"/>
|
||||
<xctk:DateTimeUpDown Grid.Column="2" Margin="5" Format="ShortTime" Value="{Binding NewHourTo}"/>
|
||||
<Button Grid.Column="3" Margin="5" Command="{Binding AddHourCommand}" Width="26" Height="26">
|
||||
<Image Source="{StaticResource AddImage}" Width="20" Height="20"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
||||
Reference in New Issue
Block a user