diff --git a/Frank.Wpf.Controls.Code/CSharpBeautifier.cs b/Frank.Wpf.Controls.Code/CSharpBeautifier.cs new file mode 100644 index 0000000..b1c1e3b --- /dev/null +++ b/Frank.Wpf.Controls.Code/CSharpBeautifier.cs @@ -0,0 +1,10 @@ +namespace Frank.Wpf.Controls.Code; + +public class CSharpBeautifier : CodeBeautifierBase +{ + public override string Beautify(string code) + { + // Placeholder: Implement C# specific beautification logic + return IndentLines(code); + } +} \ No newline at end of file diff --git a/Frank.Wpf.Controls.Code/CodeArea.cs b/Frank.Wpf.Controls.Code/CodeArea.cs index cd44903..654d421 100644 --- a/Frank.Wpf.Controls.Code/CodeArea.cs +++ b/Frank.Wpf.Controls.Code/CodeArea.cs @@ -17,4 +17,9 @@ public CodeArea(IHighlightingDefinition highlightingDefinition) WordWrap = true; VerticalScrollBarVisibility = ScrollBarVisibility.Auto; } + + public void Beautify(ICodeBeautifier codeBeautifier) + { + Text = codeBeautifier.Beautify(Text); + } } \ No newline at end of file diff --git a/Frank.Wpf.Controls.Code/CodeBeautifierBase.cs b/Frank.Wpf.Controls.Code/CodeBeautifierBase.cs new file mode 100644 index 0000000..8569e82 --- /dev/null +++ b/Frank.Wpf.Controls.Code/CodeBeautifierBase.cs @@ -0,0 +1,16 @@ +namespace Frank.Wpf.Controls.Code; + +public abstract class CodeBeautifierBase : ICodeBeautifier +{ + public abstract string Beautify(string code); + + protected string IndentLines(string code, int indentLevel = 4) + { + var lines = code.Split('\n'); + for (int i = 0; i < lines.Length; i++) + { + lines[i] = new string(' ', indentLevel) + lines[i].Trim(); + } + return string.Join("\n", lines); + } +} \ No newline at end of file diff --git a/Frank.Wpf.Controls.Code/IBeautificationTrigger.cs b/Frank.Wpf.Controls.Code/IBeautificationTrigger.cs new file mode 100644 index 0000000..2009561 --- /dev/null +++ b/Frank.Wpf.Controls.Code/IBeautificationTrigger.cs @@ -0,0 +1,6 @@ +namespace Frank.Wpf.Controls.Code; + +public interface IBeautificationTrigger +{ + bool ShouldBeautify(string code); +} \ No newline at end of file diff --git a/Frank.Wpf.Controls.Code/ICodeBeautifier.cs b/Frank.Wpf.Controls.Code/ICodeBeautifier.cs new file mode 100644 index 0000000..a4b0d6b --- /dev/null +++ b/Frank.Wpf.Controls.Code/ICodeBeautifier.cs @@ -0,0 +1,6 @@ +namespace Frank.Wpf.Controls.Code; + +public interface ICodeBeautifier +{ + string Beautify(string code); +} \ No newline at end of file diff --git a/Frank.Wpf.Controls.Code/JsonBeautifier.cs b/Frank.Wpf.Controls.Code/JsonBeautifier.cs new file mode 100644 index 0000000..c70b831 --- /dev/null +++ b/Frank.Wpf.Controls.Code/JsonBeautifier.cs @@ -0,0 +1,16 @@ +using System.Text.Json; + +namespace Frank.Wpf.Controls.Code; + +public class JsonBeautifier : CodeBeautifierBase +{ + public override string Beautify(string code) + { + // Example using System.Text.Json for formatting JSON + var jsonElement = JsonSerializer.Deserialize(code); + return JsonSerializer.Serialize(jsonElement, new JsonSerializerOptions + { + WriteIndented = true + }); + } +} \ No newline at end of file diff --git a/Frank.Wpf.Controls.Code/SimpleTrigger.cs b/Frank.Wpf.Controls.Code/SimpleTrigger.cs new file mode 100644 index 0000000..2917b12 --- /dev/null +++ b/Frank.Wpf.Controls.Code/SimpleTrigger.cs @@ -0,0 +1,16 @@ +namespace Frank.Wpf.Controls.Code; + +public class SimpleTrigger : IBeautificationTrigger +{ + private readonly Func _shouldBeautify; + + public SimpleTrigger(Func shouldBeautify) + { + _shouldBeautify = shouldBeautify; + } + + public bool ShouldBeautify(string code) + { + return _shouldBeautify(code); + } +} \ No newline at end of file diff --git a/Frank.Wpf.Controls.CompletionPopup/CompletionData.cs b/Frank.Wpf.Controls.CompletionPopup/CompletionData.cs new file mode 100644 index 0000000..4cc3714 --- /dev/null +++ b/Frank.Wpf.Controls.CompletionPopup/CompletionData.cs @@ -0,0 +1,13 @@ +namespace Frank.Wpf.Controls.CompletionPopup; + +public class CompletionData : ICompletionData +{ + public CompletionData(string text, string description = "") + { + Text = text; + Description = description; + } + + public string Text { get; } + public string Description { get; } +} \ No newline at end of file diff --git a/Frank.Wpf.Controls.CompletionPopup/CompletionPopup.cs b/Frank.Wpf.Controls.CompletionPopup/CompletionPopup.cs new file mode 100644 index 0000000..cf9cd30 --- /dev/null +++ b/Frank.Wpf.Controls.CompletionPopup/CompletionPopup.cs @@ -0,0 +1,176 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Input; +using Frank.Wpf.Controls.CompletionPopup; + +public class CompletionPopup : Popup +{ + private readonly ListBox _completionListBox; + private ICompletionSource _completionSource; + private ICompletionTriggerRule _triggerRule; + + public CompletionPopup() + { + StaysOpen = false; + _completionListBox = new ListBox(); + _completionListBox.PreviewMouseLeftButtonUp += CompletionListBox_MouseUp; + _completionListBox.KeyDown += CompletionListBox_KeyDown; + Child = _completionListBox; + } + + public void Initialize(TextBox textBox, ICompletionSource completionSource, ICompletionTriggerRule triggerRule) + { + _completionSource = completionSource; + _triggerRule = triggerRule; + + _completionListBox.ItemTemplate = CreateVisualTemplate(); + + // Set PlacementTarget and PlacementMode + PlacementTarget = textBox; + Placement = PlacementMode.RelativePoint; + + textBox.PreviewKeyDown += (s, e) => HandleKeyDown(e); + textBox.TextChanged += (s, e) => HandleTextChanged(textBox); + } + + /// + protected override void OnOpened(EventArgs e) + { + _completionListBox.Focus(); + base.OnOpened(e); + } + + private void HandleTextChanged(TextBox textBox) + { + int position = textBox.CaretIndex; + string text = textBox.Text; + + if (_triggerRule.ShouldTriggerCompletion(text, position)) + { + var completions = _completionSource.GetCompletions(text, position); + SetCompletionData(completions); + + var caretPosition = textBox.GetRectFromCharacterIndex(position); + + // Position the popup relative to the caret + HorizontalOffset = caretPosition.Left; + VerticalOffset = caretPosition.Bottom; + + IsOpen = true; + } + else + { + IsOpen = false; + } + } + + private void HandleKeyDown(KeyEventArgs e) + { + if (IsOpen && (e.Key == Key.Up || e.Key == Key.Down)) + { + e.Handled = true; + } + } + + private void SetCompletionData(IEnumerable completions) + { + _completionListBox.Items.Clear(); + foreach (var completion in completions) + { + _completionListBox.Items.Add(completion); + } + + if (_completionListBox.Items.Count > 0) + { + _completionListBox.SelectedIndex = 0; + IsOpen = true; + } + else + { + IsOpen = false; + } + } + + public string SelectedCompletion => (_completionListBox.SelectedItem as ICompletionData)?.Text; + + // public event EventHandler CompletionSelected; + + private void CompletionListBox_MouseUp(object sender, MouseButtonEventArgs e) + { + OnCompletionSelected(); + } + + public DataTemplate CreateVisualTemplate() + { + var type = typeof(ICompletionData); + + // Create a generic DataTemplate that relies on the interface's properties + var template = new DataTemplate(type); + + // Create a TextBlock that binds to the Text property of the ICompletionData + var textBlockFactory = new FrameworkElementFactory(typeof(TextBlock)); + textBlockFactory.SetBinding(TextBlock.TextProperty, new Binding(nameof(ICompletionData.Text))); + + // Optionally add a tooltip or other visual elements based on the interface properties + if (type.GetProperty(nameof(ICompletionData.Description)) is not null) + { + var toolTipBinding = new Binding(nameof(ICompletionData.Description)); + textBlockFactory.SetValue(FrameworkElement.ToolTipProperty, toolTipBinding); + } + + template.VisualTree = textBlockFactory; + + return template; + } + + private void CompletionListBox_KeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Enter || e.Key == Key.Tab) + { + OnCompletionSelected(); + e.Handled = true; + } + else if (e.Key == Key.Escape) + { + IsOpen = false; + } + else if (e.Key == Key.Up || e.Key == Key.Down) + { + e.Handled = false; // Let the ListBox handle up/down navigation + } + else + { + IsOpen = false; + } + } + + private void OnCompletionSelected() + { + if (_completionListBox.SelectedItem != null) + { + CompletionPopup_CompletionSelected(this, SelectedCompletion); + IsOpen = false; + } + } + + + private void CompletionPopup_CompletionSelected(object? sender, string completion) + { + var popup = sender as CompletionPopup; + if (popup?.PlacementTarget is not TextBox textBox) + return; + + int position = textBox.CaretIndex; + + // Find the start of the word being completed + int wordStart = textBox.Text.LastIndexOf('.', position - 1) + 1; + + textBox.Text = textBox.Text.Remove(wordStart, position - wordStart); + textBox.Text = textBox.Text.Insert(wordStart, completion); + textBox.CaretIndex = wordStart + completion.Length; + + textBox.Focus(); + } +} diff --git a/Frank.Wpf.Controls.CompletionPopup/Frank.Wpf.Controls.CompletionPopup.csproj b/Frank.Wpf.Controls.CompletionPopup/Frank.Wpf.Controls.CompletionPopup.csproj new file mode 100644 index 0000000..a6aabdd --- /dev/null +++ b/Frank.Wpf.Controls.CompletionPopup/Frank.Wpf.Controls.CompletionPopup.csproj @@ -0,0 +1,7 @@ + + + + A WPF control that provides a completion popup for text editors. + + + diff --git a/Frank.Wpf.Controls.CompletionPopup/ICompletionData.cs b/Frank.Wpf.Controls.CompletionPopup/ICompletionData.cs new file mode 100644 index 0000000..bd102e7 --- /dev/null +++ b/Frank.Wpf.Controls.CompletionPopup/ICompletionData.cs @@ -0,0 +1,7 @@ +namespace Frank.Wpf.Controls.CompletionPopup; + +public interface ICompletionData +{ + string Text { get; } + string Description { get; } +} \ No newline at end of file diff --git a/Frank.Wpf.Controls.CompletionPopup/ICompletionSource.cs b/Frank.Wpf.Controls.CompletionPopup/ICompletionSource.cs new file mode 100644 index 0000000..76fcb24 --- /dev/null +++ b/Frank.Wpf.Controls.CompletionPopup/ICompletionSource.cs @@ -0,0 +1,6 @@ +namespace Frank.Wpf.Controls.CompletionPopup; + +public interface ICompletionSource +{ + IEnumerable GetCompletions(string text, int position); +} \ No newline at end of file diff --git a/Frank.Wpf.Controls.CompletionPopup/ICompletionTriggerRule.cs b/Frank.Wpf.Controls.CompletionPopup/ICompletionTriggerRule.cs new file mode 100644 index 0000000..1073e03 --- /dev/null +++ b/Frank.Wpf.Controls.CompletionPopup/ICompletionTriggerRule.cs @@ -0,0 +1,6 @@ +namespace Frank.Wpf.Controls.CompletionPopup; + +public interface ICompletionTriggerRule +{ + bool ShouldTriggerCompletion(string text, int position); +} \ No newline at end of file diff --git a/Frank.Wpf.Controls.HttpMessage/Frank.Wpf.Controls.HttpMessage.csproj b/Frank.Wpf.Controls.HttpMessage/Frank.Wpf.Controls.HttpMessage.csproj new file mode 100644 index 0000000..0f42399 --- /dev/null +++ b/Frank.Wpf.Controls.HttpMessage/Frank.Wpf.Controls.HttpMessage.csproj @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Frank.Wpf.Controls.HttpMessage/HttpRequestControl.cs b/Frank.Wpf.Controls.HttpMessage/HttpRequestControl.cs new file mode 100644 index 0000000..d587f94 --- /dev/null +++ b/Frank.Wpf.Controls.HttpMessage/HttpRequestControl.cs @@ -0,0 +1,47 @@ +using System.Net.Http; +using System.Windows.Controls; +using Frank.Http.Abstractions; + +namespace Frank.Wpf.Controls.HttpMessage; + +public class HttpRequestControl : UserControl +{ + private readonly IRestClient _restClient; + private HttpRequestMessageControl? _httpRequestMessageControl; + private HttpResponseMessageControl? _httpResponseMessageControl; + + public HttpRequestControl(IRestClient restClient) + { + _restClient = restClient; + } + + public required HttpRequestMessage HttpRequestMessage + { + get => _httpRequestMessageControl!.HttpRequestMessage; + set + { + if (_httpRequestMessageControl == null) + { + _httpRequestMessageControl = new HttpRequestMessageControl(); + Content = _httpRequestMessageControl; + } + _httpRequestMessageControl.HttpRequestMessage = value; + } + } + + public HttpResponseMessage? HttpResponseMessage + { + get => _httpResponseMessageControl?.HttpResponseMessage; + private set + { + if (_httpResponseMessageControl == null) + { + _httpResponseMessageControl = new HttpResponseMessageControl(); + Content = _httpResponseMessageControl; + } + _httpResponseMessageControl.HttpResponseMessage = value; + } + } + + public async Task SendAsync(CancellationToken cancellationToken = default) => HttpResponseMessage = await _restClient.SendAsync(HttpRequestMessage, cancellationToken); +} \ No newline at end of file diff --git a/Frank.Wpf.Controls.HttpMessage/HttpRequestMessageControl.cs b/Frank.Wpf.Controls.HttpMessage/HttpRequestMessageControl.cs new file mode 100644 index 0000000..a3f38fc --- /dev/null +++ b/Frank.Wpf.Controls.HttpMessage/HttpRequestMessageControl.cs @@ -0,0 +1,26 @@ +using System.Net.Http; +using System.Windows.Controls; + +namespace Frank.Wpf.Controls.HttpMessage; + +public class HttpRequestMessageControl : UserControl +{ + private HttpRequestMessage _httpRequestMessage; + private TextBlock _textBlock; + + public HttpRequestMessageControl() + { + _textBlock = new TextBlock(); + Content = _textBlock; + } + + public HttpRequestMessage HttpRequestMessage + { + get => _httpRequestMessage; + set + { + _httpRequestMessage = value; + _textBlock.Text = _httpRequestMessage.ToString(); + } + } +} \ No newline at end of file diff --git a/Frank.Wpf.Controls.HttpMessage/HttpResponseMessageControl.cs b/Frank.Wpf.Controls.HttpMessage/HttpResponseMessageControl.cs new file mode 100644 index 0000000..483437d --- /dev/null +++ b/Frank.Wpf.Controls.HttpMessage/HttpResponseMessageControl.cs @@ -0,0 +1,35 @@ +using System.Net.Http; +using System.Windows.Controls; + +namespace Frank.Wpf.Controls.HttpMessage; + +public class HttpResponseMessageControl : ContentControl +{ + private HttpResponseMessage? _httpResponseMessage; + private readonly TextBlock _textBlock; + + public HttpResponseMessageControl() + { + _textBlock = new TextBlock(); + + Content = _textBlock; + } + + public HttpResponseMessage? HttpResponseMessage + { + get => _httpResponseMessage; + set + { + _httpResponseMessage = value; + Render(); + } + } + + private void Render() + { + Content = new TextBlock + { + Text = _httpResponseMessage?.ToString() + }; + } +} \ No newline at end of file diff --git a/Frank.Wpf.Controls.HttpMessageRenderer/Frank.Wpf.Controls.HttpMessageRenderer.csproj b/Frank.Wpf.Controls.HttpMessageRenderer/Frank.Wpf.Controls.HttpMessageRenderer.csproj deleted file mode 100644 index f87276e..0000000 --- a/Frank.Wpf.Controls.HttpMessageRenderer/Frank.Wpf.Controls.HttpMessageRenderer.csproj +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/Frank.Wpf.Controls.HttpMessageRenderer/HttpRequestMessageControl.cs b/Frank.Wpf.Controls.HttpMessageRenderer/HttpRequestMessageControl.cs deleted file mode 100644 index 6e7c23d..0000000 --- a/Frank.Wpf.Controls.HttpMessageRenderer/HttpRequestMessageControl.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Net.Http; -using System.Windows.Controls; - -namespace Frank.Wpf.Controls.HttpMessageRenderer; - -public class HttpRequestMessageControl : ContentControl -{ - private HttpRequestMessage _httpRequestMessage; - - public HttpRequestMessageControl(HttpRequestMessage httpRequestMessage) - { - _httpRequestMessage = httpRequestMessage; - Render(); - } - - public void Update(HttpRequestMessage httpRequestMessage) - { - _httpRequestMessage = httpRequestMessage; - Render(); - } - - public void Render() - { - Content = new TextBlock - { - Text = _httpRequestMessage.ToString() - }; - } -} \ No newline at end of file diff --git a/Frank.Wpf.Controls.HttpMessageRenderer/HttpResponseMessageControl.cs b/Frank.Wpf.Controls.HttpMessageRenderer/HttpResponseMessageControl.cs deleted file mode 100644 index 1450fca..0000000 --- a/Frank.Wpf.Controls.HttpMessageRenderer/HttpResponseMessageControl.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Net.Http; -using System.Windows.Controls; - -namespace Frank.Wpf.Controls.HttpMessageRenderer; - -public class HttpResponseMessageControl : ContentControl -{ - private HttpResponseMessage _httpResponseMessage; - - public HttpResponseMessageControl(HttpResponseMessage httpResponseMessage) - { - _httpResponseMessage = httpResponseMessage; - Render(); - } - - public void Update(HttpResponseMessage httpResponseMessage) - { - _httpResponseMessage = httpResponseMessage; - Render(); - } - - public void Render() - { - Content = new TextBlock - { - Text = _httpResponseMessage.ToString() - }; - } -} \ No newline at end of file diff --git a/Frank.Wpf.Controls.JsonRenderer/JsonRendererTreeView.cs b/Frank.Wpf.Controls.JsonRenderer/JsonRendererTreeView.cs index 54c28a2..9cf26ef 100644 --- a/Frank.Wpf.Controls.JsonRenderer/JsonRendererTreeView.cs +++ b/Frank.Wpf.Controls.JsonRenderer/JsonRendererTreeView.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System.ComponentModel; +using System.Text.Json; using System.Windows; using System.Windows.Controls; using System.Windows.Input; @@ -6,7 +7,8 @@ namespace Frank.Wpf.Controls.JsonRenderer; -internal class JsonRendererTreeView : TreeView +[EditorBrowsable(EditorBrowsableState.Never)] +public class JsonRendererTreeView : TreeView { private JsonElement? _selectedElement; private JsonDocument? Document { get; set; } diff --git a/Frank.Wpf.Controls.Pages/Frank.Wpf.Controls.Pages.csproj b/Frank.Wpf.Controls.Pages/Frank.Wpf.Controls.Pages.csproj index 2b8a13c..cb02d82 100644 --- a/Frank.Wpf.Controls.Pages/Frank.Wpf.Controls.Pages.csproj +++ b/Frank.Wpf.Controls.Pages/Frank.Wpf.Controls.Pages.csproj @@ -3,4 +3,8 @@ + + + + diff --git a/Frank.Wpf.Controls.Pages/PageFrame.cs b/Frank.Wpf.Controls.Pages/PageFrame.cs index 0522240..3bf3c33 100644 --- a/Frank.Wpf.Controls.Pages/PageFrame.cs +++ b/Frank.Wpf.Controls.Pages/PageFrame.cs @@ -16,9 +16,15 @@ public PageFrame() NavigationUIVisibility = NavigationUIVisibility.Hidden; } + public Page Page + { + get => Content as Page ?? throw new InvalidOperationException(); + set => Content = value; + } + /// /// Sets the page to be displayed in the frame. /// /// - public void SetPage(Page page) => Content = page; + public void SetPage(Page page) => Page = page; } \ No newline at end of file diff --git a/Frank.Wpf.Controls.Pages/PagedTabControl.cs b/Frank.Wpf.Controls.Pages/PagedTabControl.cs new file mode 100644 index 0000000..fb2b9bf --- /dev/null +++ b/Frank.Wpf.Controls.Pages/PagedTabControl.cs @@ -0,0 +1,42 @@ +using System.Windows.Controls; +using Frank.Wpf.Core; + +namespace Frank.Wpf.Controls.Pages; + +public class PagedTabControl : UserControl +{ + private readonly TabControl _tabControl = new(); + + public PagedTabControl() + { + Content = _tabControl; + } + + public Page? SelectedPage => _tabControl.SelectedItem.As()?.Content.As(); + + public Dock TabStripPlacement + { + get => _tabControl.TabStripPlacement; + set => _tabControl.TabStripPlacement = value; + } + + public IEnumerable Pages + { + get => _tabControl.Items.OfType().Select(x => x.Content.As()).Where(x => x != null).Select(x => x!); + set + { + _tabControl.Items.Clear(); + foreach (var page in value.OrderBy(x => x.Title)) + { + _tabControl.Items.Add(new TabItem + { + Header = page.Title, + Content = new PageFrame() + { + Page = page + } + }); + } + } + } +} \ No newline at end of file diff --git a/Frank.Wpf.Controls.SimpleInputs/BaseControl.cs b/Frank.Wpf.Controls.SimpleInputs/BaseControl.cs new file mode 100644 index 0000000..85dc083 --- /dev/null +++ b/Frank.Wpf.Controls.SimpleInputs/BaseControl.cs @@ -0,0 +1,92 @@ +using System.Windows.Controls; + +namespace Frank.Wpf.Controls.SimpleInputs; + +public abstract class BaseControl : UserControl +{ + private readonly GroupBox _groupBox = new(); + private object? _innerContent; + private bool _isGroupBoxVisible; + + public string Header + { + get => _groupBox.Header as string ?? string.Empty; + set + { + if (string.IsNullOrWhiteSpace(value)) + { + HideGroupBox(); + } + else + { + ShowGroupBoxWithHeader(value); + } + } + } + + public new object? Content + { + get => _isGroupBoxVisible ? _groupBox.Content : _innerContent; + set + { + if (value is GroupBox groupBox) + { + SetGroupBoxContent(groupBox.Content); + } + else + { + SetInnerContent(value); + } + } + } + + private void HideGroupBox() + { + _groupBox.Header = string.Empty; + _isGroupBoxVisible = false; + base.Content = _innerContent; + } + + private void ShowGroupBoxWithHeader(string header) + { + _groupBox.Header = header; + _isGroupBoxVisible = true; + + + _groupBox.Content = _innerContent ?? _groupBox.Content; + if (_groupBox.Content != _innerContent) + { + _groupBox.Content = _innerContent; + } + _groupBox.UpdateLayout(); + + if (_groupBox.Content != _innerContent) + { + _groupBox.Content = _innerContent; + } + + base.Content = _groupBox; + } + + private void SetGroupBoxContent(object? content) + { + _groupBox.Content = content; + _isGroupBoxVisible = true; + base.Content = _groupBox; + } + + private void SetInnerContent(object? value) + { + _innerContent = value; + + if (_isGroupBoxVisible) + { + _groupBox.Content = _innerContent; + base.Content = _groupBox; + } + else + { + base.Content = _innerContent; + } + } +} diff --git a/Frank.Wpf.Controls.SimpleInputs/BigTextInput.cs b/Frank.Wpf.Controls.SimpleInputs/BigTextInput.cs new file mode 100644 index 0000000..9158bdc --- /dev/null +++ b/Frank.Wpf.Controls.SimpleInputs/BigTextInput.cs @@ -0,0 +1,94 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +namespace Frank.Wpf.Controls.SimpleInputs; + +/// +/// A big text input. This is a multiline text input with a header. +/// +public class BigTextInput : UserControl +{ + private readonly TextBox _textBox; + private readonly GroupBox _groupBox = new(); + + /// + /// Creates a new instance of . + /// + public BigTextInput() + { + _textBox = new TextBox + { + AcceptsReturn = true, + AcceptsTab = true + }; + + Content = _textBox; + } + + public event Action? SaveKeyCombination + { + add => _textBox.KeyDown += (_, e) => + { + if (e.KeyboardDevice.Modifiers == (ModifierKeys.Control) && e.Key == Key.S) + { + // Handle the Ctrl+S key combination + e.Handled = true; // Optional: prevents the default save action if any + value?.Invoke(_textBox.Text); + } + }; + remove => _textBox.KeyDown -= (_, e) => + { + if (e.KeyboardDevice.Modifiers == (ModifierKeys.Control) && e.Key == Key.S) + { + // Handle the Ctrl+S key combination + e.Handled = true; // Optional: prevents the default save action if any + value?.Invoke(_textBox.Text); + } + }; + } + + public string Header + { + get => _groupBox.Header as string ?? string.Empty; + set + { + if (string.IsNullOrWhiteSpace(value)) + { + _groupBox.Header = null; + Content = _textBox; + return; + } + + _groupBox.Header = value; + _groupBox.Content = _textBox; + Content = _groupBox; + } + } + + public event Action? TextChanged + { + add => _textBox.TextChanged += (_, _) => value?.Invoke(_textBox.Text); + remove => _textBox.TextChanged -= (_, _) => value?.Invoke(_textBox.Text); + } + + public TextWrapping TextWrapping + { + get => _textBox.TextWrapping; + set => _textBox.TextWrapping = value; + } + + /// + /// The text of the input. + /// + public string Text + { + get => _textBox.Text; + set => _textBox.Text = value; + } + + /// + /// Clears the text of the input. + /// + public void Clear() => _textBox.Clear(); +} \ No newline at end of file diff --git a/Frank.Wpf.Controls.SimpleInputs/PasswordInput.cs b/Frank.Wpf.Controls.SimpleInputs/PasswordInput.cs deleted file mode 100644 index 378a099..0000000 --- a/Frank.Wpf.Controls.SimpleInputs/PasswordInput.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Windows; -using System.Windows.Controls; - -namespace Frank.Wpf.Controls.SimpleInputs; - -public class PasswordInput : GroupBox -{ - private readonly PasswordBox _textBox; - - public PasswordInput(string header, string text, RoutedEventHandler passwordChanged) - { - Header = header; - _textBox = new PasswordBox(); - _textBox.Password = text; - _textBox.PasswordChanged += passwordChanged; - base.Content = _textBox; - } - - public new string Content => _textBox.Password; -} \ No newline at end of file diff --git a/Frank.Wpf.Controls.SimpleInputs/TextBoxWithLineNumbers.cs b/Frank.Wpf.Controls.SimpleInputs/TextBoxWithLineNumbers.cs new file mode 100644 index 0000000..bb048ab --- /dev/null +++ b/Frank.Wpf.Controls.SimpleInputs/TextBoxWithLineNumbers.cs @@ -0,0 +1,136 @@ +using System; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +namespace Frank.Wpf.Controls.SimpleInputs +{ + public class TextBoxWithLineNumbers : UserControl + { + private readonly TextBox _textBox; + private readonly TextBlock _lineNumbers; + + public TextBoxWithLineNumbers() + { + // Initialize TextBlock for line numbers + _lineNumbers = new TextBlock + { + Background = Brushes.LightGray, + VerticalAlignment = VerticalAlignment.Top, + TextAlignment = TextAlignment.Right, + Padding = new Thickness(5, 0, 10, 0), + FontFamily = new FontFamily("Consolas"), + FontSize = 14 + }; + + // Initialize TextBox for text input + _textBox = new TextBox + { + AcceptsReturn = true, + AcceptsTab = true, + FontFamily = new FontFamily("Consolas"), + FontSize = 14, + Padding = new Thickness(5, 0, 5, 0), + VerticalScrollBarVisibility = ScrollBarVisibility.Hidden, + HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden + }; + + // Set up event handlers + _textBox.TextChanged += (s, e) => UpdateLineNumbers(); + _textBox.SizeChanged += (s, e) => UpdateLineNumbers(); + _textBox.LayoutUpdated += (s, e) => UpdateLineNumbers(); + + // Create a Grid to hold the line numbers and the TextBox + var grid = new Grid(); + grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); + grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); + + grid.Children.Add(_lineNumbers); + grid.Children.Add(_textBox); + Grid.SetColumn(_textBox, 1); + + // Wrap the grid in a ScrollViewer + var scrollViewer = new ScrollViewer + { + Content = grid, + VerticalScrollBarVisibility = ScrollBarVisibility.Auto, + HorizontalScrollBarVisibility = ScrollBarVisibility.Auto + }; + + Content = scrollViewer; + + UpdateLineNumbers(); + } + + public void Clear() => _textBox.Clear(); + public int LineCount => _textBox.LineCount; + public string GetLineText(int lineIndex) => _textBox.GetLineText(lineIndex); + + public TextWrapping TextWrapping + { + get => _textBox.TextWrapping; + set => _textBox.TextWrapping = value; + } + + public string Text + { + get => _textBox.Text; + set => _textBox.Text = value; + } + + public new FontFamily FontFamily + { + get => _textBox.FontFamily; + set + { + _textBox.FontFamily = value; + _lineNumbers.FontFamily = value; + } + } + + public new double FontSize + { + get => _textBox.FontSize; + set + { + _textBox.FontSize = value; + _lineNumbers.FontSize = value; + } + } + + public event Action? SaveKeyCombination + { + add => _textBox.KeyDown += (_, e) => + { + if (e.KeyboardDevice.Modifiers == (ModifierKeys.Control) && e.Key == Key.S) + { + // Handle the Ctrl+S key combination + e.Handled = true; // Optional: prevents the default save action if any + value?.Invoke(_textBox.Text); + } + }; + remove => _textBox.KeyDown -= (_, e) => + { + if (e.KeyboardDevice.Modifiers == (ModifierKeys.Control) && e.Key == Key.S) + { + // Handle the Ctrl+S key combination + e.Handled = true; // Optional: prevents the default save action if any + value?.Invoke(_textBox.Text); + } + }; + } + + private void UpdateLineNumbers() + { + var lineCount = _textBox.LineCount; + if (lineCount < 1) + { + lineCount = 1; + } + var lines = Enumerable.Range(1, lineCount).Select(i => i.ToString()).ToArray(); + _lineNumbers.Text = string.Join(Environment.NewLine, lines); + } + } +} diff --git a/Frank.Wpf.Controls.SimpleInputs/TextLabel.cs b/Frank.Wpf.Controls.SimpleInputs/TextLabel.cs index cdc1ee2..beb58b5 100644 --- a/Frank.Wpf.Controls.SimpleInputs/TextLabel.cs +++ b/Frank.Wpf.Controls.SimpleInputs/TextLabel.cs @@ -2,22 +2,32 @@ namespace Frank.Wpf.Controls.SimpleInputs; -public class TextLabel : GroupBox +public class TextLabel : BaseControl { private readonly Label _label; - private string _text; - public TextLabel(string header, string text) + public TextLabel() { - Header = header; - _text = text; - _label = new Label(); - _label.Content = _text; - _label.Height = 32; - base.Content = _label; + _label = new Label + { + Height = 32 + }; + Content = _label; + } + + public double FontSize + { + get => _label.FontSize; + set => _label.FontSize = value; + } + + public double Height + { + get => _label.Height; + set => _label.Height = value; } - public new string Content + public string Text { get => _label.Content as string ?? string.Empty; set => _label.Content = value; diff --git a/Frank.Wpf.Core/BooleanInputAttribute.cs b/Frank.Wpf.Core/Attributes/BooleanInputAttribute.cs similarity index 100% rename from Frank.Wpf.Core/BooleanInputAttribute.cs rename to Frank.Wpf.Core/Attributes/BooleanInputAttribute.cs diff --git a/Frank.Wpf.Core/FileInputAttribute.cs b/Frank.Wpf.Core/Attributes/FileInputAttribute.cs similarity index 100% rename from Frank.Wpf.Core/FileInputAttribute.cs rename to Frank.Wpf.Core/Attributes/FileInputAttribute.cs diff --git a/Frank.Wpf.Core/GuidInputAttribute.cs b/Frank.Wpf.Core/Attributes/GuidInputAttribute.cs similarity index 100% rename from Frank.Wpf.Core/GuidInputAttribute.cs rename to Frank.Wpf.Core/Attributes/GuidInputAttribute.cs diff --git a/Frank.Wpf.Core/InputAttribute.cs b/Frank.Wpf.Core/Attributes/InputAttribute.cs similarity index 100% rename from Frank.Wpf.Core/InputAttribute.cs rename to Frank.Wpf.Core/Attributes/InputAttribute.cs diff --git a/Frank.Wpf.Core/IntegerInputAttribute.cs b/Frank.Wpf.Core/Attributes/IntegerInputAttribute.cs similarity index 100% rename from Frank.Wpf.Core/IntegerInputAttribute.cs rename to Frank.Wpf.Core/Attributes/IntegerInputAttribute.cs diff --git a/Frank.Wpf.Core/NumberInputAttribute.cs b/Frank.Wpf.Core/Attributes/NumberInputAttribute.cs similarity index 100% rename from Frank.Wpf.Core/NumberInputAttribute.cs rename to Frank.Wpf.Core/Attributes/NumberInputAttribute.cs diff --git a/Frank.Wpf.Core/PasswordInputAttribute.cs b/Frank.Wpf.Core/Attributes/PasswordInputAttribute.cs similarity index 100% rename from Frank.Wpf.Core/PasswordInputAttribute.cs rename to Frank.Wpf.Core/Attributes/PasswordInputAttribute.cs diff --git a/Frank.Wpf.Core/TextInputAttribute.cs b/Frank.Wpf.Core/Attributes/TextInputAttribute.cs similarity index 100% rename from Frank.Wpf.Core/TextInputAttribute.cs rename to Frank.Wpf.Core/Attributes/TextInputAttribute.cs diff --git a/Frank.Wpf.Core/ComboboxExtensions.cs b/Frank.Wpf.Core/Extensions/ComboboxExtensions.cs similarity index 100% rename from Frank.Wpf.Core/ComboboxExtensions.cs rename to Frank.Wpf.Core/Extensions/ComboboxExtensions.cs diff --git a/Frank.Wpf.Core/ControlExtensions.cs b/Frank.Wpf.Core/Extensions/ControlExtensions.cs similarity index 100% rename from Frank.Wpf.Core/ControlExtensions.cs rename to Frank.Wpf.Core/Extensions/ControlExtensions.cs diff --git a/Frank.Wpf.Core/DependencyObjectExtensions.cs b/Frank.Wpf.Core/Extensions/DependencyObjectExtensions.cs similarity index 100% rename from Frank.Wpf.Core/DependencyObjectExtensions.cs rename to Frank.Wpf.Core/Extensions/DependencyObjectExtensions.cs diff --git a/Frank.Wpf.Core/FrameworkElementExtensions.cs b/Frank.Wpf.Core/Extensions/FrameworkElementExtensions.cs similarity index 100% rename from Frank.Wpf.Core/FrameworkElementExtensions.cs rename to Frank.Wpf.Core/Extensions/FrameworkElementExtensions.cs diff --git a/Frank.Wpf.Core/GenericExtensions.cs b/Frank.Wpf.Core/Extensions/GenericExtensions.cs similarity index 100% rename from Frank.Wpf.Core/GenericExtensions.cs rename to Frank.Wpf.Core/Extensions/GenericExtensions.cs diff --git a/Frank.Wpf.Core/GridExtensions.cs b/Frank.Wpf.Core/Extensions/GridExtensions.cs similarity index 100% rename from Frank.Wpf.Core/GridExtensions.cs rename to Frank.Wpf.Core/Extensions/GridExtensions.cs diff --git a/Frank.Wpf.Core/MemberInfoExtensions.cs b/Frank.Wpf.Core/Extensions/MemberInfoExtensions.cs similarity index 100% rename from Frank.Wpf.Core/MemberInfoExtensions.cs rename to Frank.Wpf.Core/Extensions/MemberInfoExtensions.cs diff --git a/Frank.Wpf.Core/ObjectExtensions.cs b/Frank.Wpf.Core/Extensions/ObjectExtensions.cs similarity index 100% rename from Frank.Wpf.Core/ObjectExtensions.cs rename to Frank.Wpf.Core/Extensions/ObjectExtensions.cs diff --git a/Frank.Wpf.Core/PropertyInfoExtensions.cs b/Frank.Wpf.Core/Extensions/PropertyInfoExtensions.cs similarity index 100% rename from Frank.Wpf.Core/PropertyInfoExtensions.cs rename to Frank.Wpf.Core/Extensions/PropertyInfoExtensions.cs diff --git a/Frank.Wpf.Core/Extensions/UIElementExtensions.cs b/Frank.Wpf.Core/Extensions/UIElementExtensions.cs new file mode 100644 index 0000000..ae19ef7 --- /dev/null +++ b/Frank.Wpf.Core/Extensions/UIElementExtensions.cs @@ -0,0 +1,24 @@ +using System.Windows; +using System.Windows.Markup; + +namespace Frank.Wpf.Core; + +public static class UIElementExtensions +{ + /// + /// Converts a UIElement to XAML. + /// + /// + /// The XAML representation of the UIElement or an exception message. + public static string ToXaml(this UIElement dumpMe) + { + try + { + return XamlWriter.Save(dumpMe); + } + catch (Exception e) + { + return e.Message + Environment.NewLine + e.StackTrace; + } + } +} \ No newline at end of file diff --git a/Frank.Wpf.Core/WindowExtensions.cs b/Frank.Wpf.Core/Extensions/WindowExtensions.cs similarity index 100% rename from Frank.Wpf.Core/WindowExtensions.cs rename to Frank.Wpf.Core/Extensions/WindowExtensions.cs diff --git a/Frank.Wpf.Core/Frank.Wpf.Core.csproj b/Frank.Wpf.Core/Frank.Wpf.Core.csproj index 11d14b2..6147b9a 100644 --- a/Frank.Wpf.Core/Frank.Wpf.Core.csproj +++ b/Frank.Wpf.Core/Frank.Wpf.Core.csproj @@ -4,7 +4,10 @@ A WPF extension library that provides a variety of extension methods for WPF controls and other WPF-related classes. WPF, Extension, Library, Frank, Haugen true - + + + + diff --git a/Frank.Wpf.Core/Frank.Wpf.Core.csproj.DotSettings b/Frank.Wpf.Core/Frank.Wpf.Core.csproj.DotSettings new file mode 100644 index 0000000..dfcd9a3 --- /dev/null +++ b/Frank.Wpf.Core/Frank.Wpf.Core.csproj.DotSettings @@ -0,0 +1,4 @@ + + True + True + True \ No newline at end of file diff --git a/Frank.Wpf.Core/IO/FileDialogBuilderBase.cs b/Frank.Wpf.Core/IO/FileDialogBuilderBase.cs new file mode 100644 index 0000000..d3ce295 --- /dev/null +++ b/Frank.Wpf.Core/IO/FileDialogBuilderBase.cs @@ -0,0 +1,33 @@ +using System.IO; + +namespace Frank.Wpf.Core; + +public abstract class FileDialogBuilderBase +{ + protected readonly FileDialog _dialog; + + protected FileDialogBuilderBase(FileDialog dialog) + { + _dialog = dialog; + } + + public FileDialogBuilderBase SetDefaultExtension(string defaultExtension) + { + _dialog.DefaultExt = defaultExtension; + return this; + } + + public FileDialogBuilderBase SetFilter(string filter) + { + _dialog.Filter = filter; + return this; + } + + public FileDialogBuilderBase SetInitialDirectory(DirectoryInfo initialDirectory) + { + _dialog.InitialDirectory = initialDirectory.FullName; + return this; + } + + public abstract DialogResult ShowDialog(); +} \ No newline at end of file diff --git a/Frank.Wpf.Core/IO/FileHelper.cs b/Frank.Wpf.Core/IO/FileHelper.cs new file mode 100644 index 0000000..d808d50 --- /dev/null +++ b/Frank.Wpf.Core/IO/FileHelper.cs @@ -0,0 +1,23 @@ +using System.IO; + +namespace Frank.Wpf.Core; + +using System; + +public class FileHelper +{ + public ISingleFileDialogResult CreateSaveFileDialog() + { + return new SaveFileDialogBuilder(); + } + + public ISingleFileDialogResult CreateOpenFileDialog() + { + return new OpenFileDialogBuilder(); + } + + public IMultipleFileDialogResult CreateOpenMultipleFileDialog() + { + return new OpenFileDialogBuilder(); + } +} \ No newline at end of file diff --git a/Frank.Wpf.Core/IO/FilterBuilder.cs b/Frank.Wpf.Core/IO/FilterBuilder.cs new file mode 100644 index 0000000..9aa0946 --- /dev/null +++ b/Frank.Wpf.Core/IO/FilterBuilder.cs @@ -0,0 +1,17 @@ +namespace Frank.Wpf.Core; + +public class FilterBuilder +{ + private readonly List _filterParts = new List(); + + public FilterBuilder AddFilter(string description, string pattern) + { + _filterParts.Add($"{description}|{pattern}"); + return this; + } + + public string Build() + { + return string.Join("|", _filterParts); + } +} \ No newline at end of file diff --git a/Frank.Wpf.Core/IO/IDialogResult.cs b/Frank.Wpf.Core/IO/IDialogResult.cs new file mode 100644 index 0000000..cbe6825 --- /dev/null +++ b/Frank.Wpf.Core/IO/IDialogResult.cs @@ -0,0 +1,6 @@ +namespace Frank.Wpf.Core; + +public interface IDialogResult +{ + DialogResult ShowDialog(); +} \ No newline at end of file diff --git a/Frank.Wpf.Core/IO/IMultipleFileDialogResult.cs b/Frank.Wpf.Core/IO/IMultipleFileDialogResult.cs new file mode 100644 index 0000000..dc7516a --- /dev/null +++ b/Frank.Wpf.Core/IO/IMultipleFileDialogResult.cs @@ -0,0 +1,8 @@ +using System.IO; + +namespace Frank.Wpf.Core; + +public interface IMultipleFileDialogResult : IDialogResult +{ + IEnumerable FileInfos { get; } +} \ No newline at end of file diff --git a/Frank.Wpf.Core/IO/ISingleFileDialogResult.cs b/Frank.Wpf.Core/IO/ISingleFileDialogResult.cs new file mode 100644 index 0000000..7288f21 --- /dev/null +++ b/Frank.Wpf.Core/IO/ISingleFileDialogResult.cs @@ -0,0 +1,8 @@ +using System.IO; + +namespace Frank.Wpf.Core; + +public interface ISingleFileDialogResult : IDialogResult +{ + FileInfo? FileInfo { get; } +} \ No newline at end of file diff --git a/Frank.Wpf.Core/IO/OpenFileDialogBuilder.cs b/Frank.Wpf.Core/IO/OpenFileDialogBuilder.cs new file mode 100644 index 0000000..0527884 --- /dev/null +++ b/Frank.Wpf.Core/IO/OpenFileDialogBuilder.cs @@ -0,0 +1,22 @@ +using System.IO; + +namespace Frank.Wpf.Core; + +public class OpenFileDialogBuilder : FileDialogBuilderBase, ISingleFileDialogResult, IMultipleFileDialogResult +{ + private readonly OpenFileDialog _dialog; + + public OpenFileDialogBuilder() : base(new OpenFileDialog()) + { + _dialog = (OpenFileDialog)_dialog; + } + + public FileInfo? FileInfo => _dialog.FileNames.Length > 0 ? new FileInfo(_dialog.FileNames[0]) : null; + + public IEnumerable FileInfos => _dialog.FileNames.Select(fileName => new FileInfo(fileName)); + + public override DialogResult ShowDialog() + { + return _dialog.ShowDialog(); + } +} \ No newline at end of file diff --git a/Frank.Wpf.Core/IO/SaveFileDialogBuilder.cs b/Frank.Wpf.Core/IO/SaveFileDialogBuilder.cs new file mode 100644 index 0000000..3916d1d --- /dev/null +++ b/Frank.Wpf.Core/IO/SaveFileDialogBuilder.cs @@ -0,0 +1,20 @@ +using System.IO; + +namespace Frank.Wpf.Core; + +public class SaveFileDialogBuilder : FileDialogBuilderBase, ISingleFileDialogResult +{ + private readonly SaveFileDialog _dialog; + + public SaveFileDialogBuilder() : base(new SaveFileDialog()) + { + _dialog = (SaveFileDialog)_dialog; + } + + public FileInfo? FileInfo => _dialog.FileName != null ? new FileInfo(_dialog.FileName) : null; + + public override DialogResult ShowDialog() + { + return _dialog.ShowDialog(); + } +} \ No newline at end of file diff --git a/Frank.Wpf.Core/KnownFontFamilies.cs b/Frank.Wpf.Core/KnownFontFamilies.cs new file mode 100644 index 0000000..0f49607 --- /dev/null +++ b/Frank.Wpf.Core/KnownFontFamilies.cs @@ -0,0 +1,57 @@ +using System.Collections; +using FontFamily = System.Windows.Media.FontFamily; + +namespace Frank.Wpf.Core; + +public class KnownFontFamilies : IEnumerable +{ + /// + public IEnumerator GetEnumerator() + { + yield return new FontFamily(KnownFontFamilyName.Arial); + yield return new FontFamily(KnownFontFamilyName.ArialBlack); + yield return new FontFamily(KnownFontFamilyName.Bahnschrift); + yield return new FontFamily(KnownFontFamilyName.Calibri); + yield return new FontFamily(KnownFontFamilyName.Cambria); + yield return new FontFamily(KnownFontFamilyName.CambriaMath); + yield return new FontFamily(KnownFontFamilyName.Candara); + yield return new FontFamily(KnownFontFamilyName.ComicSansMS); + yield return new FontFamily(KnownFontFamilyName.Consolas); + yield return new FontFamily(KnownFontFamilyName.Constantia); + yield return new FontFamily(KnownFontFamilyName.Corbel); + yield return new FontFamily(KnownFontFamilyName.CourierNew); + yield return new FontFamily(KnownFontFamilyName.Ebrima); + yield return new FontFamily(KnownFontFamilyName.FranklinGothicMedium); + yield return new FontFamily(KnownFontFamilyName.Gabriola); + yield return new FontFamily(KnownFontFamilyName.Gadugi); + yield return new FontFamily(KnownFontFamilyName.Georgia); + yield return new FontFamily(KnownFontFamilyName.Impact); + yield return new FontFamily(KnownFontFamilyName.InkFree); + yield return new FontFamily(KnownFontFamilyName.JavaneseText); + yield return new FontFamily(KnownFontFamilyName.LeelawadeeUI); + yield return new FontFamily(KnownFontFamilyName.LucidaConsole); + yield return new FontFamily(KnownFontFamilyName.LucidaSansUnicode); + yield return new FontFamily(KnownFontFamilyName.MalgunGothic); + yield return new FontFamily(KnownFontFamilyName.Mangal); + yield return new FontFamily(KnownFontFamilyName.Marlett); + yield return new FontFamily(KnownFontFamilyName.MicrosoftHimalaya); + yield return new FontFamily(KnownFontFamilyName.MicrosoftJhengHei); + yield return new FontFamily(KnownFontFamilyName.MicrosoftJhengHeiUI); + yield return new FontFamily(KnownFontFamilyName.MicrosoftNewTaiLue); + yield return new FontFamily(KnownFontFamilyName.MicrosoftPhagsPa); + yield return new FontFamily(KnownFontFamilyName.MicrosoftSansSerif); + yield return new FontFamily(KnownFontFamilyName.MicrosoftTaiLe); + yield return new FontFamily(KnownFontFamilyName.Tahoma); + yield return new FontFamily(KnownFontFamilyName.TimesNewRoman); + yield return new FontFamily(KnownFontFamilyName.TrebuchetMS); + yield return new FontFamily(KnownFontFamilyName.Verdana); + yield return new FontFamily(KnownFontFamilyName.Webdings); + yield return new FontFamily(KnownFontFamilyName.Wingdings); + yield return new FontFamily(KnownFontFamilyName.YuGothic); + yield return new FontFamily(KnownFontFamilyName.YuGothicUI); + yield return new FontFamily(KnownFontFamilyName.JetBrainsMono); + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} \ No newline at end of file diff --git a/Frank.Wpf.Core/KnownFontFamilyName.cs b/Frank.Wpf.Core/KnownFontFamilyName.cs new file mode 100644 index 0000000..951510f --- /dev/null +++ b/Frank.Wpf.Core/KnownFontFamilyName.cs @@ -0,0 +1,101 @@ +using System.Collections; + +namespace Frank.Wpf.Core; + +public class KnownFontFamilyNames : IEnumerable +{ + /// + public IEnumerator GetEnumerator() + { + yield return KnownFontFamilyName.Arial; + yield return KnownFontFamilyName.ArialBlack; + yield return KnownFontFamilyName.Bahnschrift; + yield return KnownFontFamilyName.Calibri; + yield return KnownFontFamilyName.Cambria; + yield return KnownFontFamilyName.CambriaMath; + yield return KnownFontFamilyName.Candara; + yield return KnownFontFamilyName.ComicSansMS; + yield return KnownFontFamilyName.Consolas; + yield return KnownFontFamilyName.Constantia; + yield return KnownFontFamilyName.Corbel; + yield return KnownFontFamilyName.CourierNew; + yield return KnownFontFamilyName.Ebrima; + yield return KnownFontFamilyName.FranklinGothicMedium; + yield return KnownFontFamilyName.Gabriola; + yield return KnownFontFamilyName.Gadugi; + yield return KnownFontFamilyName.Georgia; + yield return KnownFontFamilyName.Impact; + yield return KnownFontFamilyName.InkFree; + yield return KnownFontFamilyName.JavaneseText; + yield return KnownFontFamilyName.LeelawadeeUI; + yield return KnownFontFamilyName.LucidaConsole; + yield return KnownFontFamilyName.LucidaSansUnicode; + yield return KnownFontFamilyName.MalgunGothic; + yield return KnownFontFamilyName.Mangal; + yield return KnownFontFamilyName.Marlett; + yield return KnownFontFamilyName.MicrosoftHimalaya; + yield return KnownFontFamilyName.MicrosoftJhengHei; + yield return KnownFontFamilyName.MicrosoftJhengHeiUI; + yield return KnownFontFamilyName.MicrosoftNewTaiLue; + yield return KnownFontFamilyName.MicrosoftPhagsPa; + yield return KnownFontFamilyName.MicrosoftSansSerif; + yield return KnownFontFamilyName.MicrosoftTaiLe; + yield return KnownFontFamilyName.Tahoma; + yield return KnownFontFamilyName.TimesNewRoman; + yield return KnownFontFamilyName.TrebuchetMS; + yield return KnownFontFamilyName.Verdana; + yield return KnownFontFamilyName.Webdings; + yield return KnownFontFamilyName.Wingdings; + yield return KnownFontFamilyName.YuGothic; + yield return KnownFontFamilyName.YuGothicUI; + yield return KnownFontFamilyName.JetBrainsMono; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} +public static class KnownFontFamilyName +{ + public const string Arial = "Arial"; + public const string ArialBlack = "Arial Black"; + public const string Bahnschrift = "Bahnschrift"; + public const string Calibri = "Calibri"; + public const string Cambria = "Cambria"; + public const string CambriaMath = "Cambria Math"; + public const string Candara = "Candara"; + public const string ComicSansMS = "Comic Sans MS"; + public const string Consolas = "Consolas"; + public const string Constantia = "Constantia"; + public const string Corbel = "Corbel"; + public const string CourierNew = "Courier New"; + public const string Ebrima = "Ebrima"; + public const string FranklinGothicMedium = "Franklin Gothic Medium"; + public const string Gabriola = "Gabriola"; + public const string Gadugi = "Gadugi"; + public const string Georgia = "Georgia"; + public const string Impact = "Impact"; + public const string InkFree = "Ink Free"; + public const string JavaneseText = "Javanese Text"; + public const string LeelawadeeUI = "Leelawadee UI"; + public const string LucidaConsole = "Lucida Console"; + public const string LucidaSansUnicode = "Lucida Sans Unicode"; + public const string MalgunGothic = "Malgun Gothic"; + public const string Mangal = "Mangal"; + public const string Marlett = "Marlett"; + public const string MicrosoftHimalaya = "Microsoft Himalaya"; + public const string MicrosoftJhengHei = "Microsoft JhengHei"; + public const string MicrosoftJhengHeiUI = "Microsoft JhengHei UI"; + public const string MicrosoftNewTaiLue = "Microsoft New Tai Lue"; + public const string MicrosoftPhagsPa = "Microsoft PhagsPa"; + public const string MicrosoftSansSerif = "Microsoft Sans Serif"; + public const string MicrosoftTaiLe = "Microsoft Tai Le"; + public const string Tahoma = "Tahoma"; + public const string TimesNewRoman = "Times New Roman"; + public const string TrebuchetMS = "Trebuchet MS"; + public const string Verdana = "Verdana"; + public const string Webdings = "Webdings"; + public const string Wingdings = "Wingdings"; + public const string YuGothic = "Yu Gothic"; + public const string YuGothicUI = "Yu Gothic UI"; + public const string JetBrainsMono = "JetBrains Mono"; +} \ No newline at end of file diff --git a/Frank.Wpf.Core/XamlSerializer.cs b/Frank.Wpf.Core/XamlSerializer.cs new file mode 100644 index 0000000..83dcaeb --- /dev/null +++ b/Frank.Wpf.Core/XamlSerializer.cs @@ -0,0 +1,58 @@ +using System.IO; +using System.Reflection; +using System.Windows; +using System.Xml; + +namespace Frank.Wpf.Core; + +public static class XamlSerializer +{ + public static string SerializeToXaml(UIElement element) + { + using var stringWriter = new StringWriter(); + using var xmlWriter = XmlWriter.Create(stringWriter, new XmlWriterSettings { Indent = true }); + + xmlWriter.WriteStartDocument(); + var visitedElements = new HashSet(); + WriteElement(xmlWriter, element, visitedElements); + xmlWriter.WriteEndDocument(); + + var result = string.Empty; + stringWriter.Write(result); + return result; + } + + private static void WriteElement(XmlWriter writer, UIElement element, HashSet visitedElements) + { + if (element == null || !visitedElements.Add(element)) + return; + + var type = element.GetType(); + writer.WriteStartElement(type.Name, type.Namespace); + + // Write attributes first + foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (property.CanRead && property.GetValue(element) is not null) + { + var value = property.GetValue(element); + + if (value is string || value.GetType().IsPrimitive) + { + writer.WriteAttributeString(property.Name, value.ToString()); + } + } + } + + // Write child elements + foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (property.CanRead && property.GetValue(element) is UIElement childElement) + { + WriteElement(writer, childElement, visitedElements); + } + } + + writer.WriteEndElement(); + } +} \ No newline at end of file diff --git a/Frank.Wpf.Dialogs/Frank.Wpf.Dialogs.csproj b/Frank.Wpf.Dialogs/Frank.Wpf.Dialogs.csproj index 2b8a13c..14ab203 100644 --- a/Frank.Wpf.Dialogs/Frank.Wpf.Dialogs.csproj +++ b/Frank.Wpf.Dialogs/Frank.Wpf.Dialogs.csproj @@ -3,4 +3,8 @@ + + + + diff --git a/Frank.Wpf.Dialogs/XamlWindow.cs b/Frank.Wpf.Dialogs/XamlWindow.cs new file mode 100644 index 0000000..93f2f8b --- /dev/null +++ b/Frank.Wpf.Dialogs/XamlWindow.cs @@ -0,0 +1,26 @@ +using System.Windows; +using Frank.Wpf.Controls.SimpleInputs; +using Frank.Wpf.Core; + +namespace Frank.Wpf.Dialogs; + +public class XamlWindow : Window where T : UIElement +{ + public XamlWindow(T uiElement) + { + Title = "XAML of " + uiElement.GetType().Name; + SizeToContent = SizeToContent.WidthAndHeight; + MinWidth = 300; + MinHeight = 250; + + MaxWidth = 800; + MaxHeight = 600; + + var textBoxWithLineNumbers = new TextBoxWithLineNumbers + { + Text = uiElement.ToXaml() + }; + + Content = textBoxWithLineNumbers; + } +} \ No newline at end of file diff --git a/Frank.Wpf.Hosting/IPageFactory.cs b/Frank.Wpf.Hosting/IPageFactory.cs new file mode 100644 index 0000000..270b1f8 --- /dev/null +++ b/Frank.Wpf.Hosting/IPageFactory.cs @@ -0,0 +1,12 @@ +using System.Windows.Controls; + +namespace Frank.Wpf.Hosting; + +public interface IPageFactory +{ + T CreatePage() where T : Page; + + T CreatePage(bool newInstanceOfPage) where T : Page; + + T CreatePage(Action configure, bool newInstanceOfPage) where T : Page; +} \ No newline at end of file diff --git a/Frank.Wpf.Hosting/PageFactory.cs b/Frank.Wpf.Hosting/PageFactory.cs new file mode 100644 index 0000000..7695a3c --- /dev/null +++ b/Frank.Wpf.Hosting/PageFactory.cs @@ -0,0 +1,24 @@ +using System.Windows.Controls; + +namespace Frank.Wpf.Hosting; + +public class PageFactory : IPageFactory +{ + private readonly IEnumerable _pages; + + public PageFactory(IEnumerable pages) + { + _pages = pages; + } + + public T CreatePage() where T : Page => _pages.OfType().FirstOrDefault() ?? throw new InvalidOperationException($"Page of type {typeof(T).Name} not found"); + + public T CreatePage(bool newInstanceOfPage) where T : Page => newInstanceOfPage ? Activator.CreateInstance() : CreatePage(); + + public T CreatePage(Action configure, bool newInstanceOfPage) where T : Page + { + var page = CreatePage(newInstanceOfPage); + configure(page); + return page; + } +} \ No newline at end of file diff --git a/Frank.Wpf.Hosting/ServiceCollectionExtensions.cs b/Frank.Wpf.Hosting/ServiceCollectionExtensions.cs index 18e19d3..6c1ae68 100644 --- a/Frank.Wpf.Hosting/ServiceCollectionExtensions.cs +++ b/Frank.Wpf.Hosting/ServiceCollectionExtensions.cs @@ -19,7 +19,7 @@ public static IServiceCollection AddPage(this IServiceCollection services) wh public static IServiceCollection AddWindow(this IServiceCollection services) where T : Window { services.AddTransient(); - services.AddTransient(provider => provider.GetRequiredService()); + services.AddTransient(provider => provider.GetRequiredService()); return services; } diff --git a/Frank.Wpf.Hosting/WpfHostBuilder.cs b/Frank.Wpf.Hosting/WpfHostBuilder.cs index c23cb01..5dfba8b 100644 --- a/Frank.Wpf.Hosting/WpfHostBuilder.cs +++ b/Frank.Wpf.Hosting/WpfHostBuilder.cs @@ -47,6 +47,7 @@ public WpfHost Build() where T : MainWindow Services.AddSingleton(); Services.AddSingleton(); Services.AddSingleton(); + Services.AddSingleton(); // Add the services' collection to the services' collection. This is used to resolve services from the serviceprovider after the host is built. // This is a workaround to resolve services from the serviceprovider after the host is built. diff --git a/Frank.Wpf.Tests.App/DefaultWindow.cs b/Frank.Wpf.Tests.App/DefaultWindow.cs index 442b08c..046908a 100644 --- a/Frank.Wpf.Tests.App/DefaultWindow.cs +++ b/Frank.Wpf.Tests.App/DefaultWindow.cs @@ -18,8 +18,7 @@ public DefaultWindow() Title = "Frank.Wpf.Tests.App"; Content = _windowButtons; - Width = defaultWidth; - Height = defaultHeight; + SizeToContent = SizeToContent.WidthAndHeight; WindowStartupLocation = defaultPosition; _windowButtons.AddButton("Show SQL Runner", (sender, args) => @@ -98,5 +97,71 @@ public DefaultWindow() }; window.Show(); }); + + _windowButtons.AddButton("Show Paged Tab Control Window", (sender, args) => + { + var window = new PagedTabControlWindow + { + Width = defaultWidth, + Height = defaultHeight, + WindowStartupLocation = defaultPosition + }; + window.Show(); + }); + + _windowButtons.AddButton("Show Big Text Input Window", (sender, args) => + { + var window = new BigTextInputWindow + { + Width = defaultWidth, + Height = defaultHeight, + WindowStartupLocation = defaultPosition + }; + window.Show(); + }); + + _windowButtons.AddButton("Show Text Box With Line Numbers Window", (sender, args) => + { + var window = new TextBoxWithLineNumbersWindow + { + Width = defaultWidth, + Height = defaultHeight, + WindowStartupLocation = defaultPosition + }; + window.Show(); + }); + + _windowButtons.AddButton("Show Text Label Experimentation Window", (sender, args) => + { + var window = new TextLabelExperimentationWindow + { + Width = defaultWidth, + Height = defaultHeight, + WindowStartupLocation = defaultPosition + }; + window.Show(); + }); + + _windowButtons.AddButton("Show Text Completion Window", (sender, args) => + { + var window = new TextCompletionWindow + { + Width = defaultWidth, + Height = defaultHeight, + WindowStartupLocation = defaultPosition + }; + window.Show(); + }); + + _windowButtons.AddButton("Show Code Window", (sender, args) => + { + var window = new CodeWindow + { + Width = defaultWidth, + Height = defaultHeight, + WindowStartupLocation = defaultPosition + }; + window.Show(); + }); } } \ No newline at end of file diff --git a/Frank.Wpf.Tests.App/Frank.Wpf.Tests.App.csproj b/Frank.Wpf.Tests.App/Frank.Wpf.Tests.App.csproj index a9a8bfb..ff07b9e 100644 --- a/Frank.Wpf.Tests.App/Frank.Wpf.Tests.App.csproj +++ b/Frank.Wpf.Tests.App/Frank.Wpf.Tests.App.csproj @@ -7,11 +7,14 @@ + + + diff --git a/Frank.Wpf.Tests.App/Windows/BigTextInputWindow.cs b/Frank.Wpf.Tests.App/Windows/BigTextInputWindow.cs new file mode 100644 index 0000000..1d6d992 --- /dev/null +++ b/Frank.Wpf.Tests.App/Windows/BigTextInputWindow.cs @@ -0,0 +1,24 @@ +using System.Windows; +using Frank.Wpf.Controls.SimpleInputs; + +namespace Frank.Wpf.Tests.App.Windows; + +public class BigTextInputWindow : Window +{ + private readonly BigTextInput _bigTextInput; + + public BigTextInputWindow() + { + _bigTextInput = new BigTextInput() + { + Header = "Big Text Input", + Text = "Hello, World!" + }; + + Content = _bigTextInput; + + _bigTextInput.TextChanged += text => Title = text; + + _bigTextInput.SaveKeyCombination += text => MessageBox.Show($"Saved:\n\n{text}"); + } +} \ No newline at end of file diff --git a/Frank.Wpf.Tests.App/Windows/CodeWindow.cs b/Frank.Wpf.Tests.App/Windows/CodeWindow.cs new file mode 100644 index 0000000..01f0883 --- /dev/null +++ b/Frank.Wpf.Tests.App/Windows/CodeWindow.cs @@ -0,0 +1,67 @@ +using System.Windows; +using System.Windows.Controls; + +namespace Frank.Wpf.Tests.App.Windows; + +public class CodeWindow : Window +{ + public CodeWindow() + { + Title = "Code Window"; + Width = 800; + Height = 600; + WindowStartupLocation = WindowStartupLocation.CenterScreen; + + var codeArea = new Frank.Wpf.Controls.Code.CodeArea(new ICSharpCode.AvalonEdit.Highlighting.HighlightingDefinitionTypeConverter().ConvertFrom("C#") as ICSharpCode.AvalonEdit.Highlighting.IHighlightingDefinition ?? throw new InvalidOperationException()); + codeArea.Text = """ + { + "name": "Frank", + "age": 30, + "isCool": true, + "address": { + "street": "123 Main St", + "city": "Anytown", + "state": "AS", + "zip": 12345}, + "phoneNumbers": [ + "123-456-7890", + "098-765-4321" + ] + } + """; + + var codeBeautifier = new Frank.Wpf.Controls.Code.JsonBeautifier(); + + var beautifyButton = new Button + { + Content = "Beautify", + Margin = new(5), + Padding = new(5), + HorizontalAlignment = HorizontalAlignment.Left + }; + + beautifyButton.Click += (sender, args) => codeArea.Beautify(codeBeautifier); + + var stackPanel = new StackPanel + { + Orientation = Orientation.Horizontal, + Margin = new(5), + Children = + { + beautifyButton + } + }; + + var grid = new Grid(); + + grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); + grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }); + + grid.Children.Add(stackPanel); + grid.Children.Add(codeArea); + + Grid.SetRow(codeArea, 1); + + Content = grid; + } +} \ No newline at end of file diff --git a/Frank.Wpf.Tests.App/Windows/PagedTabControlWindow.cs b/Frank.Wpf.Tests.App/Windows/PagedTabControlWindow.cs new file mode 100644 index 0000000..8f1915f --- /dev/null +++ b/Frank.Wpf.Tests.App/Windows/PagedTabControlWindow.cs @@ -0,0 +1,64 @@ +using System.Windows; +using System.Windows.Controls; +using Frank.Wpf.Controls.Pages; + +namespace Frank.Wpf.Tests.App.Windows; + +public class PagedTabControlWindow : Window +{ + public PagedTabControlWindow() + { + Title = "Paged Tab Control Window"; + Width = 800; + Height = 600; + Content = new PagedTabControl() + { + TabStripPlacement = Dock.Left, + Pages = new Page[] + { + new Page1(), + new Page2(), + new Page3() + } + }; + } + + private class Page1 : Page + { + public Page1() + { + Title = "Page 1"; + Content = new TextBlock + { + Text = "Page 1", + FontSize = 24 + }; + } + } + + private class Page2 : Page + { + public Page2() + { + Title = "Page 2"; + Content = new TextBlock + { + Text = "Page 2", + FontSize = 24 + }; + } + } + + private class Page3 : Page + { + public Page3() + { + Title = "Page 3"; + Content = new TextBlock + { + Text = "Page 3", + FontSize = 24 + }; + } + } +} \ No newline at end of file diff --git a/Frank.Wpf.Tests.App/Windows/TextBoxWithLineNumbersWindow.cs b/Frank.Wpf.Tests.App/Windows/TextBoxWithLineNumbersWindow.cs new file mode 100644 index 0000000..4cb2664 --- /dev/null +++ b/Frank.Wpf.Tests.App/Windows/TextBoxWithLineNumbersWindow.cs @@ -0,0 +1,38 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using Frank.Wpf.Controls.SimpleInputs; +using Frank.Wpf.Core; + +namespace Frank.Wpf.Tests.App.Windows; + +public class TextBoxWithLineNumbersWindow : Window +{ + private readonly StackPanel _stackPanel = new(); + private readonly TextBoxWithLineNumbers _textBoxWithLineNumbers; + private readonly Dropdown _fontFamilyDropdown; + public TextBoxWithLineNumbersWindow() + { + Title = "Text Box With Line Numbers Window"; + Width = 800; + Height = 600; + _textBoxWithLineNumbers = new TextBoxWithLineNumbers() + { + Text = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\nLine 7\nLine 8\nLine 9\nLine 10", + FontSize = 16, + FontFamily = new FontFamily(KnownFontFamilyName.ComicSansMS), + }; + + _fontFamilyDropdown = new Dropdown() + { + Items = new KnownFontFamilies(), + DisplayFunc = x => x.Source, + SelectionChangedAction = x => _textBoxWithLineNumbers.FontFamily = x, + }; + + _stackPanel.Children.Add(_fontFamilyDropdown); + _stackPanel.Children.Add(_textBoxWithLineNumbers); + + Content = _stackPanel; + } +} \ No newline at end of file diff --git a/Frank.Wpf.Tests.App/Windows/TextCompletionWindow.cs b/Frank.Wpf.Tests.App/Windows/TextCompletionWindow.cs new file mode 100644 index 0000000..cc6f557 --- /dev/null +++ b/Frank.Wpf.Tests.App/Windows/TextCompletionWindow.cs @@ -0,0 +1,64 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using Frank.Wpf.Controls.CompletionPopup; + +namespace Frank.Wpf.Tests.App.Windows; + +public class TextCompletionWindow : Window +{ + private readonly CompletionPopup _completionPopup = new(); + private readonly TextBox _scriptTextBox = new() + { + AcceptsReturn = true, + AcceptsTab = true, + HorizontalScrollBarVisibility = ScrollBarVisibility.Auto, + VerticalScrollBarVisibility = ScrollBarVisibility.Auto, + TextWrapping = TextWrapping.Wrap, + FontFamily = new FontFamily("Consolas"), + FontSize = 14, + Text = "Console" + }; + + public TextCompletionWindow() + { + var completions = new List + { + new CompletionData("WriteLine"), + new CompletionData("Write"), + new CompletionData("ReadLine") + }; + + // Initialize the CompletionPopup with a basic completion source and trigger rule + _completionPopup.Initialize(_scriptTextBox, new BasicCompletionSource(completions), new DotCompletionTriggerRule()); + + // Handle the completion selection event + // _completionPopup.CompletionSelected += CompletionPopup_CompletionSelected; + + Content = _scriptTextBox; + } + + + public class BasicCompletionSource : ICompletionSource + { + private readonly List _completions; + + public BasicCompletionSource(IEnumerable completions) + { + _completions = completions.ToList(); + } + + public IEnumerable GetCompletions(string text, int position) + { + return _completions; + } + } + + public class DotCompletionTriggerRule : ICompletionTriggerRule + { + public bool ShouldTriggerCompletion(string text, int position) + { + return position > 0 && text[position - 1] == '.'; + } + } +} \ No newline at end of file diff --git a/Frank.Wpf.Tests.App/Windows/TextLabelExperimentationWindow.cs b/Frank.Wpf.Tests.App/Windows/TextLabelExperimentationWindow.cs new file mode 100644 index 0000000..044f37b --- /dev/null +++ b/Frank.Wpf.Tests.App/Windows/TextLabelExperimentationWindow.cs @@ -0,0 +1,71 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using Frank.Wpf.Controls.SimpleInputs; +using Frank.Wpf.Core; +using Frank.Wpf.Dialogs; + +namespace Frank.Wpf.Tests.App.Windows; + +public class TextLabelExperimentationWindow : Window +{ + private readonly StackPanel _stackPanel = new(); + private readonly ComboBox _dropdown; + private readonly TextLabel _textLabel; + + public TextLabelExperimentationWindow() + { + Title = "Text Label Experimentation"; + SizeToContent = SizeToContent.WidthAndHeight; + + _textLabel = new TextLabel + { + Text = "Text", + Header = "Initial Header" + }; + + var items = new[] {"", "Item 2", "Item 3"}; + + _dropdown = new ComboBox(); + + foreach (var item in items) + { + _dropdown.Items.Add(item); + } + + _dropdown.SelectionChanged += (sender, args) => + { + var header = _dropdown.SelectedItem.As()?.Content.As(); + + if (header == null) + { + header = _dropdown.SelectedItem.As(); + } + + _textLabel.Header = header ?? string.Empty; + }; + + _stackPanel.Children.Add(_dropdown); + _stackPanel.Children.Add(_textLabel); + + Content = _stackPanel; + } + + /// + protected override void OnKeyDown(KeyEventArgs e) + { + // If Ctrl + Alt + D is pressed, open a new window with a readonly textbox and a button to close the window showing the XAML of the window's content + if (e.Key == Key.D && Keyboard.Modifiers == (ModifierKeys.Control | ModifierKeys.Alt)) + { + var window = new XamlWindow(_stackPanel); + window.Show(); + } + + if (e.Key == Key.Escape) + { + Close(); + } + + base.OnKeyDown(e); + } +} \ No newline at end of file diff --git a/Frank.Wpf.Tests/Frank.Wpf.Tests.csproj b/Frank.Wpf.Tests/Frank.Wpf.Tests.csproj index fc2526a..e882f69 100644 --- a/Frank.Wpf.Tests/Frank.Wpf.Tests.csproj +++ b/Frank.Wpf.Tests/Frank.Wpf.Tests.csproj @@ -24,7 +24,9 @@ + + diff --git a/Frank.Wpf.Tests/JsonRendererTreeViewTests.cs b/Frank.Wpf.Tests/JsonRendererTreeViewTests.cs new file mode 100644 index 0000000..ba5b1db --- /dev/null +++ b/Frank.Wpf.Tests/JsonRendererTreeViewTests.cs @@ -0,0 +1,66 @@ +using System.IO; +using System.Windows.Markup; +using Frank.Wpf.Controls.JsonRenderer; +using Frank.Wpf.Controls.SimpleInputs; +using Xunit.Abstractions; + +namespace Frank.Wpf.Tests; + +public class JsonRendererTreeViewTests +{ + private readonly ITestOutputHelper _outputHelper; + + public JsonRendererTreeViewTests(ITestOutputHelper outputHelper) + { + _outputHelper = outputHelper; + } + + [WpfFact] + public void Test1() + { + var renderer = new JsonRendererTreeView(); + renderer.Render("""{"key":"value"}"""); + + var result = XamlWriter.Save(renderer); + + _outputHelper.WriteLine(result); + } + + [WpfFact] + public void Test2() + { + var textBoxWithLineNumbers = new TextBoxWithLineNumbers(); + textBoxWithLineNumbers.Text = "Hello world"; + + var result = XamlWriter.Save(textBoxWithLineNumbers); + + _outputHelper.WriteLine(result); + } + + [WpfFact] + public void Test3() + { + var label = new TextLabel() + { + Text = "Hello world" + }; + + _outputHelper.WriteLine($""); + + _outputHelper.WriteLine($"{XamlWriter.Save(label)}"); + + label.Header = "My Header"; + + _outputHelper.WriteLine($"{XamlWriter.Save(label)}"); + + label.Header = string.Empty; + + _outputHelper.WriteLine($"{XamlWriter.Save(label)}"); + + label.Header = "My Header"; + + _outputHelper.WriteLine($"{XamlWriter.Save(label)}"); + + _outputHelper.WriteLine($""); + } +} \ No newline at end of file diff --git a/Frank.Wpf.Tests/UnitTest1.cs b/Frank.Wpf.Tests/UnitTest1.cs deleted file mode 100644 index 7bb7e8c..0000000 --- a/Frank.Wpf.Tests/UnitTest1.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.IO; -using System.Windows.Markup; -using Frank.Wpf.Controls.JsonRenderer; -using Xunit.Abstractions; - -namespace Frank.Wpf.Tests; - -public class UnitTest1 -{ - private readonly ITestOutputHelper _outputHelper; - - public UnitTest1(ITestOutputHelper outputHelper) - { - _outputHelper = outputHelper; - } - - [WpfFact] - public void Test1() - { - var renderer = new JsonRendererTreeView(); - renderer.Render("""{"key":"value"}"""); - - using var writer = new StringWriter(); - - - XamlWriter.Save(renderer, writer); - - _outputHelper.WriteLine(writer.ToString()); - } -} \ No newline at end of file diff --git a/Frank.Wpf.Tests/XamlSerializerTests.cs b/Frank.Wpf.Tests/XamlSerializerTests.cs new file mode 100644 index 0000000..4a3b48b --- /dev/null +++ b/Frank.Wpf.Tests/XamlSerializerTests.cs @@ -0,0 +1,125 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Markup; +using Frank.Wpf.Core; +using Xunit.Abstractions; + +namespace Frank.Wpf.Tests; + +public class XamlSerializerTests +{ + private readonly ITestOutputHelper _outputHelper; + + public XamlSerializerTests(ITestOutputHelper outputHelper) + { + _outputHelper = outputHelper; + } + + [WpfFact] + public void Test1() + { + var uiElement = new System.Windows.Controls.StackPanel + { + Orientation = System.Windows.Controls.Orientation.Horizontal, + Children = + { + new System.Windows.Controls.Button + { + Content = "Click me" + } + } + }; + + var result = XamlWriter.Save(uiElement); + _outputHelper.WriteLine(result); + } + + // [WpfFact] + public void Test2() + { + var uiElement = new StackPanel + { + Orientation = Orientation.Horizontal, + Children = + { + new MyDropDown() + { + Items = new[] { "One", "Two", "Three" }, + DisplayFunc = x => x, + SelectionChangedAction = x => { } + } + } + }; + + var result = XamlWriter.Save(uiElement); + _outputHelper.WriteLine(result); + + Assert.Contains("One", result); + } + +} +public class MyDropDown : UserControl +{ + private readonly ComboBox _comboBox = new(); + + public MyDropDown() + { + _comboBox.SelectionChanged += ComboBox_SelectionChanged; + Content = _comboBox; + } + + public IEnumerable Items + { + get => (IEnumerable)_comboBox.ItemsSource; + set => _comboBox.ItemsSource = value; + } + + public Func DisplayFunc + { + init => _comboBox.ItemTemplate = CreateDataTemplate(value); + } + + public Action SelectionChangedAction { get; init; } + + private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (_comboBox.SelectedItem is T selectedItem) + { + SelectionChangedAction(selectedItem); + } + } + + private DataTemplate CreateDataTemplate(Func displayFunc) + { + var dataTemplate = new DataTemplate(typeof(T)); + var factory = new FrameworkElementFactory(typeof(TextBlock)); + factory.SetBinding(TextBlock.TextProperty, new System.Windows.Data.Binding + { + Converter = new FuncValueConverter(displayFunc), + Mode = System.Windows.Data.BindingMode.OneWay + }); + dataTemplate.VisualTree = factory; + return dataTemplate; + } + + // Converter for converting the Func to a binding-friendly format + private class FuncValueConverter : System.Windows.Data.IValueConverter + { + private readonly Func _func; + + public FuncValueConverter(Func func) + { + _func = func; + } + + public object? Convert(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture) + { + return value is TInput input ? _func(input) : default; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture) + { + return value is TOutput output ? output : default; + } + } +} diff --git a/Frank.Wpf.sln b/Frank.Wpf.sln index f5fe935..baa9b85 100644 --- a/Frank.Wpf.sln +++ b/Frank.Wpf.sln @@ -50,12 +50,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.Wpf.Windows.SqlRunner EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.Wpf.Controls.Console", "Frank.Wpf.Controls.Console\Frank.Wpf.Controls.Console.csproj", "{2ACEECC6-D78A-43FE-B0B1-6058C9CB0B7E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.Wpf.Controls.HttpMessageRenderer", "Frank.Wpf.Controls.HttpMessageRenderer\Frank.Wpf.Controls.HttpMessageRenderer.csproj", "{4F4A407A-193B-4B10-A3EE-545D0F9F787E}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.Wpf.Controls.HttpMessage", "Frank.Wpf.Controls.HttpMessage\Frank.Wpf.Controls.HttpMessage.csproj", "{4F4A407A-193B-4B10-A3EE-545D0F9F787E}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.Wpf.Controls.ExpandoControl", "Frank.Wpf.Controls.ExpandoControl\Frank.Wpf.Controls.ExpandoControl.csproj", "{48C83B4F-0ECE-475A-9F0C-E8DA5FF22877}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.Wpf.Controls.SearchableList", "Frank.Wpf.Controls.SearchableList\Frank.Wpf.Controls.SearchableList.csproj", "{AA842911-72AF-4343-8C87-47D2635FD367}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.Wpf.Controls.CompletionPopup", "Frank.Wpf.Controls.CompletionPopup\Frank.Wpf.Controls.CompletionPopup.csproj", "{33FB68C5-B9DF-443E-B9FB-9EF8FEF4A668}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -149,5 +151,9 @@ Global {AA842911-72AF-4343-8C87-47D2635FD367}.Debug|Any CPU.Build.0 = Debug|Any CPU {AA842911-72AF-4343-8C87-47D2635FD367}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA842911-72AF-4343-8C87-47D2635FD367}.Release|Any CPU.Build.0 = Release|Any CPU + {33FB68C5-B9DF-443E-B9FB-9EF8FEF4A668}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33FB68C5-B9DF-443E-B9FB-9EF8FEF4A668}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33FB68C5-B9DF-443E-B9FB-9EF8FEF4A668}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33FB68C5-B9DF-443E-B9FB-9EF8FEF4A668}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal