Implement import and export of Settings.

- NEU: Stile können importiert und exportiert werden.
This commit is contained in:
Daniel Kraus 2015-08-11 21:22:34 +02:00
parent 00c60ba0ad
commit 0708f2a2b3
11 changed files with 379 additions and 49 deletions

View File

@ -50,21 +50,5 @@ namespace Tests.Controller
((Items)clone.Elements[1]).Content,
"Items content");
}
[Test]
public void PersistSettings()
{
string name = "hello world";
Settings persisting = new Settings(name, new List<ElementBase>() { new Items() });
persisting.ReferenceStyle = zaaReloaded2.Formatter.ReferenceStyle.IfSpecialItem;
MemoryStream s = new MemoryStream();
persisting.Persist(s);
s.Position = 0;
Settings retrieved = Settings.Unpersist(s);
Assert.AreEqual(persisting.Name, retrieved.Name, "Name");
Assert.AreEqual(persisting.ReferenceStyle, retrieved.ReferenceStyle, "ReferenceStyle");
}
}
}

View File

@ -24,6 +24,7 @@ using zaaReloaded2.Controller.Elements;
using System.IO;
using System.Text.RegularExpressions;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Soap;
namespace zaaReloaded2.Controller
{
@ -36,34 +37,34 @@ namespace zaaReloaded2.Controller
#region Persistence
/// <summary>
/// Custom serialization method that writes this settings
/// to a human-readable text file.
/// Deserializes a Settings object from SOAP XML that is read from a file.
/// </summary>
/// <param name="stream"></param>
public void Persist(Stream stream)
/// <param name="fileName">File to read.</param>
/// <returns>Deserialized Settings object with new unique ID.</returns>
/// <exception cref="InvalidDataException">if the file does not contain
/// SOAP XML that can be deserialized to a Settings object.</exception>
public static Settings LoadFromFile(string fileName)
{
StreamWriter w = new StreamWriter(stream);
w.WriteLine(String.Format("[{0}]", Name));
w.WriteLine(ReferenceStyle.ToString());
w.Flush();
StreamReader reader = new StreamReader(fileName);
SoapFormatter formatter = new SoapFormatter();
Settings settings = formatter.Deserialize(reader.BaseStream) as Settings;
if (settings == null)
{
throw new InvalidDataException("Datei enthält keine Stil-Daten oder ist beschädigt.");
}
settings.Uid = Guid.NewGuid();
return settings;
}
public static Settings Unpersist(Stream stream)
/// <summary>
/// Serializes a Settings object to SOAP XML that is written to a file.
/// </summary>
/// <param name="fileName">File to write to.</param>
public void SaveToFile(string fileName)
{
StreamReader r = new StreamReader(stream);
string line = r.ReadLine();
Match m = _persistenceHeaderRegex.Match(line);
if (!m.Success)
throw new InvalidDataException("Settings header line does not match expected format.");
Settings settings = new Settings();
settings.Name = m.Groups["name"].Value;
line = r.ReadLine();
settings.ReferenceStyle = (ReferenceStyle)Enum.Parse(typeof(ReferenceStyle), line);
return settings;
StreamWriter writer = new StreamWriter(fileName);
SoapFormatter formatter = new SoapFormatter();
formatter.Serialize(writer.BaseStream, this);
}
#endregion

View File

@ -291,5 +291,17 @@ namespace zaaReloaded2.Properties {
return ((int)(this["SerializationVersion"]));
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string ImportExportPath {
get {
return ((string)(this["ImportExportPath"]));
}
set {
this["ImportExportPath"] = value;
}
}
}
}

View File

@ -89,5 +89,8 @@
<Setting Name="SerializationVersion" Type="System.Int32" Scope="Application">
<Value Profile="(Default)">1</Value>
</Setting>
<Setting Name="ImportExportPath" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
</Settings>
</SettingsFile>

View File

