Add Outlook folder browser and user settings

- Replace Outlook folder ComboBox with TextBox + Browse button
  that opens a TreeView-based folder browser dialog
- Add per-user settings stored in %AppData% (not HKLM)
  with option to start minimized (System Tray only)
- Add Settings button to main window
- Settings are per-user, independent of profiles

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
duffyduck 2026-04-03 11:04:57 +02:00
parent 41c690f8c2
commit 1aa74ec014
5 changed files with 306 additions and 29 deletions

View File

@ -0,0 +1,37 @@
using System;
using System.IO;
using Newtonsoft.Json;
namespace StarfaceOutlookSync.Models
{
public class UserSettings
{
public bool StartMinimized { get; set; } = false;
private static readonly string SettingsFile = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"StarfaceOutlookSync", "settings.json");
public static UserSettings Load()
{
try
{
if (File.Exists(SettingsFile))
return JsonConvert.DeserializeObject<UserSettings>(File.ReadAllText(SettingsFile))
?? new UserSettings();
}
catch { }
return new UserSettings();
}
public void Save()
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(SettingsFile));
File.WriteAllText(SettingsFile, JsonConvert.SerializeObject(this, Formatting.Indented));
}
catch { }
}
}
}

View File

@ -18,7 +18,7 @@ namespace StarfaceOutlookSync.UI
private NotifyIcon _trayIcon;
private ContextMenuStrip _trayMenu;
private ListView _profileList;
private Button _btnNew, _btnEdit, _btnDelete, _btnSync, _btnInfo;
private Button _btnNew, _btnEdit, _btnDelete, _btnSync, _btnSettings, _btnInfo;
private StatusStrip _statusBar;
private ToolStripStatusLabel _statusLabel;
private Timer _autoSyncTimer;
@ -29,6 +29,30 @@ namespace StarfaceOutlookSync.UI
SetupTrayIcon();
SetupAutoSync();
RefreshProfileList();
// Minimiert starten falls in Einstellungen aktiviert
var settings = UserSettings.Load();
if (settings.StartMinimized)
{
WindowState = FormWindowState.Minimized;
ShowInTaskbar = false;
Visible = false;
}
}
protected override void SetVisibleCore(bool value)
{
// Beim ersten Anzeigen pruefen ob minimiert gestartet werden soll
if (!IsHandleCreated)
{
var settings = UserSettings.Load();
if (settings.StartMinimized)
{
CreateHandle();
value = false;
}
}
base.SetVisibleCore(value);
}
private void InitializeComponent()
@ -76,10 +100,13 @@ namespace StarfaceOutlookSync.UI
_btnSync = new Button { Text = "Jetzt synchronisieren", Width = 150, Height = 30 };
_btnSync.Click += async (s, e) => await SyncSelectedProfile();
_btnSettings = new Button { Text = "Einstellungen", Width = 100, Height = 30 };
_btnSettings.Click += (s, e) => ShowSettings();
_btnInfo = new Button { Text = "Info", Width = 50, Height = 30 };
_btnInfo.Click += (s, e) => ShowAbout();
buttonPanel.Controls.AddRange(new Control[] { _btnNew, _btnEdit, _btnDelete, _btnSync, _btnInfo });
buttonPanel.Controls.AddRange(new Control[] { _btnNew, _btnEdit, _btnDelete, _btnSync, _btnSettings, _btnInfo });
// Statusbar
_statusBar = new StatusStrip();
@ -283,6 +310,14 @@ namespace StarfaceOutlookSync.UI
_statusLabel.Text = text;
}
private void ShowSettings()
{
using (var settings = new SettingsForm())
{
settings.ShowDialog(this);
}
}
private void ShowAbout()
{
using (var about = new AboutForm())

View File

@ -0,0 +1,125 @@
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Collections.Generic;
namespace StarfaceOutlookSync.UI
{
public class OutlookFolderBrowserForm : Form
{
private TreeView _tree;
private Button _btnOk, _btnCancel;
public string SelectedFolderPath { get; private set; } = "";
public string SelectedFolderName { get; private set; } = "";
public OutlookFolderBrowserForm(List<string> folderPaths, string currentSelection)
{
InitializeComponent();
BuildTree(folderPaths, currentSelection);
}
private void InitializeComponent()
{
Text = "Outlook-Ordner waehlen";
Size = new Size(400, 450);
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
StartPosition = FormStartPosition.CenterParent;
Font = new Font("Segoe UI", 9);
var lblInfo = new Label
{
Text = "Bitte waehlen Sie den Outlook-Kontaktordner fuer die Synchronisation:",
Left = 12, Top = 12, Width = 360, Height = 36
};
_tree = new TreeView
{
Left = 12, Top = 52, Width = 360, Height = 310,
HideSelection = false,
ShowLines = true,
ShowPlusMinus = true,
ShowRootLines = true
};
_tree.DoubleClick += (s, e) => { if (_tree.SelectedNode?.Tag != null) SelectAndClose(); };
_btnOk = new Button
{
Text = "OK", Left = 210, Top = 372, Width = 75, Height = 28,
DialogResult = DialogResult.None
};
_btnOk.Click += (s, e) => SelectAndClose();
_btnCancel = new Button
{
Text = "Abbrechen", Left = 292, Top = 372, Width = 80, Height = 28,
DialogResult = DialogResult.Cancel
};
Controls.AddRange(new Control[] { lblInfo, _tree, _btnOk, _btnCancel });
AcceptButton = _btnOk;
CancelButton = _btnCancel;
}
private void BuildTree(List<string> folderPaths, string currentSelection)
{
_tree.Nodes.Clear();
var nodeMap = new Dictionary<string, TreeNode>();
foreach (var path in folderPaths)
{
// Pfad: \\Store\Kontakte\Unterordner
var parts = path.TrimStart('\\').Split('\\');
string runningPath = "";
TreeNode parent = null;
for (int i = 0; i < parts.Length; i++)
{
runningPath += "\\" + parts[i];
if (!nodeMap.ContainsKey(runningPath))
{
var node = new TreeNode(parts[i]);
// Nur Blatt-Knoten oder bekannte Pfade sind waehlbar
if (i == parts.Length - 1)
node.Tag = path; // Vollstaendiger Pfad
if (parent == null)
_tree.Nodes.Add(node);
else
parent.Nodes.Add(node);
nodeMap[runningPath] = node;
// Aktuellen Ordner vorauswaehlen
if (path == currentSelection)
{
_tree.SelectedNode = node;
node.EnsureVisible();
}
}
parent = nodeMap[runningPath];
}
}
_tree.ExpandAll();
}
private void SelectAndClose()
{
if (_tree.SelectedNode?.Tag == null)
{
MessageBox.Show("Bitte einen Kontaktordner waehlen.", "Hinweis",
MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
SelectedFolderPath = _tree.SelectedNode.Tag.ToString();
SelectedFolderName = _tree.SelectedNode.Text;
DialogResult = DialogResult.OK;
Close();
}
}
}

View File

@ -15,14 +15,18 @@ namespace StarfaceOutlookSync.UI
private readonly bool _isNew;
// Controls
private TextBox _txtName, _txtHost, _txtPort, _txtLoginId, _txtPassword;
private TextBox _txtName, _txtHost, _txtPort, _txtLoginId, _txtPassword, _txtOutlookFolder;
private CheckBox _chkSsl, _chkEnabled;
private ComboBox _cmbAddressBook, _cmbOutlookFolder, _cmbDirection;
private ComboBox _cmbAddressBook, _cmbDirection;
private Button _btnBrowseFolder;
private NumericUpDown _numAutoSync;
private Button _btnTest, _btnLoadBooks, _btnSave, _btnCancel;
private Label _lblTestResult;
private List<StarfaceAddressBook> _addressBooks = new List<StarfaceAddressBook>();
private List<string> _outlookFolderPaths = new List<string>();
private string _selectedOutlookPath = "";
private string _selectedOutlookName = "";
public ProfileEditorForm(SyncProfile profile)
{
@ -87,8 +91,11 @@ namespace StarfaceOutlookSync.UI
panel.Controls.Add(MakeSectionLabel("Outlook-Einstellungen", 12, y)); y += 26;
panel.Controls.Add(MakeLabel("Kontakte-Ordner:", 12, y)); y += 22;
_cmbOutlookFolder = new ComboBox { Left = 12, Top = y, Width = 420, DropDownStyle = ComboBoxStyle.DropDownList };
panel.Controls.Add(_cmbOutlookFolder); y += 32;
_txtOutlookFolder = new TextBox { Left = 12, Top = y, Width = 330, ReadOnly = true, BackColor = SystemColors.Window };
_btnBrowseFolder = new Button { Text = "Durchsuchen...", Left = 348, Top = y - 1, Width = 84, Height = 24 };
_btnBrowseFolder.Click += (s, e) => BrowseOutlookFolder();
panel.Controls.Add(_txtOutlookFolder);
panel.Controls.Add(_btnBrowseFolder); y += 32;
panel.Controls.Add(MakeLabel("Sync-Richtung:", 12, y)); y += 22;
_cmbDirection = new ComboBox { Left = 12, Top = y, Width = 250, DropDownStyle = ComboBoxStyle.DropDownList };
@ -132,18 +139,12 @@ namespace StarfaceOutlookSync.UI
{
using (var outlook = new OutlookContactsService())
{
var folders = outlook.GetContactFolderPaths();
_cmbOutlookFolder.Items.Clear();
foreach (var f in folders)
_cmbOutlookFolder.Items.Add(f);
if (folders.Count > 0)
_cmbOutlookFolder.SelectedIndex = 0;
_outlookFolderPaths = outlook.GetContactFolderPaths();
}
}
catch
{
_cmbOutlookFolder.Items.Add("\\\\Kontakte");
_cmbOutlookFolder.SelectedIndex = 0;
_outlookFolderPaths = new List<string> { "\\\\Kontakte" };
}
// Bestehende Werte laden
@ -160,15 +161,10 @@ namespace StarfaceOutlookSync.UI
_cmbDirection.SelectedIndex = (int)_existingProfile.SyncDirection;
// Outlook-Ordner auswaehlen
for (int i = 0; i < _cmbOutlookFolder.Items.Count; i++)
{
if (_cmbOutlookFolder.Items[i].ToString() == _existingProfile.OutlookFolderPath)
{
_cmbOutlookFolder.SelectedIndex = i;
break;
}
}
// Outlook-Ordner
_selectedOutlookPath = _existingProfile.OutlookFolderPath;
_selectedOutlookName = _existingProfile.OutlookFolderName;
_txtOutlookFolder.Text = _selectedOutlookPath;
// Adressbuch
if (_existingProfile.StarfaceAddressBook != null)
@ -178,6 +174,26 @@ namespace StarfaceOutlookSync.UI
_cmbAddressBook.SelectedIndex = 0;
}
}
else if (_outlookFolderPaths.Count > 0)
{
// Standard-Ordner vorauswaehlen
_selectedOutlookPath = _outlookFolderPaths[0];
_selectedOutlookName = _selectedOutlookPath.Substring(_selectedOutlookPath.LastIndexOf('\\') + 1);
_txtOutlookFolder.Text = _selectedOutlookPath;
}
}
private void BrowseOutlookFolder()
{
using (var browser = new OutlookFolderBrowserForm(_outlookFolderPaths, _selectedOutlookPath))
{
if (browser.ShowDialog(this) == DialogResult.OK)
{
_selectedOutlookPath = browser.SelectedFolderPath;
_selectedOutlookName = browser.SelectedFolderName;
_txtOutlookFolder.Text = _selectedOutlookPath;
}
}
}
private StarfaceConnection GetConnection()
@ -292,10 +308,12 @@ namespace StarfaceOutlookSync.UI
return;
}
var selectedFolder = _cmbOutlookFolder.SelectedItem?.ToString() ?? "";
var folderName = selectedFolder;
if (folderName.Contains("\\"))
folderName = folderName.Substring(folderName.LastIndexOf('\\') + 1);
if (string.IsNullOrEmpty(_selectedOutlookPath))
{
MessageBox.Show("Bitte einen Outlook-Kontaktordner waehlen.",
"Fehler", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var profile = new SyncProfile
{
@ -303,8 +321,8 @@ namespace StarfaceOutlookSync.UI
Name = _txtName.Text.Trim(),
StarfaceConnection = GetConnection(),
StarfaceAddressBook = _addressBooks[_cmbAddressBook.SelectedIndex],
OutlookFolderPath = selectedFolder,
OutlookFolderName = folderName,
OutlookFolderPath = _selectedOutlookPath,
OutlookFolderName = _selectedOutlookName,
SyncDirection = (SyncDirection)_cmbDirection.SelectedIndex,
Enabled = _chkEnabled.Checked,
AutoSyncIntervalMinutes = (int)_numAutoSync.Value,

View File

@ -0,0 +1,62 @@
using System.Drawing;
using System.Windows.Forms;
using StarfaceOutlookSync.Models;
namespace StarfaceOutlookSync.UI
{
public class SettingsForm : Form
{
private CheckBox _chkStartMinimized;
private Button _btnSave, _btnCancel;
private readonly UserSettings _settings;
public SettingsForm()
{
_settings = UserSettings.Load();
InitializeComponent();
}
private void InitializeComponent()
{
Text = "Einstellungen";
Size = new Size(350, 180);
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
StartPosition = FormStartPosition.CenterParent;
Font = new Font("Segoe UI", 9);
_chkStartMinimized = new CheckBox
{
Text = "Minimiert starten (nur im System Tray)",
Left = 20, Top = 24, AutoSize = true,
Checked = _settings.StartMinimized
};
_btnSave = new Button
{
Text = "Speichern", Left = 80, Top = 100, Width = 85, Height = 28,
DialogResult = DialogResult.None
};
_btnSave.Click += (s, e) => Save();
_btnCancel = new Button
{
Text = "Abbrechen", Left = 174, Top = 100, Width = 85, Height = 28,
DialogResult = DialogResult.Cancel
};
Controls.AddRange(new Control[] { _chkStartMinimized, _btnSave, _btnCancel });
AcceptButton = _btnSave;
CancelButton = _btnCancel;
}
private void Save()
{
_settings.StartMinimized = _chkStartMinimized.Checked;
_settings.Save();
DialogResult = DialogResult.OK;
Close();
}
}
}