Entry page is now a Hub

EntryDetailVM and GroupDetailVM are now singleton
Read-only Additional fields and Attachments
This commit is contained in:
Geoffroy BONNEVILLE
2020-05-06 18:54:39 +02:00
parent 1488c3244f
commit ca04a6c8ee
15 changed files with 369 additions and 122 deletions

View File

@@ -29,6 +29,7 @@ namespace ModernKeePass.Application.Entry.Models
public bool HasExpirationDate { get; set; }
public DateTimeOffset ExpirationDate { get; set; }
public DateTimeOffset ModificationDate { get; set; }
public Dictionary<string, byte[]> Attachments { get; set; }
public override string ToString()
{
@@ -53,7 +54,8 @@ namespace ModernKeePass.Application.Entry.Models
.ForMember(d => d.ModificationDate, opts => opts.MapFrom(s => s.LastModificationDate))
.ForMember(d => d.Icon, opts => opts.MapFrom(s => s.HasExpirationDate && s.ExpirationDate < DateTimeOffset.Now ? Icon.ReportHacked : s.Icon))
.ForMember(d => d.ForegroundColor, opts => opts.MapFrom(s => s.ForegroundColor))
.ForMember(d => d.BackgroundColor, opts => opts.MapFrom(s => s.BackgroundColor));
.ForMember(d => d.BackgroundColor, opts => opts.MapFrom(s => s.BackgroundColor))
.ForMember(d => d.Attachments, opts => opts.MapFrom(s => s.Attachments));
}
}
}

View File

@@ -76,7 +76,9 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Common\Constants.cs" />
<Compile Include="Dtos\Attachment.cs" />
<Compile Include="Dtos\Credentials.cs" />
<Compile Include="Dtos\Field.cs" />
<Compile Include="Dtos\FileInfo.cs" />
<Compile Include="Dtos\PasswordGenerationOptions.cs" />
<Compile Include="Entities\BaseEntity.cs" />

View File

@@ -0,0 +1,8 @@
namespace ModernKeePass.Domain.Dtos
{
public class Attachment
{
public string Name { get; set; }
public byte[] Content { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace ModernKeePass.Domain.Dtos
{
public class Field
{
public string Name { get; set; }
public string Value { get; set; }
}
}

View File

@@ -18,5 +18,6 @@ namespace ModernKeePass.Domain.Entities
public Color ForegroundColor { get; set; }
public Color BackgroundColor { get; set; }
public bool HasExpirationDate { get; set; }
public Dictionary<string, byte[]> Attachments { get; set; } = new Dictionary<string, byte[]>();
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using ModernKeePass.Domain.Entities;
@@ -27,7 +28,8 @@ namespace ModernKeePass.Infrastructure.KeePass
.ForMember(dest => dest.AdditionalFields, opt => opt.MapFrom(src =>
src.Strings.Where(s => !PwDefs.GetStandardFields().Contains(s.Key))
.ToDictionary(s => s.Key, s => GetEntryValue(src, s.Key))))
.ForMember(dest => dest.LastModificationDate, opt => opt.MapFrom(src => new DateTimeOffset(src.LastModificationTime)));
.ForMember(dest => dest.LastModificationDate, opt => opt.MapFrom(src => new DateTimeOffset(src.LastModificationTime)))
.ForMember(dest => dest.Attachments, opt => opt.MapFrom(src => src.Binaries.Select(b => new KeyValuePair<string, byte[]> (b.Key, b.Value.ReadData()) )));
}
private string GetEntryValue(PwEntry entry, string key) => entry.Strings.GetSafe(key).ReadString();

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/2010/manifest" xmlns:m2="http://schemas.microsoft.com/appx/2013/manifest">
<Identity Name="wismna.ModernKeePass" Publisher="CN=0719A91A-C322-4EE0-A257-E60733EECF06" Version="1.17.0.12" />
<Identity Name="wismna.ModernKeePass" Publisher="CN=0719A91A-C322-4EE0-A257-E60733EECF06" Version="1.18.0.12" />
<Properties>
<DisplayName>ModernKeePass</DisplayName>
<PublisherDisplayName>wismna</PublisherDisplayName>

View File

@@ -516,4 +516,19 @@
<data name="NewGroupTextBox.ButtonTooltip" xml:space="preserve">
<value>New group name</value>
</data>
<data name="EntryHubAdditional.Header" xml:space="preserve">
<value>Additional</value>
</data>
<data name="EntryHubAttachments.Header" xml:space="preserve">
<value>Attachments</value>
</data>
<data name="EntryHubMain.Header" xml:space="preserve">
<value>Main</value>
</data>
<data name="EntryHubOther.Header" xml:space="preserve">
<value>Other</value>
</data>
<data name="EntryIcon.Text" xml:space="preserve">
<value>Icon</value>
</data>
</root>

View File

@@ -516,4 +516,19 @@
<data name="NewGroupTextBox.ButtonTooltip" xml:space="preserve">
<value>Nom du groupe</value>
</data>
<data name="EntryHubAdditional.Header" xml:space="preserve">
<value>Additionnel</value>
</data>
<data name="EntryHubAttachments.Header" xml:space="preserve">
<value>Pièce jointes</value>
</data>
<data name="EntryHubMain.Header" xml:space="preserve">
<value>Principal</value>
</data>
<data name="EntryHubOther.Header" xml:space="preserve">
<value>Autres</value>
</data>
<data name="EntryIcon.Text" xml:space="preserve">
<value>Icone</value>
</data>
</root>

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
@@ -29,6 +28,7 @@ using ModernKeePass.Application.Security.Queries.EstimatePasswordComplexity;
using ModernKeePass.Domain.Enums;
using ModernKeePass.Application.Group.Models;
using ModernKeePass.Common;
using ModernKeePass.Domain.Dtos;
using ModernKeePass.Domain.Exceptions;
using ModernKeePass.Extensions;
using ModernKeePass.Models;
@@ -63,8 +63,9 @@ namespace ModernKeePass.ViewModels
}
}
public IEnumerable<GroupVm> BreadCrumb => new List<GroupVm> { _parent };
public ObservableCollection<EntryVm> History { get; private set; }
public ObservableCollection<Field> AdditionalFields { get; private set; }
public ObservableCollection<Attachment> Attachments { get; private set; }
/// <summary>
/// Determines if the Entry is current or from history
@@ -76,10 +77,24 @@ namespace ModernKeePass.ViewModels
get { return _selectedItem; }
set
{
Set(() => SelectedItem, ref _selectedItem, value);
if (value != null) RaisePropertyChanged();
Set(() => SelectedItem, ref _selectedItem, value, true);
if (value != null)
{
AdditionalFields = new ObservableCollection<Field>(SelectedItem.AdditionalFields.Select(f => new Field
{
Name = f.Key,
Value = f.Value
}));
Attachments = new ObservableCollection<Attachment>(SelectedItem.Attachments.Select(f => new Attachment
{
Name = f.Key,
Content = f.Value
}));
RaisePropertyChanged(string.Empty);
}
}
}
public int SelectedIndex
{
get { return _selectedIndex; }
@@ -235,6 +250,7 @@ namespace ModernKeePass.ViewModels
public RelayCommand DeleteCommand { get; }
public RelayCommand GoBackCommand { get; }
public RelayCommand GoToParentCommand { get; set; }
public RelayCommand<Attachment> OpenAttachmentCommand { get; set; }
private DatabaseVm Database => _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult();
@@ -243,6 +259,7 @@ namespace ModernKeePass.ViewModels
private readonly IResourceProxy _resource;
private readonly IDialogService _dialog;
private readonly INotificationService _notification;
private readonly IFileProxy _file;
private GroupVm _parent;
private EntryVm _selectedItem;
private int _selectedIndex;
@@ -251,13 +268,14 @@ namespace ModernKeePass.ViewModels
private double _passwordLength = 25;
private bool _isDirty;
public EntryDetailVm(IMediator mediator, INavigationService navigation, IResourceProxy resource, IDialogService dialog, INotificationService notification)
public EntryDetailVm(IMediator mediator, INavigationService navigation, IResourceProxy resource, IDialogService dialog, INotificationService notification, IFileProxy file)
{
_mediator = mediator;
_navigation = navigation;
_resource = resource;
_dialog = dialog;
_notification = notification;
_file = file;
SaveCommand = new RelayCommand(async () => await SaveChanges(), () => Database.IsDirty);
GeneratePasswordCommand = new RelayCommand(async () => await GeneratePassword());
@@ -266,10 +284,12 @@ namespace ModernKeePass.ViewModels
DeleteCommand = new RelayCommand(async () => await AskForDelete());
GoBackCommand = new RelayCommand(() => _navigation.GoBack());
GoToParentCommand = new RelayCommand(() => GoToGroup(_parent.Id));
OpenAttachmentCommand = new RelayCommand<Attachment>(async attachment => await OpenAttachment(attachment));
MessengerInstance.Register<DatabaseSavedMessage>(this, _ => SaveCommand.RaiseCanExecuteChanged());
}
public async Task Initialize(string entryId)
{
SelectedItem = await _mediator.Send(new GetEntryQuery { Id = entryId });
@@ -355,6 +375,11 @@ namespace ModernKeePass.ViewModels
if (_isDirty) await _mediator.Send(new AddHistoryCommand { Entry = History[0] });
}
public void GoToGroup(string groupId)
{
_navigation.NavigateTo(Constants.Navigation.GroupPage, new NavigationItem { Id = groupId });
}
private async Task RestoreHistory()
{
await _mediator.Send(new RestoreHistoryCommand { Entry = History[0], HistoryIndex = History.Count - SelectedIndex - 1 });
@@ -389,9 +414,15 @@ namespace ModernKeePass.ViewModels
_navigation.GoBack();
}
public void GoToGroup(string groupId)
private async Task OpenAttachment(Attachment attachment)
{
_navigation.NavigateTo(Constants.Navigation.GroupPage, new NavigationItem { Id = groupId });
var extensionIndex = attachment.Name.LastIndexOf('.');
var fileInfo = await _file.CreateFile(attachment.Name,
attachment.Name.Substring(extensionIndex, attachment.Name.Length - extensionIndex),
string.Empty,
false);
if (fileInfo == null) return;
await _file.WriteBinaryContentsToFile(fileInfo.Id, attachment.Content);
}
}
}

View File

@@ -37,7 +37,7 @@ namespace ModernKeePass.ViewModels
public MainVm Main => ServiceLocator.Current.GetInstance<MainVm>(Guid.NewGuid().ToString());
public SettingsVm Settings => ServiceLocator.Current.GetInstance<SettingsVm>(Guid.NewGuid().ToString());
public GroupDetailVm Group => ServiceLocator.Current.GetInstance<GroupDetailVm>(Guid.NewGuid().ToString());
public EntryDetailVm Entry => ServiceLocator.Current.GetInstance<EntryDetailVm>(Guid.NewGuid().ToString());
public GroupDetailVm Group => ServiceLocator.Current.GetInstance<GroupDetailVm>();
public EntryDetailVm Entry => ServiceLocator.Current.GetInstance<EntryDetailVm>();
}
}

View File

@@ -380,20 +380,25 @@
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
<Grid Grid.Column="1">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="20,0,0,20">
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Hub Padding="0">
<Hub.Resources>
<Style TargetType="TextBlock" x:Key="EntryTextBlockStyle">
<Setter Property="Margin" Value="0,20,0,0"/>
<Setter Property="FontSize" Value="18"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</Hub.Resources>
<HubSection x:Uid="EntryHubMain" IsHeaderInteractive="True">
<DataTemplate>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="20,0,0,20" MinWidth="400">
<StackPanel.Resources>
<Style TargetType="CheckBox">
<Setter Property="Margin" Value="0,20,0,0"/>
<Setter Property="FontSize" Value="18"/>
</Style>
</StackPanel.Resources>
<TextBlock x:Uid="EntryLogin" />
<TextBlock x:Uid="EntryLogin" Style="{StaticResource EntryTextBlockStyle}" />
<local:TextBoxWithButton x:Uid="LoginTextBox" Text="{Binding UserName, Mode=TwoWay}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonSymbol="&#xE16F;" IsEnabled="{Binding IsCurrentEntry}">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ButtonClick">
@@ -402,7 +407,7 @@
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</local:TextBoxWithButton>
<TextBlock x:Uid="EntryPassword" />
<TextBlock x:Uid="EntryPassword" Style="{StaticResource EntryTextBlockStyle}" />
<local:PasswordBoxWithButton Password="{Binding Password, Mode=TwoWay}" IsPasswordRevealEnabled="True" Visibility="{Binding IsRevealPassword, Converter={StaticResource InverseBooleanToVisibilityConverter}}" Style="{StaticResource PasswordBoxWithButtonStyle}" IsEnabled="{Binding IsCurrentEntry}" ButtonSymbol="&#xE15E;" />
<local:TextBoxWithButton x:Uid="PasswordTextBox" Text="{Binding Password, Mode=TwoWay}" Visibility="{Binding IsRevealPassword, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonSymbol="&#xE16F;" IsEnabled="{Binding IsCurrentEntry}">
<interactivity:Interaction.Behaviors>
@@ -414,7 +419,7 @@
</local:TextBoxWithButton>
<ProgressBar Value="{Binding PasswordComplexityIndicator, ConverterParameter=0\,128, Converter={StaticResource ProgressBarLegalValuesConverter}}" Maximum="128" Width="350" HorizontalAlignment="Left" Foreground="{Binding PasswordComplexityIndicator, ConverterParameter=128, Converter={StaticResource DoubleToForegroundBrushComplexityConverter}}" />
<CheckBox x:Uid="EntryShowPassword" HorizontalAlignment="Left" Margin="-3,0,0,0" IsChecked="{Binding IsRevealPassword, Mode=TwoWay}" IsEnabled="{Binding IsRevealPasswordEnabled}" />
<TextBlock TextWrapping="Wrap" Text="URL" FontSize="18"/>
<TextBlock Text="URL" Style="{StaticResource EntryTextBlockStyle}"/>
<local:TextBoxWithButton x:Uid="UrlTextBox" Text="{Binding Url, Mode=TwoWay}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonSymbol="&#xE111;" IsEnabled="{Binding IsCurrentEntry}">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ButtonClick">
@@ -422,7 +427,7 @@
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</local:TextBoxWithButton>
<TextBlock x:Uid="EntryNotes" />
<TextBlock x:Uid="EntryNotes" Style="{StaticResource EntryTextBlockStyle}" />
<TextBox HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding Notes, Mode=TwoWay}" Width="350" Height="200" AcceptsReturn="True" IsSpellCheckEnabled="True" IsEnabled="{Binding IsCurrentEntry}" />
<CheckBox x:Uid="EntryExpirationDate" IsChecked="{Binding HasExpirationDate, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" />
<Grid>
@@ -440,18 +445,73 @@
<TimePicker Time="{Binding ExpiryTime, Mode=TwoWay}" />
</StackPanel>
</Grid>
<StackPanel x:Name="EditDesign" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}" Orientation="Horizontal">
<StackPanel Width="250" HorizontalAlignment="Left">
<TextBlock x:Uid="EntryBackgroundColor" />
<userControls:ColorPickerUserControl SelectedColor="{Binding BackgroundColor, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" />
</StackPanel>
<StackPanel Width="250" HorizontalAlignment="Left">
<TextBlock x:Uid="EntryForegroundColor" />
<userControls:ColorPickerUserControl SelectedColor="{Binding ForegroundColor, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" />
</StackPanel>
</StackPanel>
</StackPanel>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Small">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpirationDatePanel" Storyboard.TargetProperty="Orientation">
<DiscreteObjectKeyFrame KeyTime="0" Value="Vertical"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Large">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpirationDatePanel" Storyboard.TargetProperty="Orientation">
<DiscreteObjectKeyFrame KeyTime="0" Value="Horizontal"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</ScrollViewer>
</DataTemplate>
</HubSection>
<HubSection x:Uid="EntryHubAdditional" IsHeaderInteractive="True">
<DataTemplate>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding AdditionalFields}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}" Style="{StaticResource EntryTextBlockStyle}" />
<TextBox HorizontalAlignment="Left" Text="{Binding Value, Mode=TwoWay}" Width="350" IsEnabled="{Binding Source={StaticResource Locator}, Path=Entry.IsCurrentEntry}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DataTemplate>
</HubSection>
<HubSection x:Uid="EntryHubOther" IsHeaderInteractive="True">
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock x:Uid="EntryIcon" Style="{StaticResource EntryTextBlockStyle}" />
<userControls:SymbolPickerUserControl SelectedSymbol="{Binding Icon, Mode=TwoWay}" HorizontalAlignment="Left" />
<TextBlock x:Uid="EntryBackgroundColor" Style="{StaticResource EntryTextBlockStyle}" />
<userControls:ColorPickerUserControl SelectedColor="{Binding BackgroundColor, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" Width="350" />
<TextBlock x:Uid="EntryForegroundColor" Style="{StaticResource EntryTextBlockStyle}" />
<userControls:ColorPickerUserControl SelectedColor="{Binding ForegroundColor, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" Width="350" />
</StackPanel>
</DataTemplate>
</HubSection>
<HubSection x:Uid="EntryHubAttachments" IsHeaderInteractive="True">
<DataTemplate>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Attachments}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<HyperlinkButton Content="{Binding Name}" Command="{Binding Source={StaticResource Locator}, Path=Entry.OpenAttachmentCommand}" CommandParameter="{Binding}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DataTemplate>
</HubSection>
</Hub>
</Grid>
</Grid>
<!-- Bouton Précédent et titre de la page -->
@@ -482,10 +542,7 @@
<ToolTip Content="{Binding ParentGroupName}" />
</ToolTipService.ToolTip>
</Button>
<Viewbox MaxHeight="200" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}">
<userControls:SymbolPickerUserControl Width="100" Height="70" SelectedSymbol="{Binding Icon, Mode=TwoWay}" />
</Viewbox>
<Viewbox MaxHeight="200" Visibility="{Binding IsEditMode, Converter={StaticResource InverseBooleanToVisibilityConverter}}">
<Viewbox MaxHeight="200">
<SymbolIcon Symbol="{Binding Icon}" Width="100" Height="70" />
</Viewbox>
<TextBox
@@ -526,29 +583,5 @@
</interactivity:Interaction.Behaviors>
</userControls:TopMenuUserControl>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Small">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpirationDatePanel" Storyboard.TargetProperty="Orientation">
<DiscreteObjectKeyFrame KeyTime="0" Value="Vertical"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="EditDesign" Storyboard.TargetProperty="Orientation">
<DiscreteObjectKeyFrame KeyTime="0" Value="Vertical"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Large">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpirationDatePanel" Storyboard.TargetProperty="Orientation">
<DiscreteObjectKeyFrame KeyTime="0" Value="Horizontal"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="EditDesign" Storyboard.TargetProperty="Orientation">
<DiscreteObjectKeyFrame KeyTime="0" Value="Horizontal"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</Page>

View File

@@ -1,5 +0,0 @@
Redesign of the UI (top menu and hamburger menu)
Resuming the app re-opens previously opened database
Notify user and show the Save As dialog when an error is encountered during saving
Save As actually works now
Creating a new group is now done inline

View File

@@ -1,5 +0,0 @@
Redesign de l'interface utilisateur (menu superieur et menu hamburger)
Le re-chargement de l'app re-ouvre la base de donnees ouverte precedemment
L'utlisateur est prevenu et un popup de sauvegarde est affiche quand il y a une erreur de sauvegarde
La fonctionnalite Sauvegarder Sous fonctionne correctement
La creation d'un nouveau groupe se fait directement dans le menu

View File

@@ -0,0 +1,140 @@
using System.Collections.Generic;
using System.Linq;
using Windows.Foundation.Collections;
namespace ModernKeePass.Common
{
/// <summary>
/// Implementation of IObservableMap that supports reentrancy for use as a default view
/// model.
/// </summary>
public class ObservableDictionary : IObservableMap<string, object>
{
private class ObservableDictionaryChangedEventArgs : IMapChangedEventArgs<string>
{
public ObservableDictionaryChangedEventArgs(CollectionChange change, string key)
{
CollectionChange = change;
Key = key;
}
public CollectionChange CollectionChange { get; }
public string Key { get; }
}
private readonly Dictionary<string, object> _dictionary = new Dictionary<string, object>();
public event MapChangedEventHandler<string, object> MapChanged;
private void InvokeMapChanged(CollectionChange change, string key)
{
MapChanged?.Invoke(this, new ObservableDictionaryChangedEventArgs(change, key));
}
public void Add(string key, object value)
{
_dictionary.Add(key, value);
InvokeMapChanged(CollectionChange.ItemInserted, key);
}
public void Add(KeyValuePair<string, object> item)
{
Add(item.Key, item.Value);
}
public void AddRange(IEnumerable<KeyValuePair<string, object>> values)
{
foreach (var value in values)
{
Add(value);
}
}
public bool Remove(string key)
{
if (_dictionary.Remove(key))
{
InvokeMapChanged(CollectionChange.ItemRemoved, key);
return true;
}
return false;
}
public bool Remove(KeyValuePair<string, object> item)
{
object currentValue;
if (_dictionary.TryGetValue(item.Key, out currentValue) &&
Equals(item.Value, currentValue) && _dictionary.Remove(item.Key))
{
InvokeMapChanged(CollectionChange.ItemRemoved, item.Key);
return true;
}
return false;
}
public object this[string key]
{
get
{
return _dictionary[key];
}
set
{
_dictionary[key] = value;
InvokeMapChanged(CollectionChange.ItemChanged, key);
}
}
public void Clear()
{
var priorKeys = _dictionary.Keys.ToArray();
_dictionary.Clear();
foreach (var key in priorKeys)
{
InvokeMapChanged(CollectionChange.ItemRemoved, key);
}
}
public ICollection<string> Keys => _dictionary.Keys;
public bool ContainsKey(string key)
{
return _dictionary.ContainsKey(key);
}
public bool TryGetValue(string key, out object value)
{
return _dictionary.TryGetValue(key, out value);
}
public ICollection<object> Values => _dictionary.Values;
public bool Contains(KeyValuePair<string, object> item)
{
return _dictionary.Contains(item);
}
public int Count => _dictionary.Count;
public bool IsReadOnly => false;
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
return _dictionary.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _dictionary.GetEnumerator();
}
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
var arraySize = array.Length;
foreach (var pair in _dictionary)
{
if (arrayIndex >= arraySize) break;
array[arrayIndex++] = pair;
}
}
}
}