@ -0,0 +1,46 @@
/* IoErrorViewModel.cs
* part of zaaReloaded2
*
* Copyright 2015 Daniel Kraus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Bovender.Mvvm.ViewModels;
namespace zaaReloaded2.ViewModels
{
/// <summary>
/// Simple view model for I/O error messages, for use in conjunction
/// with Bovender's ShowViewDialogAction and zaaReloaded2.Views.IoErrorView.
/// </summary>
class IoErrorViewModel : ViewModelBase
{
public Exception Exception { get; private set; }
public string Message { get { return Exception.Message; } }
public IoErrorViewModel(Exception e)
{
Exception = e;
}
public override object RevealModelObject()
{
return Exception;
}
}
}

View File

@ -24,6 +24,8 @@ using Bovender.Mvvm.ViewModels;
using Bovender.Mvvm.Messaging;
using zaaReloaded2.Controller;
using System.Collections.ObjectModel;
using System.Runtime.Serialization.Formatters.Soap;
using System.IO;
namespace zaaReloaded2.ViewModels
{
@ -134,6 +136,33 @@ namespace zaaReloaded2.ViewModels
}
}
public DelegatingCommand ExportSettingsCommand
{
get
{
if (_exportSettingsCommand == null)
{
_exportSettingsCommand = new DelegatingCommand(
param => DoExportSettings(),
param => CanExportSettings());
}
return _exportSettingsCommand;
}
}
public DelegatingCommand ImportSettingsCommand
{
get
{
if (_importSettingsCommand == null)
{
_importSettingsCommand = new DelegatingCommand(
param => DoImportSettings(),
param => CanImportSettings());
}
return _importSettingsCommand;
}
}
#endregion
#region Messages
@ -186,6 +215,54 @@ namespace zaaReloaded2.ViewModels
}
}
public Message<FileNameMessageContent> ChooseExportFileNameMessage
{
get
{
if (_chooseExportFileNameMessage == null)
{
_chooseExportFileNameMessage = new Message<FileNameMessageContent>();
}
return _chooseExportFileNameMessage;
}
}
public Message<FileNameMessageContent> ChooseImportFileNameMessage
{
get
{
if (_chooseImportFileNameMessage == null)
{
_chooseImportFileNameMessage = new Message<FileNameMessageContent>();
}
return _chooseImportFileNameMessage;
}
}
public Message<ViewModelMessageContent> ExportErrorMessage
{
get
{
if (_exportErrorMessage == null)
{
_exportErrorMessage = new Message<ViewModelMessageContent>();
}
return _exportErrorMessage;
}
}
public Message<ViewModelMessageContent> ImportErrorMessage
{
get
{
if (_importErrorMessage == null)
{
_importErrorMessage = new Message<ViewModelMessageContent>();
}
return _importErrorMessage;
}
}
#endregion
#region Constructors
@ -334,6 +411,86 @@ namespace zaaReloaded2.ViewModels
SettingsList.Add(settingsViewModel);
}
void DoExportSettings()
{
if (CanExportSettings())
{
ChooseExportFileNameMessage.Send(
new FileNameMessageContent(SuggestImportExportPath(), FILE_FILTER),
msg => ConfirmExportSettings(msg));
}
}
bool CanExportSettings()
{
return LastSelected != null && LastSelected.IsSelected && !IsDefaultSettings();
}
void ConfirmExportSettings(FileNameMessageContent message)
{
if (message.Confirmed)
{
Properties.Settings.Default.ImportExportPath = message.Value;
Properties.Settings.Default.Save();
Settings settings = LastSelected.RevealModelObject() as Settings;
try
{
settings.SaveToFile(message.Value);
}
catch (Exception e)
{
ExportErrorMessage.Send(
new ViewModelMessageContent(new IoErrorViewModel(e))
);
}
}
}
void DoImportSettings()
{
ChooseImportFileNameMessage.Send(
new FileNameMessageContent(SuggestImportExportPath(), FILE_FILTER),
msg => ConfirmImportSettings(msg));
}
bool CanImportSettings()
{
return true;
}
void ConfirmImportSettings(FileNameMessageContent message)
{
if (message.Confirmed)
{
Properties.Settings.Default.ImportExportPath = message.Value;
Properties.Settings.Default.Save();
try
{
Settings settings = Settings.LoadFromFile(message.Value);
AddSettingsViewModel(new SettingsViewModel(settings));
}
catch (Exception e)
{
ImportErrorMessage.Send(
new ViewModelMessageContent(new IoErrorViewModel(e))
);
}
}
}
string SuggestImportExportPath()
{
string path = Properties.Settings.Default.ImportExportPath;
if (String.IsNullOrEmpty(path))
{
return Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
}
else
{
return path;
}
}
#endregion
#region Implementation of ViewModelBase
@ -354,10 +511,26 @@ namespace zaaReloaded2.ViewModels
DelegatingCommand _deleteSettingsCommand;
DelegatingCommand _resetSettingsCommand;
DelegatingCommand _copySettingsCommand;
DelegatingCommand _exportSettingsCommand;
DelegatingCommand _importSettingsCommand;
Message<ViewModelMessageContent> _confirmDeleteSettingsMessage;
Message<ViewModelMessageContent> _confirmResetSettingsMessage;
Message<ViewModelMessageContent> _editSettingsMessage;
Message<ViewModelMessageContent> _useSettingsMessage;
Message<FileNameMessageContent> _chooseExportFileNameMessage;
Message<FileNameMessageContent> _chooseImportFileNameMessage;
Message<ViewModelMessageContent> _exportErrorMessage;
Message<ViewModelMessageContent> _importErrorMessage;
#endregion
#region Constant
/// <summary>
/// File filter that is used for the import and export
/// of settings.
/// </summary>
const string FILE_FILTER = "zaaReloaded-Stildatei (*.zaaReloaded)|*.zaaReloaded";
#endregion
}

View File

@ -0,0 +1,52 @@
<!--
IoErrorView.xaml
part of zaaReloaded2
Copyright 2015 Daniel Kraus
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<Window x:Class="zaaReloaded2.Views.IoErrorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="clr-namespace:Bovender.Mvvm.Views.Settings;assembly=Bovender"
SizeToContent="WidthAndHeight" MaxWidth="480"
ResizeMode="NoResize" ShowInTaskbar="False"
b:WindowState.CenterScreen="True"
Title="Import/Export-Fehler"
>
<Window.Resources>
<ResourceDictionary Source="/zaaReloaded2;component/Style.xaml" />
</Window.Resources>
<DockPanel Margin="10">
<TextBlock DockPanel.Dock="Left" Margin="0 0 20 0" LineHeight="72" LineStackingStrategy="BlockLineHeight"
Text="X" FontSize="72" FontWeight="ExtraBold" Foreground="IndianRed">
<TextBlock.Effect>
<DropShadowEffect />
</TextBlock.Effect>
</TextBlock>
<StackPanel DockPanel.Dock="Bottom" HorizontalAlignment="Right" Margin="0 10 0 0">
<Button Content="Schließen" Command="{Binding CloseViewCommand}" />
</StackPanel>
<StackPanel>
<TextBlock TextWrapping="Wrap">
Die Aktion konnte nicht ausgeführt werden, weil Windows einen Fehler gemeldet hat.
</TextBlock>
<TextBlock Margin="0 10 0 0">
Fehlermeldung:
</TextBlock>
<TextBlock Margin="0 5 0 0" Text="{Binding Message}" />
</StackPanel>
</DockPanel>
</Window>

View File

@ -0,0 +1,33 @@
/* IoErrorView.xaml.cs
* part of zaaReloaded2
*
* Copyright 2015 Daniel Kraus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Windows;
namespace zaaReloaded2.Views
{
/// <summary>
/// Interaction logic for IoErrorView.xaml
/// </summary>
public partial class IoErrorView : Window
{
public IoErrorView()
{
InitializeComponent();
}
}
}

View File

@ -23,7 +23,7 @@
xmlns:b="clr-namespace:Bovender.Mvvm.Views.Settings;assembly=Bovender"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:action="clr-namespace:Bovender.Mvvm.Actions;assembly=Bovender"
Width="380" Height="340" ResizeMode="CanResizeWithGrip" ShowInTaskbar="False"
Width="380" Height="365" ResizeMode="CanResizeWithGrip" ShowInTaskbar="False"
b:WindowState.CenterScreen="True" b:WindowState.Save="True"
Title="Stil auswählen"
>
@ -38,25 +38,40 @@
<action:ConfirmationAction Caption="Stil entfernen" OkButtonLabel="Ja" CancelButtonLabel="Nein"
Message="Soll dieser Stil wirklich unwiederbringlich entfernt werden?" />
</i:EventTrigger>
<i:EventTrigger SourceObject="{Binding ChooseExportFileNameMessage}" EventName="Sent">
<action:ChooseFileSaveAction Caption="Stil exportieren" Message="Bitte Dateinamen für den Export angeben." />
</i:EventTrigger>
<i:EventTrigger SourceObject="{Binding ChooseImportFileNameMessage}" EventName="Sent">
<action:ChooseFileOpenAction Caption="Stil importieren" Message="Bitte zu importierende Datei auswählen." />
</i:EventTrigger>
<!-- TODO: Implement meaningful error messages. -->
<i:EventTrigger SourceObject="{Binding ExportErrorMessage}" EventName="Sent">
<action:ShowViewDialogAction Assembly="zaaReloaded2" View="zaaReloaded2.Views.IoErrorView" />
</i:EventTrigger>
<i:EventTrigger SourceObject="{Binding ImportErrorMessage}" EventName="Sent">
<action:ShowViewDialogAction Assembly="zaaReloaded2" View="zaaReloaded2.Views.IoErrorView" />
</i:EventTrigger>
</i:Interaction.Triggers>
<StackPanel Margin="10">
<Label Target="{Binding ElementName=settingsList}">Bitte Stil auswählen:</Label>
<DockPanel Margin="0 5 0 0">
<StackPanel DockPanel.Dock="Right" Margin="10 0 0 0">
<Button Command="{Binding UseSettingsCommand}" Content="Wählen" IsDefault="True" Margin="0 0 0 5" />
<Button Command="{Binding AddSettingsCommand}" Content="Hinzufügen" Margin="0 10 0 5" />
<Button Command="{Binding EditSettingsCommand}" Content="Bearbeiten" Margin="0 0 0 5" />
<Button Command="{Binding DeleteSettingsCommand}" Content="Entfernen" Margin="0 0 0 5" />
<Button Command="{Binding CopySettingsCommand}" Content="Kopieren" Margin="0 0 0 5" />
<Button Command="{Binding CloseViewCommand}" Content="Schließen" Margin="0 10 0 0" IsCancel="True" />
</StackPanel>
<StackPanel>
<ListBox ItemsSource="{Binding SettingsList}"
DisplayMemberPath="Name"
ItemContainerStyle="{DynamicResource ResourceKey=ViewModelListBox}"
x:Name="settingsList"
MinWidth="240" MinHeight="240" />
<Button Command="{Binding ExportSettingsCommand}" Content="Exportieren" Margin="0 5 0 5" />
<Button Command="{Binding ImportSettingsCommand}" Content="Importieren" Margin="0 0 0 5" />
</StackPanel>
<UniformGrid DockPanel.Dock="Bottom" Margin="0 10 0 0" Columns="2" Rows="1" HorizontalAlignment="Right">
<Button Command="{Binding UseSettingsCommand}" Content="Auswählen" IsDefault="True" Margin="0 0 5 0" />
<Button Command="{Binding CloseViewCommand}" Content="Schließen" Margin="5 0 0 0" IsCancel="True" />
</UniformGrid>
<ListBox ItemsSource="{Binding SettingsList}"
DisplayMemberPath="Name"
ItemContainerStyle="{DynamicResource ResourceKey=ViewModelListBox}"
x:Name="settingsList"
MinWidth="240" MinHeight="240" />
</DockPanel>
</StackPanel>
</Window>

View File

@ -120,6 +120,9 @@
<setting name="LastSettings" serializeAs="String">
<value>00000000-0000-0000-0000-000000000000</value>
</setting>
<setting name="ImportExportPath" serializeAs="String">
<value />
</setting>
</zaaReloaded2.Properties.Settings>
</userSettings>
</configuration>

View File

@ -251,7 +251,11 @@
<Compile Include="ViewModels\ElementViewModel.cs" />
<Compile Include="ViewModels\FormatElementViewModel.cs" />
<Compile Include="ViewModels\ControlElementViewModel.cs" />
<Compile Include="ViewModels\IoErrorViewModel.cs" />
<Compile Include="ViewModels\SettingsRepositoryViewModel.cs" />
<Compile Include="Views\IoErrorView.xaml.cs">
<DependentUpon>IoErrorView.xaml</DependentUpon>
</Compile>
<Compile Include="Views\ElementPickerView.xaml.cs">
<DependentUpon>ElementPickerView.xaml</DependentUpon>
</Compile>
@ -332,6 +336,10 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Resource>
<Page Include="Views\IoErrorView.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Views\ElementPickerView.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>