From 1d63b695d1e16dea2d29cafad60336dbc122918e Mon Sep 17 00:00:00 2001 From: kozhilya Date: Fri, 16 May 2025 18:29:10 +0400 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=B0=D1=87=D0=B0=D0=BB=D0=BE=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D1=85=D0=BE=D0=B4=D0=B0=20=D0=BD=D0=B0=20WPF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pf2eDataScrapper/App/ProcessorCollection.cs | 8 + Pf2eDataScrapper/ApplicationSettings.cs | 1 + Pf2eDataScrapper/EnvSettings.cs | 3 +- .../EntityTreeViewerControl.cs | 11 + .../EntityViewerControl.Designer.cs | 36 ++ .../EntityTreeViewer/EntityViewerControl.cs | 18 + .../EntityTreeViewer/EntityViewerControl.resx | 120 +++++ .../Forms/EntityViewForm.Designer.cs | 30 +- Pf2eDataScrapper/Forms/EntityViewForm.cs | 4 +- Pf2eDataScrapper/ImportApplication.cs | 113 ++--- Pf2eDataScrapper/Pf2eDataScrapper.csproj | 1 + .../Processors/ProcessorExecutor.cs | 16 +- Pf2eDataScrapper/Program.cs | 3 + .../AbstractSchemaBuildProcessor.cs | 3 +- .../WPF/Components/DbEntityListControl.xaml | 88 ++++ .../Components/DbEntityListControl.xaml.cs | 28 ++ .../ProcessorCollectionControl.xaml | 414 ++++++++++++++++++ .../ProcessorCollectionControl.xaml.cs | 160 +++++++ .../WPF/Stores/SelectedEntityStore.cs | 20 + .../WPF/Stores/SelectedEntityTypeStore.cs | 20 + Pf2eDataScrapper/WPF/ViewModelBase.cs | 23 + .../WPF/ViewModels/DbEntityListViewModel.cs | 155 +++++++ .../WPF/ViewModels/EntityFormViewModel.cs | 49 +++ Pf2eDataScrapper/WPF/Views/EntityForm.xaml | 69 +++ Pf2eDataScrapper/WPF/Views/EntityForm.xaml.cs | 29 ++ .../WPF/Views/ProgressFormWpf.xaml | 85 ++++ .../WPF/Views/ProgressFormWpf.xaml.cs | 78 ++++ Pf2eDataScrapper/WPF/Views/StartForm.xaml | 61 +++ Pf2eDataScrapper/WPF/Views/StartForm.xaml.cs | 35 ++ .../WPF/WpfServiceProviderExtension.cs | 21 + ...signer.cs => 20250515080518_1.Designer.cs} | 2 +- ...0250515054227_1.cs => 20250515080518_1.cs} | 0 32 files changed, 1621 insertions(+), 83 deletions(-) create mode 100644 Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.Designer.cs create mode 100644 Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.cs create mode 100644 Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.resx create mode 100644 Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml create mode 100644 Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml.cs create mode 100644 Pf2eDataScrapper/WPF/Components/ProcessorCollectionControl.xaml create mode 100644 Pf2eDataScrapper/WPF/Components/ProcessorCollectionControl.xaml.cs create mode 100644 Pf2eDataScrapper/WPF/Stores/SelectedEntityStore.cs create mode 100644 Pf2eDataScrapper/WPF/Stores/SelectedEntityTypeStore.cs create mode 100644 Pf2eDataScrapper/WPF/ViewModelBase.cs create mode 100644 Pf2eDataScrapper/WPF/ViewModels/DbEntityListViewModel.cs create mode 100644 Pf2eDataScrapper/WPF/ViewModels/EntityFormViewModel.cs create mode 100644 Pf2eDataScrapper/WPF/Views/EntityForm.xaml create mode 100644 Pf2eDataScrapper/WPF/Views/EntityForm.xaml.cs create mode 100644 Pf2eDataScrapper/WPF/Views/ProgressFormWpf.xaml create mode 100644 Pf2eDataScrapper/WPF/Views/ProgressFormWpf.xaml.cs create mode 100644 Pf2eDataScrapper/WPF/Views/StartForm.xaml create mode 100644 Pf2eDataScrapper/WPF/Views/StartForm.xaml.cs create mode 100644 Pf2eDataScrapper/WPF/WpfServiceProviderExtension.cs rename Pf2eModel/Migrations/{20250515054227_1.Designer.cs => 20250515080518_1.Designer.cs} (99%) rename Pf2eModel/Migrations/{20250515054227_1.cs => 20250515080518_1.cs} (100%) diff --git a/Pf2eDataScrapper/App/ProcessorCollection.cs b/Pf2eDataScrapper/App/ProcessorCollection.cs index 74d435c..56d21db 100644 --- a/Pf2eDataScrapper/App/ProcessorCollection.cs +++ b/Pf2eDataScrapper/App/ProcessorCollection.cs @@ -184,6 +184,14 @@ internal class ProcessorCollection Items = AllItems.Where(item => nameFlags.ContainsKey(item.Name)).ToList(); } + + /// + /// Получить список типов, которые обрабатываются в компендиуме. + /// + /// Тип компендиума. + /// Набор типов. + public ICollection GetTypesOfCompendium(ProcessorCompendiumEnum compendium) => + Items.Select(item => item.InnerType).OfType().ToList(); } /// diff --git a/Pf2eDataScrapper/ApplicationSettings.cs b/Pf2eDataScrapper/ApplicationSettings.cs index f5b4938..6a0320f 100644 --- a/Pf2eDataScrapper/ApplicationSettings.cs +++ b/Pf2eDataScrapper/ApplicationSettings.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.IO; using dotenv.net; using dotenv.net.Utilities; diff --git a/Pf2eDataScrapper/EnvSettings.cs b/Pf2eDataScrapper/EnvSettings.cs index f5b02c1..1777b12 100644 --- a/Pf2eDataScrapper/EnvSettings.cs +++ b/Pf2eDataScrapper/EnvSettings.cs @@ -1,4 +1,5 @@ -using dotenv.net; +using System.IO; +using dotenv.net; using dotenv.net.Utilities; namespace Pf2eDataScrapper; diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs index c286747..ce1d0bc 100644 --- a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs +++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs @@ -167,6 +167,7 @@ public sealed partial class EntityTreeViewerControl : UserControl public EntityTreeViewerControl() { InitializeComponent(); + ContentImage = new(ContentWidth, Height); DoubleBuffered = true; VerticalScroll.Visible = true; @@ -213,6 +214,11 @@ public sealed partial class EntityTreeViewerControl : UserControl /// public void UpdateRenders() { + if (EntityView?.Entity is null) + { + return; + } + int maxNameWidth = -1; foreach (EntityTreeViewerControlRow row in _viewerRows) @@ -239,6 +245,11 @@ public sealed partial class EntityTreeViewerControl : UserControl /// public void UpdateView() { + if (EntityView?.Entity is null) + { + return; + } + _viewerRows.ForEach(row => row.UpdateButton()); List rows = _viewerRows.Where(r => r.Item.Visible).ToList(); diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.Designer.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.Designer.cs new file mode 100644 index 0000000..6ac4ecf --- /dev/null +++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.Designer.cs @@ -0,0 +1,36 @@ +namespace Pf2eDataScrapper.Forms.Controls.EntityTreeViewer; + +partial class EntityViewerControl +{ + /// + /// Обязательная переменная конструктора. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Освободить все используемые ресурсы. + /// + /// истинно, если управляемый ресурс должен быть удален; иначе ложно. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Код, автоматически созданный конструктором компонентов + + /// + /// Требуемый метод для поддержки конструктора — не изменяйте + /// содержимое этого метода с помощью редактора кода. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + } + + #endregion +} diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.cs new file mode 100644 index 0000000..c9cab89 --- /dev/null +++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Pf2eDataScrapper.Forms.Controls.EntityTreeViewer; +public partial class EntityViewerControl : UserControl +{ + public EntityViewerControl() + { + InitializeComponent(); + } +} diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.resx b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs b/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs index 3a18dd8..66ae9be 100644 --- a/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs +++ b/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs @@ -37,7 +37,7 @@ partial class EntityViewForm ResultSplitContainer = new SplitContainer(); DescriptionTextBox = new TextBox(); DatabaseViewer = new DatabaseViewerControl(); - EntityTreeViewerControl = new EntityTreeViewerControl(); + _entityTreeViewerCntrl = new EntityTreeViewerControl(); MenuStrip = new MenuStrip(); FileToolStripMenuItem = new ToolStripMenuItem(); ExitToolStripMenuItem = new ToolStripMenuItem(); @@ -137,20 +137,20 @@ partial class EntityViewForm DatabaseViewer.Size = new Size(322, 731); DatabaseViewer.TabIndex = 1; // - // EntityTreeViewerControl + // _entityTreeViewerCntrl // - EntityTreeViewerControl.AutoScroll = true; - EntityTreeViewerControl.ButtonSize = 15; - EntityTreeViewerControl.DepthSize = 15; - EntityTreeViewerControl.Dock = DockStyle.Fill; - EntityTreeViewerControl.Font = new Font("Consolas", 9F); - EntityTreeViewerControl.HoverColor = SystemColors.ControlDark; - EntityTreeViewerControl.ItemHeight = 20; - EntityTreeViewerControl.Location = new Point(0, 0); - EntityTreeViewerControl.Name = "EntityTreeViewerControl"; - EntityTreeViewerControl.Size = new Size(582, 861); - EntityTreeViewerControl.TabIndex = 0; - EntityTreeViewerControl.TypeFontSize = 7F; + _entityTreeViewerCntrl.AutoScroll = true; + _entityTreeViewerCntrl.ButtonSize = 15; + _entityTreeViewerCntrl.DepthSize = 15; + _entityTreeViewerCntrl.Dock = DockStyle.Fill; + _entityTreeViewerCntrl.Font = new Font("Consolas", 9F); + _entityTreeViewerCntrl.HoverColor = SystemColors.ControlDark; + _entityTreeViewerCntrl.ItemHeight = 20; + _entityTreeViewerCntrl.Location = new Point(0, 0); + _entityTreeViewerCntrl.Name = "_entityTreeViewerCntrl"; + _entityTreeViewerCntrl.Size = new Size(582, 861); + _entityTreeViewerCntrl.TabIndex = 0; + _entityTreeViewerCntrl.TypeFontSize = 7F; // // MenuStrip // @@ -229,7 +229,7 @@ partial class EntityViewForm private ListBox TypesListBox; private SplitContainer ResultSplitContainer; private TextBox DescriptionTextBox; - private EntityTreeViewerControl EntityTreeViewerControl; + private EntityTreeViewerControl _entityTreeViewerCntrl; private DatabaseViewerControl DatabaseViewer; private MenuStrip MenuStrip; private ToolStripMenuItem FileToolStripMenuItem; diff --git a/Pf2eDataScrapper/Forms/EntityViewForm.cs b/Pf2eDataScrapper/Forms/EntityViewForm.cs index bed9bc3..ade43ff 100644 --- a/Pf2eDataScrapper/Forms/EntityViewForm.cs +++ b/Pf2eDataScrapper/Forms/EntityViewForm.cs @@ -18,10 +18,10 @@ internal partial class EntityViewForm : Form [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public EntityView? CurrentView { - get => EntityTreeViewerControl.EntityView; + get => _entityTreeViewerCntrl.EntityView; set { - EntityTreeViewerControl.EntityView = value; + _entityTreeViewerCntrl.EntityView = value; Application.DoEvents(); } } diff --git a/Pf2eDataScrapper/ImportApplication.cs b/Pf2eDataScrapper/ImportApplication.cs index ff3510f..3111401 100644 --- a/Pf2eDataScrapper/ImportApplication.cs +++ b/Pf2eDataScrapper/ImportApplication.cs @@ -1,10 +1,14 @@ -using Microsoft.Extensions.DependencyInjection; +using System.ComponentModel; +using System.Windows; +using Microsoft.Extensions.DependencyInjection; using Pf2eDataScrapper.App; -using Pf2eDataScrapper.Forms; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Processors; +using Pf2eDataScrapper.WPF; +using Pf2eDataScrapper.WPF.Views; using Pf2eModel; using Slugify; +using StartForm = Pf2eDataScrapper.WPF.Views.StartForm; namespace Pf2eDataScrapper; @@ -20,7 +24,8 @@ internal sealed class ImportApplication : ApplicationContext Settings = settings; Processors = processors; - CurrentStage = ApplicationStage.InitialSettings; + //CurrentStage = ApplicationStage.InitialSettings; + CurrentStage = ApplicationStage.Viewer; } #region "Поля" @@ -46,30 +51,20 @@ internal sealed class ImportApplication : ApplicationContext #region "Формы" - private StartForm? _startForm; + private StartForm? _startFormWpf; - private ProgressForm? _progressForm; + private ProgressFormWpf? _progressFormWpf; - private EntityViewForm? _entityViewForm; + private EntityForm? _entityForm; #endregion #region "Жизненный цикл" - private Form? CurrentForm { get; set; } + private Window? CurrentWpfForm { get; set; } public delegate void ApplicationStateChangeDelegate(object sender, ApplicationStage nextStage); - public void ApplicationStateChange(object sender, ApplicationStage nextStage) - { - if (sender != CurrentForm) - { - return; - } - - CurrentStage = nextStage; - } - private void StartStage(ApplicationStage stage) { switch (stage) @@ -80,13 +75,13 @@ internal sealed class ImportApplication : ApplicationContext ); case ApplicationStage.InitialSettings: - _startForm = ActivatorUtilities.CreateInstance(ServiceProvider); - _startForm.StartPosition = FormStartPosition.CenterParent; - _startForm.ApplicationStateChange += ApplicationStateChange; - _startForm.FormClosing += HandleFormClosing; - _startForm.Show(); + _startFormWpf = ActivatorUtilities.CreateInstance(ServiceProvider); + _startFormWpf.WindowStartupLocation = WindowStartupLocation.CenterOwner; + _startFormWpf.Closing += Wpf_HandleFormClosing; + _startFormWpf.ApplicationStateChange += ApplicationStateChange; + _startFormWpf.Show(); - CurrentForm = _startForm; + CurrentWpfForm = _startFormWpf; break; case ApplicationStage.ImportProcess: @@ -94,38 +89,37 @@ internal sealed class ImportApplication : ApplicationContext ServiceProvider ); - _progressForm = ActivatorUtilities.CreateInstance(ServiceProvider); - _progressForm.StartPosition = FormStartPosition.CenterParent; - _progressForm.FormClosing += HandleFormClosing; + _progressFormWpf = ActivatorUtilities.CreateInstance( + ServiceProvider + ); + _progressFormWpf.WindowStartupLocation = WindowStartupLocation.CenterOwner; + _progressFormWpf.Closing += Wpf_HandleFormClosing; + _progressFormWpf.ApplicationStateChange += ApplicationStateChange; - _progressForm.Show(); - - CurrentForm = _progressForm; + CurrentWpfForm = _progressFormWpf; + _progressFormWpf.Show(); executor.Start(); break; case ApplicationStage.Viewer: - _entityViewForm = ActivatorUtilities.CreateInstance( - ServiceProvider - ); - _entityViewForm.StartPosition = FormStartPosition.CenterParent; - _entityViewForm.ApplicationStateChange += ApplicationStateChange; - _entityViewForm.FormClosing += HandleFormClosing; + _entityForm = ActivatorUtilities.CreateInstance(ServiceProvider); + _entityForm.WindowStartupLocation = WindowStartupLocation.CenterOwner; + _entityForm.Closing += Wpf_HandleFormClosing; + //_entityForm.ApplicationStateChange += ApplicationStateChange; + _entityForm.Show(); - _entityViewForm.Show(); - CurrentForm = _entityViewForm; + CurrentWpfForm = _entityForm; break; default: throw new ArgumentOutOfRangeException(); } - - Application.DoEvents(); } private void CloseStage(ApplicationStage stage) { - CurrentForm = null; + CurrentWpfForm = null; + switch (stage) { case ApplicationStage.BootUp: @@ -133,38 +127,48 @@ internal sealed class ImportApplication : ApplicationContext break; case ApplicationStage.InitialSettings: - _startForm!.Hide(); - _startForm!.Close(); - _startForm = null; + _startFormWpf!.Hide(); + _startFormWpf!.Close(); + _startFormWpf = null; break; case ApplicationStage.ImportProcess: break; case ApplicationStage.Viewer: - _entityViewForm!.Hide(); - _entityViewForm!.Close(); - _entityViewForm = null; + _entityForm!.Hide(); + _entityForm!.Close(); + _entityForm = null; break; default: throw new ArgumentOutOfRangeException(); } - - Application.DoEvents(); } - private void HandleFormClosing(object? sender, FormClosingEventArgs e) + #endregion + + #region "События" + + public void ApplicationStateChange(object sender, ApplicationStage nextStage) { - if (sender != CurrentForm) + if (sender?.Equals(CurrentWpfForm) ?? false) + { + CurrentStage = nextStage; + } + } + + private void Wpf_HandleFormClosing(object? sender, CancelEventArgs e) + { + if (sender?.Equals(CurrentWpfForm) ?? false) + { + ExitThread(); + } + else { e.Cancel = true; - return; } - - ExitThread(); } - #endregion #region "Внедрение зависимостей" @@ -177,6 +181,7 @@ internal sealed class ImportApplication : ApplicationContext { // Контекст базы данных services.AddEntityFrameworkNpgsql(); + services.AddDbContext(); //services.AddDbContextPool(options => //{ diff --git a/Pf2eDataScrapper/Pf2eDataScrapper.csproj b/Pf2eDataScrapper/Pf2eDataScrapper.csproj index a299921..ef7689d 100644 --- a/Pf2eDataScrapper/Pf2eDataScrapper.csproj +++ b/Pf2eDataScrapper/Pf2eDataScrapper.csproj @@ -5,6 +5,7 @@ enable true enable + True diff --git a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs index 8ea6ec1..560b179 100644 --- a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs +++ b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs @@ -5,7 +5,7 @@ using Pf2eDataScrapper.App; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; -using Pf2eDataScrapper.Importers.Implementations.FoundryEn; +using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium; namespace Pf2eDataScrapper.Processors; @@ -116,15 +116,13 @@ internal sealed class ProcessorExecutor( _ => throw new($"Не могу выбрать импортёр для группы {item.Group}"), }; - Progress.Local.Reset( - item.InnerType is null ? compendium.Count() : compendium.Count(item.InnerType) - ); + ICollection types = item.InnerType is null + ? Collection.GetTypesOfCompendium(item.Compendium ?? throw new("impossible")) + : [item.InnerType]; - foreach ( - (AbstractCompendiumItem compendiumItem, JObject json) in compendium.Iterate( - item.InnerType - ) - ) + Progress.Local.Reset(compendium.Count(types)); + + foreach ((AbstractCompendiumItem compendiumItem, JObject json) in compendium.Iterate(types)) { Progress.Local.Start(compendiumItem.Id); diff --git a/Pf2eDataScrapper/Program.cs b/Pf2eDataScrapper/Program.cs index f86694d..71d8b6c 100644 --- a/Pf2eDataScrapper/Program.cs +++ b/Pf2eDataScrapper/Program.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Pf2eDataScrapper.Importers.Implementations.Aonprd; using Pf2eDataScrapper.Importers.Implementations.FoundryEn; +using Pf2eDataScrapper.WPF; namespace Pf2eDataScrapper; @@ -37,6 +38,8 @@ internal static class Program { IServiceCollection services = new ServiceCollection(); + services.RegisterWpfServices(); + ImportApplication.RegisterServices(services); AonprdImporter.RegisterServices(services); diff --git a/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs b/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs index bce0c7b..6212448 100644 --- a/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs +++ b/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using System.IO; +using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Helpers; using Pf2eDataScrapper.Importers; using Pf2eDataScrapper.Processors; diff --git a/Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml b/Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml new file mode 100644 index 0000000..e595937 --- /dev/null +++ b/Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + +