├── .github
└── workflows
│ └── build_and_publish.yml
├── .gitignore
├── LICENSE.md
├── README.md
├── resources
└── images
│ ├── BasicSample.gif
│ ├── ColoredBandHeaderSample.png
│ ├── FlexGridMergedHeader.png
│ ├── FlexGridTemplateStructure.png
│ ├── FrozenHeaderSample.gif
│ ├── MergedHeaderSample.gif
│ ├── VirtualBandSample.gif
│ └── VirtualBandSample_ItemsSource_Image.png
└── src
├── KevinComponent.Demo.App
├── App.xaml
├── App.xaml.cs
├── AssemblyInfo.cs
├── BindingProxy.cs
├── KevinComponent.Demo.App.csproj
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Models
│ ├── Person.cs
│ ├── Sample.cs
│ ├── Score.cs
│ └── Subject.cs
└── Views
│ ├── BasicSampleView.xaml
│ ├── BasicSampleView.xaml.cs
│ ├── ColoredBandHeaderSampleView.xaml
│ ├── ColoredBandHeaderSampleView.xaml.cs
│ ├── FrozenHeaderView.xaml
│ ├── FrozenHeaderView.xaml.cs
│ ├── MergedHeaderSampleView.xaml
│ ├── MergedHeaderSampleView.xaml.cs
│ ├── VirtualBandSampleView.xaml
│ └── VirtualBandSampleView.xaml.cs
├── KevinComponent.sln
└── KevinComponent
├── AssemblyInfo.cs
├── Assist
├── FlexGridAssist.cs
└── TextBoxAssist.cs
├── Band.cs
├── BandCollection.cs
├── BandHeader.cs
├── BandHeaderGripper.cs
├── BandHeadersPresenter.cs
├── CheckBoxBand.cs
├── ComboBoxBand.cs
├── FlexGrid.cs
├── FlexGridColumn.cs
├── FlexGridCommittedArgs.cs
├── IVirtualBandBindable.cs
├── KevinComponent.csproj
├── TemplateBand.cs
├── TextBand.cs
├── Themes
├── Generic.xaml
├── Theme.Colors.xaml
└── Theme.FlexGrid.xaml
├── Utils.cs
├── VirtualBand.cs
├── VirtualBandBinding.cs
├── VirtualBandMultiBinding.cs
├── VirtualBandPriorityBinding.cs
├── VirtualCheckBoxBand.cs
├── VirtualComboBoxBand.cs
├── VirtualTemplateBand.cs
└── VirtualTextBand.cs
/.github/workflows/build_and_publish.yml:
--------------------------------------------------------------------------------
1 | name : Build And Publish to Nuget
2 | on:
3 | push:
4 | branches: [ master ]
5 | # workflow_dispatch:
6 | # inputs:
7 | # tags:
8 | # description: 'Description'
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | env:
14 | SOLUTION_FILE: src/KevinComponent.sln
15 | NUGET_FILE: src/KevinComponent/bin/GitHub Release/*.nupkg
16 |
17 | steps:
18 | - name: Load Repository Files
19 | uses: actions/checkout@v3
20 |
21 | - name: Setup .NET
22 | uses: actions/setup-dotnet@v3
23 |
24 | - name: Build Solution
25 | run: dotnet build $SOLUTION_FILE -c "GitHub Release"
26 |
27 | - name: Publish to nuget.org
28 | run: dotnet nuget push "$NUGET_FILE" --api-key ${{secrets.NUGET_API_KEY}} --source "https://api.nuget.org/v3/index.json" --skip-duplicate
29 |
30 | - name: Publish to GitHub Repository
31 | run: |
32 | dotnet nuget add source --username "soomin-kevin-sung" --password ${{secrets.ACCESS_TOKEN}} --store-password-in-clear-text --name "github" "https://nuget.pkg.github.com/soomin-kevin-sung/index.json"
33 | dotnet nuget push "$NUGET_FILE" --api-key ${{secrets.ACCESS_TOKEN}} --source "github" --skip-duplicate
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | src/.vs/
2 | src/KevinComponent/Build/
3 | src/KevinComponent/bin/
4 | src/KevinComponent/obj/
5 | src/KevinComponent.Demo.App/Build/
6 | src/KevinComponent.Demo.App/bin/
7 | src/KevinComponent.Demo.App/obj/
8 | *.csproj.user
9 |
10 | # Ignore NuGet Packages
11 | *.nupkg
12 |
13 | # The packages folder can be ignored because of Package Restore
14 | **/[Pp]ackages/*
15 |
16 | # except build/, which is used as an MSBuild target.
17 | !**/[Pp]ackages/build/
18 |
19 | # Uncomment if necessary however generally it will be regenerated when needed
20 | #!**/[Pp]ackages/repositories.config
21 |
22 | # NuGet v3's project.json files produces more ignorable files
23 | *.nuget.props
24 | *.nuget.targets
25 |
26 | # Ignore other intermediate files that NuGet might create. project.lock.json is used in conjunction
27 | # with project.json (NuGet v3); project.assets.json is used in conjunction with the PackageReference
28 | # format (NuGet v4 and .NET Core).
29 | project.lock.json
30 | project.assets.json
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 soomin-kevin-sung
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FlexGrid
2 |
3 | [](https://github.com/soomin-kevin-sung/dotnet-flexgrid/actions/workflows/build_and_publish.yml)
4 | 
5 |
6 | 
7 | 
8 | 
9 |
10 | [](https://www.codefactor.io/repository/github/soomin-kevin-sung/dotnet-flexgrid/overview/master)
11 | [](LICENSE.md)
12 | 
13 | 
14 |
15 | FlexGrid is a custom WPF DataGrid with convenient and useful features. When developing code using WPF, the Microsoft-supported DataGrid has limited functionality, such as nested and merged column headers and variable columns. However, with FlexGrid, your DataGrid development environment becomes significantly more convenient!
16 |
17 |
18 |
19 | I'm proud to say that FlexGrid was fully built by me, and I'm excited to share it on GitHub :)
20 |
21 |
22 |
23 | ## Goal
24 |
25 | - Create Customized DataGrid with convenient and useful features.
26 | - Use FlexGrid to develop other WPF Programs
27 |
28 |
29 |
30 | ## Available Features
31 |
32 | - [FlexGrid Template Structure](#flexgrid-template-structure)
33 | - [Using Bands Instead Of Columns](#using-bands-instead-of-columns)
34 | - [Bands and Frozen Bands](#bands-and-frozen-bands)
35 | - [Mergable Column Header (Band.Bands)](#mergable-column-header-bandbands)
36 | - [Variable Columns (VirtualBand)](#variable-columns-virtualband--virtualbandbinding)
37 |
38 |
39 |
40 | ## FlexGrid Template Structure
41 |
42 | FlexGrid is a Component that modified the Template of the default DataGrid.
43 |
44 |
45 |
46 |
47 | <FlexGrid Template Structure>
48 |
49 |
50 | ## Using Bands Instead Of Columns
51 |
52 | FlexGrid uses BandHeadersPresenters to represent the columns. The BandHeaderPresenter represents the Bands in FlexGrid.FrozenBands and FlexGrid.Bands as Columns in the FlexGrid.
53 |
54 | ### Band Types
55 |
56 | - TextBand
57 | - CheckBoxBand
58 | - ComboBoxBand
59 | - TemplateBand
60 | - VirtualBand
61 | - VirtualTextBand
62 | - VirtualCheckBoxBand
63 | - VirtualComboBoxBand
64 | - VirtualTemplateBand
65 |
66 | This is Example how to use Bands.
67 |
68 | ```xml
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | ```
106 |
107 |
108 |
109 | ## Bands and Frozen Bands
110 |
111 | For represent Frozen Columns (Always showed Columns) in FlexGrid. You should use FlexGrid.FrozenBands.
112 | The FlexGrid shows to Bands in FlexGrid.FrozenBands as Frozen Columns.
113 |
114 | This is Example Code how to use Frozen Bands.
115 |
116 | ```xml
117 |
118 |
119 |
120 |
121 |
122 |
123 |
128 |
129 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
144 |
145 |
146 |
147 | ```
148 |
149 |
150 |
151 | ## Mergable Column Header (Band.Bands)
152 |
153 | The **`Bands`** Property in Band can be used to represent Merged Column Headers.
154 |
155 |
156 |
157 |
158 | <Merged Column Headers>
159 |
160 |
161 | Related StackOverflow questions:
162 | * [Multilevel column header for datagrid in wpf](https://stackoverflow.com/questions/17652039/multilevel-column-header-for-datagrid-in-wpf)
163 | * [Wpf datagrid header above header](https://stackoverflow.com/questions/51440426/wpf-datagrid-header-above-header)
164 | * [Merge header columns datagrid wpf](https://stackoverflow.com/questions/6446684/merge-header-columns-datagrid-wpf)
165 |
166 |
167 | This is Example Code how to use Band.Bands Object.
168 |
169 | ```xml
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
186 |
187 |
188 |
193 |
194 |
195 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 | ```
225 |
226 |
227 |
228 | ## Variable Columns (VirtualBand & VirtualBandBinding)
229 |
230 | FlexGrid can represent varaible columns by **`VirtualBand`** class.
231 |
232 |
233 | List of All kind of **`VirtualBand`**.
234 | - **`VirtualTextBand`**
235 | - **`VirtualCheckBoxBand`**
236 | - **`VirtualComboBoxBand`**
237 | - **`VirtualTemplateBand`**
238 |
239 |
240 | Related StackOverflow questions:
241 | * [How do i bind a wpf datagrid to a variable number of columns](https://stackoverflow.com/questions/320089/how-do-i-bind-a-wpf-datagrid-to-a-variable-number-of-columns)
242 | * [How do i dynamically generate columns in a wpf datagrid](https://stackoverflow.com/questions/1983033/how-do-i-dynamically-generate-columns-in-a-wpf-datagrid)
243 |
244 | Related CodeProject Articles:
245 | * [Dynamic Columns in a WPF DataGrid Control](https://www.codeproject.com/Articles/891995/Dynamic-Columns-in-a-WPF-DataGrid-Control-Part-2)
246 |
247 |
248 |
249 | This is Example Code how to use VirtualBands.
250 |
251 | ```xml
252 |
253 |
254 |
255 |
256 |
261 |
262 |
267 |
268 |
272 |
273 |
274 |
275 |
276 |
277 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 | ```
306 |
307 |
308 |
309 | The **`VirtualBandBinding`** is the class to binding property to generated bands by **`VirtualBand`**. FlexGrid converts the Items in the VirtualBand to Columns, while mapping the VirtualBandBinding to each property appropriately so that the data appears in the Cell.
310 |
311 | Refer to the code below.
312 | https://github.com/soomin-kevin-sung/dotnet-flexgrid/blob/c4e05c1f2c0517c263fdca6026c2e74f2cda1fe9/src/KevinComponent/Band.cs#L469-L496
313 |
314 |
315 |
316 | ## Samples
317 |
318 | ### Basic Sample
319 |
320 |
321 |
322 |
323 | <BasicSample ScreenShot>
324 |
325 |
326 | - **`BasicSample`** shows the basic usage of FlexGrid.
327 | - You can know how to use **`FlexGrid`** the basically in this sample.
328 |
329 |
330 |
331 | ### Frozen Header Sample
332 |
333 |
334 |
335 |
336 | <FrozenHedaerSample ScreenShot>
337 |
338 |
339 | - FrozenHedaerSample shows how to using the frozen columns.
340 | - You can use **`FlexGrid.FrozenBands`** to add frozen columns.
341 | - In this sample, the Name, BirthDate bands are Frozen Bands.
342 |
343 |
344 |
345 | ### Merged Header Sample
346 |
347 |
348 |
349 |
350 | <MergedHedaerSample ScreenShot>
351 |
352 |
353 | - MergedHedaerSample shows how to merge column headers.
354 | - You can use **`Band.Bands`**(ex. TextBand.Bands, CheckBoxBand.Bands, etc.) to merge column headers.
355 | - In this sample, you can see the Name, BirthDate, Address, and WebSite bands merged into the information band.
356 |
357 |
358 |
359 | ### VirtualBand Sample
360 |
361 |
362 |
363 |
364 | <VirtualBandSample ScreenShot>
365 |
366 |
367 | - VirtualBandSample shows how variable columns are implemented in FlexGrid.
368 | - You can use **`VirtualBand`**(ex. VirtualTextBand, VirtualComboBoxBand, VirtualCheckBoxBand, etc.) to show variable columns.
369 | - In this Sample, you can see that the list of subject scores synchronizes with the subject list when you edit the subject list.
370 |
371 |
372 |
373 |
374 | <ItemsSource Description>
375 |
376 |
377 |
378 |
379 | ### Colored BandHeader Sample
380 |
381 |
382 |
383 |
384 | <ColoredBandHeaderSample ScreenShot>
385 |
386 |
387 | - ColoredBandHeaderSample shows how to set header and cell style.
388 | - You can use **`Band.HeaderStyle`** and **`Band.CellStyle`** to set header style and cell style.
389 |
390 |
391 |
392 | ## Support
393 |
394 | If you have any good ideas (such as new featrue, refactoring, improvement feature quality, etc), do not hesitate to let me know!
395 | You also can "Pull request" or request adding New Feature to the email below.
396 | Thank you.
397 |
398 | - E-mail : ssm0725@gmail.com
399 |
--------------------------------------------------------------------------------
/resources/images/BasicSample.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/soomin-kevin-sung/dotnet-flexgrid/9ae4e3da92df84145aa3ed73009820608a3a1d93/resources/images/BasicSample.gif
--------------------------------------------------------------------------------
/resources/images/ColoredBandHeaderSample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/soomin-kevin-sung/dotnet-flexgrid/9ae4e3da92df84145aa3ed73009820608a3a1d93/resources/images/ColoredBandHeaderSample.png
--------------------------------------------------------------------------------
/resources/images/FlexGridMergedHeader.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/soomin-kevin-sung/dotnet-flexgrid/9ae4e3da92df84145aa3ed73009820608a3a1d93/resources/images/FlexGridMergedHeader.png
--------------------------------------------------------------------------------
/resources/images/FlexGridTemplateStructure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/soomin-kevin-sung/dotnet-flexgrid/9ae4e3da92df84145aa3ed73009820608a3a1d93/resources/images/FlexGridTemplateStructure.png
--------------------------------------------------------------------------------
/resources/images/FrozenHeaderSample.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/soomin-kevin-sung/dotnet-flexgrid/9ae4e3da92df84145aa3ed73009820608a3a1d93/resources/images/FrozenHeaderSample.gif
--------------------------------------------------------------------------------
/resources/images/MergedHeaderSample.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/soomin-kevin-sung/dotnet-flexgrid/9ae4e3da92df84145aa3ed73009820608a3a1d93/resources/images/MergedHeaderSample.gif
--------------------------------------------------------------------------------
/resources/images/VirtualBandSample.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/soomin-kevin-sung/dotnet-flexgrid/9ae4e3da92df84145aa3ed73009820608a3a1d93/resources/images/VirtualBandSample.gif
--------------------------------------------------------------------------------
/resources/images/VirtualBandSample_ItemsSource_Image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/soomin-kevin-sung/dotnet-flexgrid/9ae4e3da92df84145aa3ed73009820608a3a1d93/resources/images/VirtualBandSample_ItemsSource_Image.png
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/App.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 |
9 | namespace KevinComponent.Demo.App
10 | {
11 | ///
12 | /// Interaction logic for App.xaml
13 | ///
14 | public partial class App : Application
15 | {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | [assembly: ThemeInfo(
4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5 | //(used if a resource is not found in the page,
6 | // or application resource dictionaries)
7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8 | //(used if a resource is not found in the page,
9 | // app, or any theme specific resource dictionaries)
10 | )]
11 |
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/BindingProxy.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 |
8 | namespace KevinComponent.Demo.App
9 | {
10 | public class BindingProxy : Freezable
11 | {
12 | #region Dependency Properties
13 |
14 | public static readonly DependencyProperty DataProperty =
15 | DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy));
16 |
17 | #endregion
18 |
19 | #region Public Properties
20 |
21 | public object Data
22 | {
23 | get => GetValue(DataProperty);
24 | set => SetValue(DataProperty, value);
25 | }
26 |
27 | #endregion
28 |
29 | #region Protected Override Methods
30 |
31 | protected override Freezable CreateInstanceCore()
32 | {
33 | return new BindingProxy();
34 | }
35 |
36 | #endregion
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/KevinComponent.Demo.App.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net6.0-windows
6 | enable
7 | true
8 | Build
9 | Debug;Release;GitHub Release
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
18 |
22 |
23 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using KevinComponent.Demo.App.Models;
2 | using KevinComponent.Demo.App.Views;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Collections.ObjectModel;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using System.Windows;
10 | using System.Windows.Controls;
11 | using System.Windows.Data;
12 | using System.Windows.Documents;
13 | using System.Windows.Input;
14 | using System.Windows.Media;
15 | using System.Windows.Media.Imaging;
16 | using System.Windows.Navigation;
17 | using System.Windows.Shapes;
18 |
19 | namespace KevinComponent.Demo.App
20 | {
21 | ///
22 | /// Interaction logic for MainWindow.xaml
23 | ///
24 | public partial class MainWindow : Window
25 | {
26 | public MainWindow()
27 | {
28 | InitializeComponent();
29 |
30 | Samples = new ObservableCollection
31 | {
32 | new Sample("Basic Sample", () => new BasicSampleView().Show()),
33 | new Sample("Frozen Header Sample", () => new FrozenHeaderView().Show()),
34 | new Sample("Merged Header Sample", () => new MergedHeaderSampleView().Show()),
35 | new Sample("VirtualBand Sample", () => new VirtualBandSampleView().Show()),
36 | new Sample("ColoredBandHeader Sample", () => new ColoredBandHeaderSampleView().Show())
37 | };
38 |
39 | flexGrid.ItemsSource = Samples;
40 | }
41 |
42 | #region Public Properties
43 |
44 | public ObservableCollection Samples { get; }
45 |
46 | #endregion
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/Models/Person.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Toolkit.Mvvm.ComponentModel;
2 | using Microsoft.VisualBasic;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Collections.ObjectModel;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace KevinComponent.Demo.App.Models
11 | {
12 | public class Person : ObservableObject
13 | {
14 | public Person(string name)
15 | {
16 | _name = name;
17 | _scoreBySubject = new Dictionary();
18 | }
19 |
20 | public Person(string name, DateTime birthDate, string address, string? webSite = null)
21 | {
22 | _name = name;
23 | _birthDate = birthDate;
24 | _address = address;
25 | _webSite = webSite;
26 | _scoreBySubject = new Dictionary();
27 | }
28 |
29 | #region Pirvate Static Variables
30 |
31 | static ObservableCollection? _sampleData;
32 |
33 | #endregion
34 |
35 | #region Public Static Properties
36 |
37 | public static ObservableCollection SampleData
38 | {
39 | get
40 | {
41 | if (_sampleData == null)
42 | {
43 | _sampleData = new ObservableCollection();
44 | _sampleData.Add(new Person("Soomin", new DateTime(1997, 7, 25), "Korea Seoul", "https://github.com/soomin-kevin-sung"));
45 | _sampleData.Add(new Person("Foo", new DateTime(2000, 1, 12), "Korea Busan"));
46 | _sampleData.Add(new Person("Bar", new DateTime(1948, 7, 15), "Korea Daejeon"));
47 | _sampleData.Add(new Person("BatMan", new DateTime(1950, 1, 8), "Cave", "https://www.naver.com"));
48 | _sampleData.Add(new Person("IronMan", new DateTime(1970, 2, 16), "Stark Tower", "https://google.com"));
49 | _sampleData.Add(new Person("Amy", new DateTime(1987, 3, 19), "Toriktu 38"));
50 | _sampleData.Add(new Person("Ben", new DateTime(1999, 4, 25), "26, boulevard", "https://www.youtube.com/"));
51 | _sampleData.Add(new Person("Cake", new DateTime(1997, 4, 10), "China Town"));
52 | _sampleData.Add(new Person("David", new DateTime(1968, 2, 21), "The Room", "https://www.linkedin.com/"));
53 | _sampleData.Add(new Person("Echo", new DateTime(1951, 1, 25), "Dust", "https://github.com/soomin-kevin-sung"));
54 | _sampleData.Add(new Person("Fox", new DateTime(2001, 7, 25), "Estate"));
55 | _sampleData.Add(new Person("Golf", new DateTime(1993, 3, 25), "Italy"));
56 | _sampleData.Add(new Person("Hotel", new DateTime(1983, 10, 29), "Nuke", "https://games.crossfit.com/"));
57 | _sampleData.Add(new Person("India", new DateTime(1975, 9, 25), "Train"));
58 | _sampleData.Add(new Person("Juliet", new DateTime(1991, 12, 29), "Mirage", "https://cafe.naver.com/cfable2ah#"));
59 | _sampleData.Add(new Person("Kilo", new DateTime(1990, 7, 29), "Inferno", "https://github.com/soomin-kevin-sung/dotnet-flexgrid"));
60 | _sampleData.Add(new Person("Lima", new DateTime(2006, 11, 29), "Overpass"));
61 | }
62 |
63 | return _sampleData;
64 | }
65 | }
66 |
67 | #endregion
68 |
69 | #region Private Variables
70 |
71 | string _name;
72 | DateTime? _birthDate;
73 | string? _address;
74 | string? _webSite;
75 | Dictionary _scoreBySubject;
76 |
77 | #endregion
78 |
79 | #region Public Properties
80 |
81 | public string Name
82 | {
83 | get => _name;
84 | set => SetProperty(ref _name, value);
85 | }
86 |
87 | public DateTime? BirthDate
88 | {
89 | get => _birthDate;
90 | set => SetProperty(ref _birthDate, value);
91 | }
92 |
93 | public string? Address
94 | {
95 | get => _address;
96 | set => SetProperty(ref _address, value);
97 | }
98 |
99 | public string? WebSite
100 | {
101 | get => _webSite;
102 | set => SetProperty(ref _webSite, value);
103 | }
104 |
105 | public Score this[Subject subject]
106 | {
107 | get
108 | {
109 | if (!_scoreBySubject.ContainsKey(subject))
110 | _scoreBySubject.Add(subject, new Score(0));
111 |
112 | return _scoreBySubject[subject];
113 | }
114 | }
115 |
116 | #endregion
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/Models/Sample.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Toolkit.Mvvm.ComponentModel;
2 | using Microsoft.Toolkit.Mvvm.Input;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Data;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using System.Windows.Input;
10 |
11 | namespace KevinComponent.Demo.App.Models
12 | {
13 | public class Sample : ObservableObject
14 | {
15 | public Sample(string description, Action action)
16 | {
17 | _description = description;
18 | _action = action;
19 | }
20 |
21 | #region Private Variables
22 |
23 | Action _action;
24 | string _description;
25 | ICommand? _runCommand;
26 |
27 | #endregion
28 |
29 | #region Public Properties
30 |
31 | public string Description
32 | {
33 | get => _description;
34 | set => SetProperty(ref _description, value);
35 | }
36 |
37 | public ICommand RunCommand => _runCommand ??= new RelayCommand(Run);
38 |
39 | #endregion
40 |
41 | #region Public Methods
42 |
43 | public void Run()
44 | {
45 | _action?.Invoke();
46 | }
47 |
48 | #endregion
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/Models/Score.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Toolkit.Mvvm.ComponentModel;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace KevinComponent.Demo.App.Models
9 | {
10 | public class Score : ObservableObject
11 | {
12 | public Score(double value)
13 | {
14 | Value = value;
15 | }
16 |
17 | #region Private Variables
18 |
19 | double _value;
20 |
21 | #endregion
22 |
23 | #region Public Properties
24 |
25 | public double Value
26 | {
27 | get => _value;
28 | set
29 | {
30 | if (SetProperty(ref _value, value))
31 | OnPropertyChanged(nameof(Grade));
32 | }
33 | }
34 |
35 | public string Grade
36 | {
37 | get
38 | {
39 | if (_value < 60)
40 | return "F";
41 | else if (_value < 65)
42 | return "D";
43 | else if (_value < 70)
44 | return "D+";
45 | else if (_value < 75)
46 | return "C";
47 | else if (_value < 80)
48 | return "C+";
49 | else if (_value < 85)
50 | return "B";
51 | else if (_value < 90)
52 | return "B+";
53 | else if (_value < 95)
54 | return "A";
55 | else
56 | return "A+";
57 | }
58 | }
59 |
60 | #endregion
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/Models/Subject.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Toolkit.Mvvm.ComponentModel;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Collections.ObjectModel;
5 | using System.Configuration;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace KevinComponent.Demo.App.Models
11 | {
12 | public class Subject : ObservableObject
13 | {
14 | public Subject(string name)
15 | {
16 | _name = name;
17 | }
18 |
19 | #region Pirvate Static Variables
20 |
21 | static ObservableCollection? _sampleData;
22 |
23 | #endregion
24 |
25 | #region Public Static Properties
26 |
27 | public static ObservableCollection SampleData
28 | {
29 | get
30 | {
31 | if (_sampleData == null)
32 | {
33 | _sampleData = new ObservableCollection();
34 | _sampleData.Add(new Subject("Math"));
35 | _sampleData.Add(new Subject("English"));
36 | _sampleData.Add(new Subject("Art"));
37 | _sampleData.Add(new Subject("Science"));
38 | _sampleData.Add(new Subject("History"));
39 | _sampleData.Add(new Subject("Music"));
40 | }
41 |
42 | return _sampleData;
43 | }
44 | }
45 |
46 | #endregion
47 |
48 | #region Private Variables
49 |
50 | string _name;
51 |
52 | #endregion
53 |
54 | #region Public Properties
55 |
56 | public string Name
57 | {
58 | get => _name;
59 | set => SetProperty(ref _name, value);
60 | }
61 |
62 | #endregion
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/Views/BasicSampleView.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
18 |
19 |
24 |
25 |
30 |
31 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/Views/BasicSampleView.xaml.cs:
--------------------------------------------------------------------------------
1 | using KevinComponent.Demo.App.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Collections.ObjectModel;
5 | using System.Diagnostics;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using System.Windows;
10 | using System.Windows.Automation;
11 | using System.Windows.Controls;
12 | using System.Windows.Data;
13 | using System.Windows.Documents;
14 | using System.Windows.Input;
15 | using System.Windows.Media;
16 | using System.Windows.Media.Imaging;
17 | using System.Windows.Navigation;
18 | using System.Windows.Shapes;
19 |
20 | namespace KevinComponent.Demo.App.Views
21 | {
22 | ///
23 | /// BasicSample.xaml에 대한 상호 작용 논리
24 | ///
25 | public partial class BasicSampleView : Window
26 | {
27 | public BasicSampleView()
28 | {
29 | InitializeComponent();
30 | flexGrid.ItemsSource = Person.SampleData;
31 | }
32 |
33 | private void OnHyperlinkRequestNavigate(object sender, RequestNavigateEventArgs e)
34 | {
35 | var info = new ProcessStartInfo(e.Uri.AbsoluteUri) { UseShellExecute = true };
36 | Process.Start(info);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/Views/ColoredBandHeaderSampleView.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
18 |
19 |
20 |
21 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
38 |
39 |
40 |
41 |
46 |
47 |
50 |
51 |
52 |
53 |
54 |
58 |
59 |
63 |
64 |
65 |
66 |
75 |
76 |
77 |
78 |
79 |
80 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/Views/ColoredBandHeaderSampleView.xaml.cs:
--------------------------------------------------------------------------------
1 | using KevinComponent.Demo.App.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using System.Windows.Data;
11 | using System.Windows.Documents;
12 | using System.Windows.Input;
13 | using System.Windows.Media;
14 | using System.Windows.Media.Imaging;
15 | using System.Windows.Navigation;
16 | using System.Windows.Shapes;
17 |
18 | namespace KevinComponent.Demo.App.Views
19 | {
20 | ///
21 | /// ColoredBandHeaderSampleView.xaml에 대한 상호 작용 논리
22 | ///
23 | public partial class ColoredBandHeaderSampleView : Window
24 | {
25 | public ColoredBandHeaderSampleView()
26 | {
27 | InitializeComponent();
28 | flexGrid.ItemsSource = Person.SampleData;
29 | }
30 |
31 | private void OnHyperlinkRequestNavigate(object sender, RequestNavigateEventArgs e)
32 | {
33 | nameBand.HorizontalHeaderAlignment = HorizontalAlignment.Right;
34 | var info = new ProcessStartInfo(e.Uri.AbsoluteUri) { UseShellExecute = true };
35 | Process.Start(info);
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/Views/FrozenHeaderView.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
23 |
24 |
28 |
29 |
34 |
35 |
38 |
39 |
40 |
41 |
46 |
47 |
50 |
51 |
52 |
53 |
54 |
55 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/Views/FrozenHeaderView.xaml.cs:
--------------------------------------------------------------------------------
1 | using KevinComponent.Demo.App.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using System.Windows.Data;
11 | using System.Windows.Documents;
12 | using System.Windows.Input;
13 | using System.Windows.Media;
14 | using System.Windows.Media.Imaging;
15 | using System.Windows.Navigation;
16 | using System.Windows.Shapes;
17 |
18 | namespace KevinComponent.Demo.App.Views
19 | {
20 | ///
21 | /// FrozenHeaderView.xaml에 대한 상호 작용 논리
22 | ///
23 | public partial class FrozenHeaderView : Window
24 | {
25 | public FrozenHeaderView()
26 | {
27 | InitializeComponent();
28 | flexGrid.ItemsSource = Person.SampleData;
29 | }
30 |
31 | private void OnHyperlinkRequestNavigate(object sender, RequestNavigateEventArgs e)
32 | {
33 | var info = new ProcessStartInfo(e.Uri.AbsoluteUri) { UseShellExecute = true };
34 | Process.Start(info);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/Views/MergedHeaderSampleView.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
18 |
19 |
20 |
21 |
26 |
27 |
32 |
33 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/Views/MergedHeaderSampleView.xaml.cs:
--------------------------------------------------------------------------------
1 | using KevinComponent.Demo.App.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using System.Windows.Data;
11 | using System.Windows.Documents;
12 | using System.Windows.Input;
13 | using System.Windows.Media;
14 | using System.Windows.Media.Imaging;
15 | using System.Windows.Navigation;
16 | using System.Windows.Shapes;
17 |
18 | namespace KevinComponent.Demo.App.Views
19 | {
20 | ///
21 | /// MergedHeaderSample.xaml에 대한 상호 작용 논리
22 | ///
23 | public partial class MergedHeaderSampleView : Window
24 | {
25 | public MergedHeaderSampleView()
26 | {
27 | InitializeComponent();
28 | flexGrid.ItemsSource = Person.SampleData;
29 | }
30 |
31 | private void OnHyperlinkRequestNavigate(object sender, RequestNavigateEventArgs e)
32 | {
33 | var info = new ProcessStartInfo(e.Uri.AbsoluteUri) { UseShellExecute = true };
34 | Process.Start(info);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/Views/VirtualBandSampleView.xaml:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
48 |
53 |
54 |
55 |
60 |
61 |
65 |
66 |
67 |
68 |
69 |
70 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
86 |
91 |
92 |
93 |
98 |
99 |
103 |
104 |
105 |
106 |
107 |
108 |
113 |
114 |
119 |
120 |
125 |
126 |
130 |
131 |
132 |
133 |
134 |
135 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
--------------------------------------------------------------------------------
/src/KevinComponent.Demo.App/Views/VirtualBandSampleView.xaml.cs:
--------------------------------------------------------------------------------
1 | using KevinComponent.Demo.App.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Collections.ObjectModel;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using System.Windows.Data;
11 | using System.Windows.Documents;
12 | using System.Windows.Input;
13 | using System.Windows.Media;
14 | using System.Windows.Media.Imaging;
15 | using System.Windows.Shapes;
16 |
17 | namespace KevinComponent.Demo.App.Views
18 | {
19 | ///
20 | /// VirtualBandSampleView.xaml에 대한 상호 작용 논리
21 | ///
22 | public partial class VirtualBandSampleView : Window
23 | {
24 | public VirtualBandSampleView()
25 | {
26 | InitializeComponent();
27 | _people = Person.SampleData;
28 | _subjects = Subject.SampleData;
29 |
30 | fgdPeople.ItemsSource = _people;
31 | fgdSubjects.ItemsSource = _subjects;
32 | flexGrid.ItemsSource = _people;
33 | vbandSubjects.ItemsSource = _subjects;
34 |
35 | // Set Sample Subject Scores
36 | var rand = new Random();
37 | foreach (var person in _people)
38 | {
39 | foreach (var subject in _subjects)
40 | person[subject].Value = rand.Next(0, 100);
41 | }
42 | }
43 |
44 | #region Private Variables
45 |
46 | ObservableCollection _people;
47 | ObservableCollection _subjects;
48 |
49 | #endregion
50 |
51 | #region Private Event Handlers
52 |
53 | private void AddPerson_Click(object sender, RoutedEventArgs e)
54 | {
55 | _people.Add(new Person("NEW PERSON"));
56 | }
57 |
58 | private void RemovePerson_Click(object sender, RoutedEventArgs e)
59 | {
60 | var items = fgdPeople.SelectedItems;
61 | foreach (Person item in items.Cast().ToArray())
62 | _people.Remove(item);
63 | }
64 |
65 | #endregion
66 |
67 | private void AddSubject_Click(object sender, RoutedEventArgs e)
68 | {
69 | _subjects.Add(new Subject("NEW SUBJECT"));
70 | }
71 |
72 | private void RemoveSubject_Click(object sender, RoutedEventArgs e)
73 | {
74 | var items = fgdSubjects.SelectedItems;
75 | foreach (Subject item in items.Cast().ToArray())
76 | _subjects.Remove(item);
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/KevinComponent.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.3.32819.101
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KevinComponent", "KevinComponent\KevinComponent.csproj", "{C4A37AC1-0ED7-4EEC-9952-50EFECE11CA3}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KevinComponent.Demo.App", "KevinComponent.Demo.App\KevinComponent.Demo.App.csproj", "{5CE219BF-7551-4903-99C7-60BB7FB43179}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | GitHub Release|Any CPU = GitHub Release|Any CPU
14 | Release|Any CPU = Release|Any CPU
15 | EndGlobalSection
16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
17 | {C4A37AC1-0ED7-4EEC-9952-50EFECE11CA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
18 | {C4A37AC1-0ED7-4EEC-9952-50EFECE11CA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
19 | {C4A37AC1-0ED7-4EEC-9952-50EFECE11CA3}.GitHub Release|Any CPU.ActiveCfg = GitHub Release|Any CPU
20 | {C4A37AC1-0ED7-4EEC-9952-50EFECE11CA3}.GitHub Release|Any CPU.Build.0 = GitHub Release|Any CPU
21 | {C4A37AC1-0ED7-4EEC-9952-50EFECE11CA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
22 | {C4A37AC1-0ED7-4EEC-9952-50EFECE11CA3}.Release|Any CPU.Build.0 = Release|Any CPU
23 | {5CE219BF-7551-4903-99C7-60BB7FB43179}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {5CE219BF-7551-4903-99C7-60BB7FB43179}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {5CE219BF-7551-4903-99C7-60BB7FB43179}.GitHub Release|Any CPU.ActiveCfg = GitHub Release|Any CPU
26 | {5CE219BF-7551-4903-99C7-60BB7FB43179}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {5CE219BF-7551-4903-99C7-60BB7FB43179}.Release|Any CPU.Build.0 = Release|Any CPU
28 | EndGlobalSection
29 | GlobalSection(SolutionProperties) = preSolution
30 | HideSolutionNode = FALSE
31 | EndGlobalSection
32 | GlobalSection(ExtensibilityGlobals) = postSolution
33 | SolutionGuid = {BE9F89BF-CC65-48BB-B6B4-B485B167E447}
34 | EndGlobalSection
35 | EndGlobal
36 |
--------------------------------------------------------------------------------
/src/KevinComponent/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | [assembly: ThemeInfo(
4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5 | //(used if a resource is not found in the page,
6 | // or application resource dictionaries)
7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8 | //(used if a resource is not found in the page,
9 | // app, or any theme specific resource dictionaries)
10 | )]
11 |
--------------------------------------------------------------------------------
/src/KevinComponent/Assist/FlexGridAssist.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 |
9 | namespace KevinComponent.Assist
10 | {
11 | public static class FlexGridAssist
12 | {
13 | #region Dependecny Attached Properties
14 |
15 | public static readonly DependencyProperty ParentCellProperty =
16 | DependencyProperty.RegisterAttached(
17 | "ParentCell",
18 | typeof(DataGridCell),
19 | typeof(FlexGridAssist),
20 | new FrameworkPropertyMetadata(null));
21 |
22 | public static DataGridCell GetParentCell(DependencyObject element) => (DataGridCell)element.GetValue(ParentCellProperty);
23 | public static void SetParentCell(DependencyObject element, DataGridCell value) => element.SetValue(ParentCellProperty, value);
24 |
25 | #endregion
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/KevinComponent/Assist/TextBoxAssist.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Text.RegularExpressions;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using System.Windows.Input;
10 |
11 | namespace KevinComponent.Assist
12 | {
13 | public static class TextBoxAssist
14 | {
15 | #region Dependency Attached Properties
16 |
17 | #region CaretIndexAlwaysEndOfText
18 |
19 | public static readonly DependencyProperty CaretIndexAlwaysEndOfTextProperty =
20 | DependencyProperty.RegisterAttached("CaretIndexAlwaysEndOfText",
21 | typeof(bool),
22 | typeof(TextBoxAssist),
23 | new FrameworkPropertyMetadata(OnSetCaretIndexAlwaysEndOfText));
24 |
25 | public static bool GetCaretIndexAlwaysEndOfText(DependencyObject element) => (bool)element.GetValue(CaretIndexAlwaysEndOfTextProperty);
26 | public static void SetCaretIndexAlwaysEndOfText(DependencyObject element, bool value) => element.SetValue(CaretIndexAlwaysEndOfTextProperty, value);
27 |
28 | private static void OnSetCaretIndexAlwaysEndOfText(DependencyObject d, DependencyPropertyChangedEventArgs e)
29 | {
30 | if (!(d is TextBox textBox)
31 | || !(e.NewValue is bool value))
32 | return;
33 |
34 | if (value)
35 | {
36 | textBox.TextChanged += OnTextBoxTextChanged;
37 | SetCaretIndexEndOfText(textBox);
38 | }
39 | else
40 | {
41 | textBox.TextChanged -= OnTextBoxTextChanged;
42 | }
43 | }
44 |
45 | private static void OnTextBoxTextChanged(object sender, TextChangedEventArgs e)
46 | {
47 | if (!(sender is TextBox textBox))
48 | return;
49 |
50 | SetCaretIndexEndOfText(textBox);
51 | }
52 |
53 | private static void SetCaretIndexEndOfText(TextBox textBox)
54 | {
55 | textBox.Focus();
56 | textBox.CaretIndex = textBox.Text.Length;
57 | }
58 |
59 | #endregion
60 |
61 | #region IsNumericTextBox
62 |
63 | public static readonly DependencyProperty IsNumericTextBoxProperty =
64 | DependencyProperty.RegisterAttached(
65 | "IsNumericTextBox",
66 | typeof(bool),
67 | typeof(TextBoxAssist),
68 | new FrameworkPropertyMetadata(OnSetIsNumericTextBox));
69 |
70 | public static bool GetIsNumericTextBox(DependencyObject element) => (bool)element.GetValue(IsNumericTextBoxProperty);
71 | public static void SetIsNumericTextBox(DependencyObject element, bool value) => element.SetValue(IsNumericTextBoxProperty, value);
72 |
73 | private static Regex _numericTextRegexAllowNegative = new Regex("^[0-9.-]+");
74 | private static Regex _numericTextRegexDisallowNegative = new Regex("^[0-9.]+");
75 | private static Dictionary _originAlignments = new Dictionary();
76 |
77 | private static void OnSetIsNumericTextBox(DependencyObject d, DependencyPropertyChangedEventArgs e)
78 | {
79 | if (!(d is TextBox textBox)
80 | || !(e.NewValue is bool value))
81 | return;
82 |
83 | if (value)
84 | {
85 | if (!_originAlignments.ContainsKey(textBox))
86 | _originAlignments.Add(textBox, HorizontalAlignment.Left);
87 |
88 | textBox.HorizontalContentAlignment = HorizontalAlignment.Right;
89 |
90 | textBox.PreviewTextInput += OnTextBoxPreviewInput;
91 | DataObject.AddPastingHandler(textBox, OnTextBoxDataObjectPasting);
92 | }
93 | else
94 | {
95 | if (_originAlignments.ContainsKey(textBox))
96 | textBox.HorizontalAlignment = _originAlignments[textBox];
97 |
98 | textBox.PreviewTextInput -= OnTextBoxPreviewInput;
99 | DataObject.RemovePastingHandler(textBox, OnTextBoxDataObjectPasting);
100 | }
101 | }
102 |
103 | private static bool IsNumericText(string text, bool allowNegative)
104 | {
105 | if (allowNegative)
106 | return _numericTextRegexAllowNegative.IsMatch(text);
107 | else
108 | return _numericTextRegexDisallowNegative.IsMatch(text);
109 | }
110 |
111 | #endregion
112 |
113 | #region AllowNegative
114 |
115 | public static readonly DependencyProperty AllowNegativeProperty =
116 | DependencyProperty.RegisterAttached("AllowNegative",
117 | typeof(bool),
118 | typeof(TextBoxAssist),
119 | new FrameworkPropertyMetadata(true));
120 | public static bool GetAllowNegative(DependencyObject element) => (bool)element.GetValue(AllowNegativeProperty);
121 | public static void SetAllowNegative(DependencyObject element, bool value) => element.SetValue(AllowNegativeProperty, value);
122 |
123 | private static void OnTextBoxPreviewInput(object sender, TextCompositionEventArgs e)
124 | {
125 | bool allowNegative = true;
126 | if (sender is DependencyObject d)
127 | allowNegative = GetAllowNegative(d);
128 |
129 | e.Handled = !IsNumericText(e.Text, allowNegative);
130 | }
131 |
132 | private static void OnTextBoxDataObjectPasting(object sender, DataObjectPastingEventArgs e)
133 | {
134 | if (e.DataObject.GetDataPresent(typeof(string)))
135 | {
136 | bool allowNegative = true;
137 | if (sender is DependencyObject d)
138 | allowNegative = GetAllowNegative(d);
139 |
140 | string text = (string)e.DataObject.GetData(typeof(string));
141 |
142 | if (!IsNumericText(text, allowNegative))
143 | e.CancelCommand();
144 | }
145 | else
146 | {
147 | e.CancelCommand();
148 | }
149 | }
150 |
151 | #endregion
152 |
153 | #endregion
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/KevinComponent/Band.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Specialized;
4 | using System.ComponentModel;
5 | using System.Linq;
6 | using System.Runtime.CompilerServices;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using System.Windows;
10 | using System.Windows.Controls;
11 | using System.Windows.Controls.Primitives;
12 | using System.Windows.Data;
13 | using System.Windows.Documents;
14 | using System.Windows.Media.Animation;
15 | using System.Windows.Media.TextFormatting;
16 | using KevinComponent.Assist;
17 |
18 | namespace KevinComponent
19 | {
20 | public abstract class Band : FrameworkElement
21 | {
22 | static Band()
23 | {
24 | DefaultStyleKeyProperty.OverrideMetadata(typeof(Band), new FrameworkPropertyMetadata(typeof(Band)));
25 | WidthProperty.OverrideMetadata(typeof(Band), new FrameworkPropertyMetadata(20d));
26 | MinWidthProperty.OverrideMetadata(typeof(Band), new FrameworkPropertyMetadata(20d));
27 | }
28 |
29 | protected Band()
30 | {
31 | SyncDataGridColumn = new FlexGridColumn(this);
32 |
33 | Bands = new BandCollection(this);
34 | Bands.CollectionChanged += OnBandsCollectionChanged;
35 | Bands.VirtualBandItemsSourceChanged += OnVirtualBandItemsSourceChanged;
36 | Bands.VirtualBandItemsSourceCollectionChanged += OnVirtualBandItemsSourceCollectionChanged;
37 |
38 | UpdateDefaultStyle();
39 | }
40 |
41 | protected Band(VirtualBand virtualBand) : this()
42 | {
43 | OwnerFlexGrid = virtualBand.OwnerFlexGrid;
44 | ParentBand = virtualBand.ParentBand;
45 | CellStyle = virtualBand.CellStyle;
46 | MinWidth = virtualBand.MinWidth;
47 | MaxWidth = virtualBand.MaxWidth;
48 | Width = virtualBand.Width;
49 | HorizontalHeaderAlignment = virtualBand.HorizontalHeaderAlignment;
50 | VerticalHeaderAlignment = virtualBand.VerticalHeaderAlignment;
51 | DerivationFrom = virtualBand;
52 |
53 | if (virtualBand.HeaderBinding != null)
54 | BindingOperations.SetBinding(this, HeaderProperty, virtualBand.HeaderBinding);
55 | else
56 | Header = virtualBand.Header;
57 | }
58 |
59 | #region Dependency Properties
60 |
61 | public static readonly DependencyProperty HeaderProperty =
62 | DependencyProperty.Register(
63 | "Header",
64 | typeof(object),
65 | typeof(Band),
66 | new FrameworkPropertyMetadata(null, OnHeaderPropertyChanged));
67 | public static readonly DependencyProperty HeaderStyleProperty =
68 | DependencyProperty.Register(
69 | "HeaderStyle",
70 | typeof(Style),
71 | typeof(Band),
72 | new FrameworkPropertyMetadata(null, OnHeaderStylePropertyChanged));
73 | public static readonly DependencyProperty HorizontalHeaderAlignmentProperty =
74 | DependencyProperty.Register(
75 | "HorizontalHeaderAlignment",
76 | typeof(HorizontalAlignment),
77 | typeof(Band),
78 | new FrameworkPropertyMetadata(HorizontalAlignment.Center));
79 | public static readonly DependencyProperty VerticalHeaderAlignmentProperty =
80 | DependencyProperty.Register(
81 | "VerticalHeaderAlignment",
82 | typeof(VerticalAlignment),
83 | typeof(Band),
84 | new FrameworkPropertyMetadata(VerticalAlignment.Center));
85 | public static readonly DependencyProperty CellTemplateProperty =
86 | DependencyProperty.Register(
87 | "CellTemplate",
88 | typeof(DataTemplate),
89 | typeof(Band),
90 | new FrameworkPropertyMetadata(null, OnCellTemplatePropertyChanged));
91 | public static readonly DependencyProperty CellEditingTemplateProperty =
92 | DependencyProperty.Register(
93 | "CellEditingTemplate",
94 | typeof(DataTemplate),
95 | typeof(Band),
96 | new FrameworkPropertyMetadata(null, OnCellEditingTemplatePropertyChanged));
97 | public static readonly DependencyProperty CellStyleProperty =
98 | DependencyProperty.Register(
99 | "CellStyle",
100 | typeof(Style),
101 | typeof(Band),
102 | new FrameworkPropertyMetadata(null, OnCellStylePropertyChanged));
103 | public static readonly DependencyProperty IsReadOnlyProperty =
104 | DependencyProperty.Register(
105 | "IsReadOnly",
106 | typeof(bool),
107 | typeof(Band),
108 | new FrameworkPropertyMetadata(false));
109 | public static readonly DependencyProperty CanUserSortProperty =
110 | DependencyProperty.Register(
111 | "CanUserSort",
112 | typeof(bool),
113 | typeof(Band),
114 | new FrameworkPropertyMetadata(false));
115 | public static readonly DependencyProperty SortDirectionProperty =
116 | DependencyProperty.Register(
117 | "SortDirection",
118 | typeof(ListSortDirection?),
119 | typeof(Band),
120 | new FrameworkPropertyMetadata(null));
121 | public static readonly DependencyProperty SortMemberPathProperty =
122 | DependencyProperty.Register(
123 | "SortMemberPath",
124 | typeof(string),
125 | typeof(Band),
126 | new FrameworkPropertyMetadata(string.Empty));
127 |
128 | private static void OnHeaderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
129 | {
130 | var band = d as Band;
131 | if (band == null)
132 | return;
133 |
134 | bool useHeaderTemplate = false;
135 | var newValue = e.NewValue;
136 | if (newValue is DataTemplate dt)
137 | {
138 | var cp = new ContentPresenter();
139 |
140 | cp.ContentTemplate = dt;
141 | BindingOperations.SetBinding(cp, ContentPresenter.ContentProperty, new Binding());
142 |
143 | newValue = cp;
144 | useHeaderTemplate = true;
145 | }
146 |
147 | band.SyncDataGridColumn.Header = newValue;
148 | band.BandHeader.Content = newValue;
149 | band._useHeaderTemplate = useHeaderTemplate;
150 | }
151 |
152 | private static void OnHeaderStylePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
153 | {
154 | var band = d as Band;
155 | if (band == null)
156 | return;
157 |
158 | var newValue = e.NewValue as Style;
159 | if (newValue == null)
160 | return;
161 |
162 | band.BandHeader.Style = newValue;
163 | }
164 |
165 | private static void OnCellTemplatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
166 | {
167 | var band = d as Band;
168 | band?.OnCellTemplateChanged((DataTemplate)e.OldValue, (DataTemplate)e.NewValue);
169 | }
170 |
171 | private static void OnCellEditingTemplatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
172 | {
173 | var band = d as Band;
174 | band?.OnCellEditingTemplateChanged((DataTemplate)e.OldValue, (DataTemplate)e.NewValue);
175 | }
176 |
177 | private static void OnCellStylePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
178 | {
179 | var band = d as Band;
180 | band?.OnCellStyleChanged((Style)e.OldValue, (Style)e.NewValue);
181 | }
182 |
183 | #endregion
184 |
185 | #region Private Variables
186 |
187 | BandHeader _bandHeader;
188 | bool _useHeaderTemplate;
189 | FlexGrid _ownerFlexGrid;
190 | bool _settingWithoutParentBand;
191 | bool _settingWithoutSubBands;
192 | Band _parentBand;
193 |
194 | #endregion
195 |
196 | #region Internal Properties
197 |
198 | internal VirtualBand DerivationFrom { get; }
199 |
200 | internal Band ParentBand
201 | {
202 | get => _parentBand;
203 | set
204 | {
205 | if (_parentBand != value)
206 | {
207 | var oldParentBand = _parentBand;
208 | _parentBand = value;
209 | OnParentBandChanged(oldParentBand, value);
210 | }
211 | }
212 | }
213 |
214 | internal FlexGrid OwnerFlexGrid
215 | {
216 | get => _ownerFlexGrid;
217 | set
218 | {
219 | if (_ownerFlexGrid != value)
220 | {
221 | var oldOwnerFlexGrid = _ownerFlexGrid;
222 | _ownerFlexGrid = value;
223 | OnOwnerFlexGridChanged(oldOwnerFlexGrid, value);
224 | }
225 | }
226 | }
227 |
228 | internal BandHeader BandHeader
229 | {
230 | get
231 | {
232 | if (_bandHeader == null)
233 | _bandHeader = new BandHeader(this);
234 |
235 | return _bandHeader;
236 | }
237 | }
238 |
239 | internal FlexGridColumn SyncDataGridColumn { get; }
240 |
241 | #endregion
242 |
243 | #region Public Properties
244 |
245 | public int Depth
246 | => Bands.MaxDepth + 1;
247 |
248 | public BandCollection Bands { get; }
249 |
250 | public bool HasChildBands
251 | => Bands.Count > 0;
252 |
253 | public object Header
254 | {
255 | get => GetValue(HeaderProperty);
256 | set => SetValue(HeaderProperty, value);
257 | }
258 |
259 | public Style HeaderStyle
260 | {
261 | get => (Style)GetValue(HeaderStyleProperty);
262 | set => SetValue(HeaderStyleProperty, value);
263 | }
264 |
265 | public HorizontalAlignment HorizontalHeaderAlignment
266 | {
267 | get => (HorizontalAlignment)GetValue(HorizontalHeaderAlignmentProperty);
268 | set => SetValue(HorizontalHeaderAlignmentProperty, value);
269 | }
270 |
271 | public VerticalAlignment VerticalHeaderAlignment
272 | {
273 | get => (VerticalAlignment)GetValue(VerticalHeaderAlignmentProperty);
274 | set => SetValue(VerticalHeaderAlignmentProperty, value);
275 | }
276 |
277 | public DataTemplate CellTemplate
278 | {
279 | get => (DataTemplate)GetValue(CellTemplateProperty);
280 | set => SetValue(CellTemplateProperty, value);
281 | }
282 |
283 | public DataTemplate CellEditingTemplate
284 | {
285 | get => (DataTemplate)GetValue(CellEditingTemplateProperty);
286 | set => SetValue(CellEditingTemplateProperty, value);
287 | }
288 |
289 | public Style CellStyle
290 | {
291 | get => (Style)GetValue(StyleProperty);
292 | set => SetValue(StyleProperty, value);
293 | }
294 |
295 | public bool IsReadOnly
296 | {
297 | get => (bool)GetValue(IsReadOnlyProperty);
298 | set => SetValue(IsReadOnlyProperty, value);
299 | }
300 |
301 | public bool CanUserSort
302 | {
303 | get => (bool)GetValue(CanUserSortProperty);
304 | set => SetValue(CanUserSortProperty, value);
305 | }
306 |
307 | public ListSortDirection? SortDirection
308 | {
309 | get => (ListSortDirection?)GetValue(SortDirectionProperty);
310 | set => SetValue(SortDirectionProperty, value);
311 | }
312 |
313 | public string SortMemberPath
314 | {
315 | get => (string)GetValue(SortMemberPathProperty);
316 | set => SetValue(SortMemberPathProperty, value);
317 | }
318 |
319 | #endregion
320 |
321 | #region Private Methods
322 |
323 | private void SetBandsOwnerFlexGrid(FlexGrid flexGrid)
324 | {
325 | Bands.OwnerFlexGrid = flexGrid;
326 | }
327 |
328 | private DataTemplate GetTemplate(bool isEditing)
329 | {
330 | if (isEditing && !IsReadOnly && CellEditingTemplate != null)
331 | return CellEditingTemplate;
332 | else
333 | return CellTemplate;
334 | }
335 |
336 | private void AttachEventHandlers(ContentPresenter contentPresenter, bool isEditing)
337 | {
338 | DetachEventHandlers(contentPresenter, isEditing);
339 |
340 | if (isEditing && !IsReadOnly)
341 | {
342 | contentPresenter.Loaded += OnCellEditingTemplateLoaded;
343 | contentPresenter.Unloaded += OnCellEditingTemplateUnloaded;
344 | }
345 | else
346 | {
347 | contentPresenter.Loaded += OnCellTemplateLoaded;
348 | contentPresenter.Unloaded += OnCellTemplateUnloaded;
349 | }
350 | }
351 |
352 | private void DetachEventHandlers(ContentPresenter contentPresenter, bool isEditing)
353 | {
354 | if (isEditing && !IsReadOnly)
355 | {
356 | contentPresenter.Loaded -= OnCellEditingTemplateLoaded;
357 | contentPresenter.Unloaded -= OnCellEditingTemplateUnloaded;
358 | }
359 | else
360 | {
361 | contentPresenter.Loaded -= OnCellTemplateLoaded;
362 | contentPresenter.Unloaded -= OnCellTemplateUnloaded;
363 | }
364 | }
365 |
366 | private void AttachEventHandlers(INotifyPropertyChanged dataContext)
367 | {
368 | DetachEventHandlers(dataContext);
369 | dataContext.PropertyChanged += OnDataContextPropertyChanged;
370 | }
371 |
372 | private void DetachEventHandlers(INotifyPropertyChanged dataContext)
373 | {
374 | dataContext.PropertyChanged -= OnDataContextPropertyChanged;
375 | }
376 |
377 | private double LimitWidthForPreventOverflow(ref double totalWidth, double newWidth)
378 | {
379 | totalWidth -= newWidth;
380 | if (totalWidth < 0)
381 | {
382 | var result = newWidth + totalWidth;
383 | totalWidth = 0;
384 |
385 | return result;
386 | }
387 |
388 | return newWidth;
389 | }
390 |
391 | private void SetSubBandsWidth(double newTotalWidth)
392 | {
393 | if (_settingWithoutSubBands)
394 | return;
395 |
396 | var unusedWidth = newTotalWidth;
397 | var prevTotalWidth = Bands.TotalWidth;
398 | foreach (var band in Bands)
399 | {
400 | if (band is VirtualBand vband)
401 | {
402 | foreach (var subBand in vband.VirtualizedBands)
403 | {
404 | var calcWidth = Math.Max(subBand.MinWidth, newTotalWidth * (subBand.Width / prevTotalWidth));
405 | calcWidth = LimitWidthForPreventOverflow(ref unusedWidth, calcWidth);
406 | subBand.SetWidthWithoutParentBand(calcWidth);
407 | }
408 | }
409 | else
410 | {
411 | var calcWidth = Math.Max(band.MinWidth, newTotalWidth * (band.Width / prevTotalWidth));
412 | calcWidth = LimitWidthForPreventOverflow(ref unusedWidth, calcWidth);
413 | band.SetWidthWithoutParentBand(calcWidth);
414 | }
415 | }
416 | }
417 |
418 | private void SetParentBandWidth(double addedWidth)
419 | {
420 | if (ParentBand == null || _settingWithoutParentBand)
421 | return;
422 |
423 | var newWidth = ParentBand.Width + addedWidth;
424 | newWidth = Math.Max(MinWidth, newWidth);
425 | newWidth = Math.Min(MaxWidth, newWidth);
426 |
427 | ParentBand.SetWidthWithoutSubBands(newWidth);
428 | }
429 |
430 | private void SetWidthWithoutParentBand(double width)
431 | {
432 | _settingWithoutParentBand = true;
433 |
434 | Width = width;
435 |
436 | _settingWithoutParentBand = false;
437 | }
438 |
439 | private void SetWidthWithoutSubBands(double width)
440 | {
441 | // Lock Setting SubBands.
442 | _settingWithoutSubBands = true;
443 |
444 | Width = width;
445 |
446 | // Unlock Setting SubBands.
447 | _settingWithoutSubBands = false;
448 | }
449 |
450 | private void UpdateAllWidths()
451 | {
452 | double totalWidth = 0d;
453 | double totalMinWidth = 0d;
454 | double totalMaxWidth = 0d;
455 |
456 | foreach (var band in Bands)
457 | {
458 | if (band is VirtualBand vband)
459 | {
460 | foreach (var subBand in vband.VirtualizedBands)
461 | {
462 | totalWidth += subBand.Width;
463 | totalMinWidth += subBand.MinWidth;
464 | totalMaxWidth += subBand.MaxWidth;
465 | }
466 | }
467 | else
468 | {
469 | totalWidth += band.Width;
470 | totalMinWidth += band.MinWidth;
471 | totalMaxWidth += band.MaxWidth;
472 | }
473 | }
474 |
475 | MinWidth = totalMinWidth;
476 | MaxWidth = totalMaxWidth;
477 | Width = totalWidth;
478 |
479 | ParentBand?.UpdateAllWidths();
480 | }
481 |
482 | private void SetBindingSource(BindingBase bindingBase, object source)
483 | {
484 | if (bindingBase is Binding newBinding)
485 | {
486 | if (newBinding.Source == null
487 | && newBinding.ElementName == null
488 | && newBinding.RelativeSource == null)
489 | newBinding.Source = source;
490 | }
491 | else if (bindingBase is MultiBinding newMultiBinding)
492 | {
493 | foreach (var child in newMultiBinding.Bindings)
494 | SetBindingSource(child, source);
495 | }
496 | else if (bindingBase is PriorityBinding newPrioBinding)
497 | {
498 | foreach (var child in newPrioBinding.Bindings)
499 | SetBindingSource(child, source);
500 | }
501 | }
502 |
503 | private void UpdateVirtualBandBindings(ContentPresenter cp)
504 | {
505 | if (DerivationFrom == null && DataContext == null)
506 | return;
507 |
508 | var propertyAndBindings = Utils.GetBindings(cp);
509 | foreach (var (element, property, bindingBase) in propertyAndBindings)
510 | {
511 | var cell = FlexGridAssist.GetParentCell(cp);
512 | if (cell == null)
513 | continue;
514 |
515 | if (Utils.GetIndexerValue(cell.DataContext, new object[] { DataContext }, out object bindingSource))
516 | {
517 | if (cell.DataContext is INotifyPropertyChanged dataContext)
518 | AttachEventHandlers(dataContext);
519 |
520 | var newBindingBase = Utils.CloneBinding((BindingBase)bindingBase);
521 | SetBindingSource(newBindingBase, bindingSource);
522 |
523 | BindingOperations.SetBinding(element, property, newBindingBase);
524 | }
525 | else
526 | {
527 | var newBindingBase = Utils.CloneBinding((BindingBase)bindingBase);
528 | SetBindingSource(newBindingBase, null);
529 |
530 | BindingOperations.SetBinding(element, property, newBindingBase);
531 | }
532 | }
533 | }
534 |
535 | private void SetWidth(double oldValue, double newValue)
536 | {
537 | if (this is VirtualBand)
538 | return;
539 |
540 | newValue = Math.Max(MinWidth, newValue);
541 | newValue = Math.Min(MaxWidth, newValue);
542 |
543 | BandHeader.Width = newValue;
544 | SetSubBandsWidth(newValue);
545 | SetParentBandWidth(newValue - oldValue);
546 | }
547 |
548 | private void SetSyncDataGridColumnMinWidth(double newValue)
549 | {
550 | if (SyncDataGridColumn != null)
551 | SyncDataGridColumn.MinWidth = newValue;
552 | }
553 |
554 | private void SetSyncDataGridColumnMaxWidth(double newValue)
555 | {
556 | if (SyncDataGridColumn != null)
557 | SyncDataGridColumn.MaxWidth = newValue;
558 | }
559 |
560 | private void SetSyncDataGridColumnCanUserSort(bool newValue)
561 | {
562 | if (SyncDataGridColumn != null)
563 | SyncDataGridColumn.CanUserSort = newValue;
564 | }
565 |
566 | private void SetSyncDataGridColumnSortDirection(ListSortDirection? newValue)
567 | {
568 | if (SyncDataGridColumn != null)
569 | SyncDataGridColumn.SortDirection = newValue;
570 |
571 | if (BandHeader != null)
572 | BandHeader.SortDirection = newValue;
573 | }
574 |
575 | private void SetSyncDataGridColumnSortMemberPath(string newValue)
576 | {
577 | if (SyncDataGridColumn != null)
578 | SyncDataGridColumn.SortMemberPath = newValue;
579 | }
580 |
581 | private void SetBandHeaderDataContext(object newValue)
582 | {
583 | if (BandHeader != null)
584 | BandHeader.DataContext = newValue;
585 | }
586 |
587 | #endregion
588 |
589 | #region Private EventHandlers
590 |
591 | private void OnVirtualBandItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
592 | {
593 | UpdateAllWidths();
594 | }
595 |
596 | private void OnVirtualBandItemsSourceChanged(object sender, DependencyPropertyChangedEventArgs e)
597 | {
598 | UpdateAllWidths();
599 | }
600 |
601 | private void OnBandsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
602 | {
603 | UpdateAllWidths();
604 | }
605 |
606 | private void OnCellTemplateLoaded(object sender, RoutedEventArgs e)
607 | {
608 | if (sender is ContentPresenter cp)
609 | {
610 | OnCellTemplateLoaded(cp);
611 | UpdateVirtualBandBindings(cp);
612 | }
613 | }
614 |
615 | private void OnCellTemplateUnloaded(object sender, RoutedEventArgs e)
616 | {
617 | if (sender is ContentPresenter cp)
618 | {
619 | cp.Loaded -= OnCellTemplateLoaded;
620 | cp.Unloaded -= OnCellTemplateUnloaded;
621 | }
622 | }
623 |
624 | private void OnCellEditingTemplateLoaded(object sender, RoutedEventArgs e)
625 | {
626 | if (sender is ContentPresenter cp)
627 | {
628 | OnCellEditingTemplateLoaded(cp);
629 | UpdateVirtualBandBindings(cp);
630 | PrepareEdit(cp);
631 | }
632 | }
633 |
634 | private void OnCellEditingTemplateUnloaded(object sender, RoutedEventArgs e)
635 | {
636 | if (sender is ContentPresenter cp)
637 | {
638 | cp.Loaded -= OnCellEditingTemplateLoaded;
639 | cp.Unloaded -= OnCellEditingTemplateUnloaded;
640 | }
641 | }
642 |
643 | private void OnDataContextPropertyChanged(object sender, PropertyChangedEventArgs e)
644 | {
645 | switch (e.PropertyName)
646 | {
647 | case "Item[]":
648 | OnDataContextIndexerChanged(sender);
649 | break;
650 | }
651 | }
652 |
653 | private void OnDataContextIndexerChanged(object sender)
654 | {
655 | if (!(OwnerFlexGrid.ItemContainerGenerator.ContainerFromItem(sender) is DataGridRow row))
656 | return;
657 |
658 | SyncDataGridColumn?.RefreshCellContent(row);
659 | }
660 |
661 | #endregion
662 |
663 | #region Internal Methods
664 |
665 | internal FrameworkElement GenerateElement(DataGridCell cell, bool isEditing)
666 | {
667 | var cp = new ContentPresenter();
668 | FlexGridAssist.SetParentCell(cp, cell);
669 | BindingOperations.SetBinding(cp, ContentPresenter.ContentProperty, new Binding());
670 |
671 | cp.ContentTemplate = GetTemplate(isEditing);
672 |
673 | AttachEventHandlers(cp, isEditing);
674 |
675 | return cp;
676 | }
677 |
678 | #endregion
679 |
680 | #region Public Methods
681 |
682 | public void PerformSort()
683 | {
684 | if (SyncDataGridColumn != null)
685 | OwnerFlexGrid?.PerformSort(SyncDataGridColumn);
686 | }
687 |
688 | #endregion
689 |
690 | #region Protected Virtual Methods
691 |
692 | protected virtual void OnParentBandChanged(Band oldParent, Band newParent) { }
693 |
694 | protected virtual void OnOwnerFlexGridChanged(FlexGrid oldOwnerFlexGrid, FlexGrid newOwnerFlexGrid)
695 | {
696 | SetBandsOwnerFlexGrid(newOwnerFlexGrid);
697 | }
698 |
699 | protected virtual void OnCellTemplateChanged(DataTemplate oldValue, DataTemplate newValue)
700 | {
701 | if (OwnerFlexGrid != null)
702 | {
703 | foreach (var item in OwnerFlexGrid.Items)
704 | {
705 | var row = (DataGridRow)OwnerFlexGrid.ItemContainerGenerator.ContainerFromItem(item);
706 | if (row == null)
707 | continue;
708 |
709 | SyncDataGridColumn?.RefreshCellContent(row);
710 | }
711 | }
712 | }
713 |
714 | protected virtual void OnCellEditingTemplateChanged(DataTemplate oldValue, DataTemplate newValue) { }
715 |
716 | protected virtual void OnCellStyleChanged(Style oldValue, Style newValue)
717 | {
718 | if (SyncDataGridColumn != null)
719 | SyncDataGridColumn.CellStyle = newValue;
720 | }
721 |
722 | protected virtual void OnCellTemplateLoaded(ContentPresenter contentPresenter) { }
723 |
724 | protected virtual void OnCellEditingTemplateLoaded(ContentPresenter contentPresenter)
725 | {
726 | OnCellTemplateLoaded(contentPresenter);
727 | }
728 |
729 | protected virtual void PrepareEdit(ContentPresenter contentPresenter) { }
730 |
731 | #endregion
732 |
733 | #region Protected Override Methods
734 |
735 | protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
736 | {
737 | base.OnPropertyChanged(e);
738 |
739 | // Width Sync.
740 | if (e.Property == WidthProperty)
741 | SetWidth((double)e.OldValue, (double)e.NewValue);
742 | else if (e.Property == MinWidthProperty)
743 | SetSyncDataGridColumnMinWidth((double)e.NewValue);
744 | else if (e.Property == MaxWidthProperty)
745 | SetSyncDataGridColumnMaxWidth((double)e.NewValue);
746 | else if (e.Property == CanUserSortProperty)
747 | SetSyncDataGridColumnCanUserSort((bool)e.NewValue);
748 | else if (e.Property == SortDirectionProperty)
749 | SetSyncDataGridColumnSortDirection((ListSortDirection?)e.NewValue);
750 | else if (e.Property == SortMemberPathProperty)
751 | SetSyncDataGridColumnSortMemberPath((string)e.NewValue);
752 | else if (e.Property == DataContextProperty)
753 | SetBandHeaderDataContext(e.NewValue);
754 | }
755 |
756 | #endregion
757 | }
758 | }
759 |
--------------------------------------------------------------------------------
/src/KevinComponent/BandCollection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Collections.Specialized;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 |
10 | namespace KevinComponent
11 | {
12 | public sealed class BandCollection : ObservableCollection
13 | {
14 | internal BandCollection(FlexGrid flexGrid)
15 | {
16 | OwnerFlexGrid = flexGrid;
17 | }
18 |
19 | internal BandCollection(Band parentBand)
20 | {
21 | OwnerFlexGrid = parentBand.OwnerFlexGrid;
22 | ParentBand = parentBand;
23 | }
24 |
25 | #region Private Variables
26 |
27 | FlexGrid _ownerFlexGrid;
28 |
29 | #endregion
30 |
31 | #region Public Events
32 |
33 | public event DependencyPropertyChangedEventHandler VirtualBandItemsSourceChanged;
34 | public event NotifyCollectionChangedEventHandler VirtualBandItemsSourceCollectionChanged;
35 |
36 | #endregion
37 |
38 | #region Internal Properties
39 |
40 | internal FlexGrid OwnerFlexGrid
41 | {
42 | get => _ownerFlexGrid;
43 | set
44 | {
45 | if (_ownerFlexGrid != value)
46 | {
47 | _ownerFlexGrid = value;
48 |
49 | foreach (var band in this)
50 | band.OwnerFlexGrid = value;
51 | }
52 | }
53 | }
54 |
55 | #endregion
56 |
57 | #region Public Properties
58 |
59 | public Band ParentBand { get; }
60 |
61 | public int MaxDepth
62 | {
63 | get
64 | {
65 | int max = 0;
66 | foreach (var band in this)
67 | max = Math.Max(max, band.Depth);
68 |
69 | return max;
70 | }
71 | }
72 |
73 | public int BottomBandsCount
74 | {
75 | get
76 | {
77 | int sum = 0;
78 | foreach (var band in this)
79 | {
80 | if (band is VirtualBand vband)
81 | sum += vband.VirtualizedBands.Length;
82 | else if (!band.HasChildBands)
83 | sum++;
84 | else
85 | sum += band.Bands.BottomBandsCount;
86 | }
87 |
88 | return sum;
89 | }
90 | }
91 |
92 | public double TotalWidth
93 | {
94 | get
95 | {
96 | double total = 0;
97 | foreach (var band in this)
98 | {
99 | if (band is VirtualBand vband)
100 | total += vband.VirtualizedBands.Sum(t => t.Width);
101 | else if (band.Bands.Count > 0)
102 | total += band.Bands.TotalWidth;
103 | else
104 | total += band.Width;
105 | }
106 |
107 | return total;
108 | }
109 | }
110 |
111 | #endregion
112 |
113 | #region Private Methods
114 |
115 | private Band[] GetBottomBands(Band band)
116 | {
117 | if (band is VirtualBand vband)
118 | {
119 | if (vband.VirtualizedBands.Length > 0)
120 | return vband.VirtualizedBands;
121 | else
122 | return new Band[0];
123 | }
124 | else if (!band.HasChildBands)
125 | {
126 | return new Band[] { band };
127 | }
128 |
129 | var result = new List();
130 | foreach (var subBand in band.Bands)
131 | result.AddRange(GetBottomBands(subBand));
132 |
133 | return result.ToArray();
134 | }
135 |
136 | private void AttachEventHandlers(VirtualBand vband)
137 | {
138 | DetachEventHandlers(vband);
139 |
140 | vband.ItemsSourceChanged += OnVirtualBandItemsSourceChanged;
141 | vband.ItemsSourceCollectionChanged += OnVirtualBandItemsSourceCollectionChanged;
142 | }
143 |
144 | private void DetachEventHandlers(VirtualBand vband)
145 | {
146 | vband.ItemsSourceChanged -= OnVirtualBandItemsSourceChanged;
147 | vband.ItemsSourceCollectionChanged -= OnVirtualBandItemsSourceCollectionChanged;
148 | }
149 |
150 | #endregion
151 |
152 | #region Private EventHandlers
153 |
154 | private void OnVirtualBandItemsSourceChanged(object sender, DependencyPropertyChangedEventArgs e)
155 | {
156 | VirtualBandItemsSourceChanged?.Invoke(sender, e);
157 | }
158 |
159 | private void OnVirtualBandItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
160 | {
161 | VirtualBandItemsSourceCollectionChanged?.Invoke(sender, e);
162 | }
163 |
164 | #endregion
165 |
166 | #region Public Methods
167 |
168 | public Band[] GetBottomBands()
169 | {
170 | var result = new List();
171 | foreach (var band in this)
172 | result.AddRange(GetBottomBands(band));
173 |
174 | return result.ToArray();
175 | }
176 |
177 | #endregion
178 |
179 | #region Protected Override Methods
180 |
181 | protected override void InsertItem(int index, Band item)
182 | {
183 | if (item == null)
184 | throw new ArgumentNullException("item");
185 |
186 | if (ParentBand is VirtualBand)
187 | throw new InvalidOperationException("Cannot modify child bands when ParentBand is VirtualBand Type.");
188 |
189 | item.ParentBand = ParentBand;
190 | item.OwnerFlexGrid = OwnerFlexGrid;
191 |
192 | if (item is VirtualBand vband)
193 | AttachEventHandlers(vband);
194 |
195 | base.InsertItem(index, item);
196 | }
197 |
198 | protected override void ClearItems()
199 | {
200 | if (ParentBand is VirtualBand)
201 | throw new InvalidOperationException("Cannot modify child bands when ParentBand is VirtualBand Type.");
202 |
203 | foreach (var item in this)
204 | {
205 | if (item != null)
206 | {
207 | item.ParentBand = null;
208 | item.OwnerFlexGrid = null;
209 |
210 | if (item is VirtualBand vband)
211 | DetachEventHandlers(vband);
212 | }
213 | }
214 |
215 | base.ClearItems();
216 | }
217 |
218 | protected override void SetItem(int index, Band item)
219 | {
220 | if (item == null)
221 | throw new ArgumentNullException("item");
222 |
223 | if (ParentBand is VirtualBand)
224 | throw new InvalidOperationException("Cannot modify child bands when ParentBand is VirtualBand Type.");
225 |
226 | var oldItem = this[index];
227 | if (oldItem is VirtualBand oldVband)
228 | DetachEventHandlers(oldVband);
229 |
230 | oldItem.ParentBand = null;
231 | oldItem.OwnerFlexGrid = null;
232 |
233 | item.ParentBand = ParentBand;
234 | item.OwnerFlexGrid = OwnerFlexGrid;
235 |
236 | if (item is VirtualBand vband)
237 | AttachEventHandlers(vband);
238 |
239 | base.SetItem(index, item);
240 | }
241 |
242 | #endregion
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/src/KevinComponent/BandHeader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using System.Windows.Controls.Primitives;
11 | using System.Windows.Input;
12 | using System.Windows.Shell;
13 |
14 | namespace KevinComponent
15 | {
16 | [TemplatePart(Name = RightHeaderGripperPartName, Type = typeof(BandHeaderGripper))]
17 | public sealed class BandHeader : ButtonBase
18 | {
19 | static BandHeader()
20 | {
21 | DefaultStyleKeyProperty.OverrideMetadata(typeof(BandHeader), new FrameworkPropertyMetadata(typeof(BandHeader)));
22 | WidthProperty.OverrideMetadata(typeof(BandHeader), new FrameworkPropertyMetadata(double.NaN));
23 | }
24 |
25 | internal BandHeader(Band ownerBand)
26 | {
27 | OwnerBand = ownerBand;
28 | Width = ownerBand.Width;
29 | }
30 |
31 | #region Constants
32 |
33 | public const string RightHeaderGripperPartName = "PART_RightHeaderGripper";
34 |
35 | #endregion
36 |
37 | #region Dependency Properties
38 |
39 | private static readonly DependencyProperty SortDirectionProperty =
40 | DependencyProperty.Register(
41 | "SortDirection",
42 | typeof(ListSortDirection?),
43 | typeof(BandHeader),
44 | new FrameworkPropertyMetadata(null, OnSortDirectionChanged));
45 |
46 | private static void OnSortDirectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
47 | {
48 | if (d is FrameworkElement element)
49 | Utils.UpdateVisualState(element, true);
50 | }
51 |
52 | #endregion
53 |
54 | #region Private Variables
55 |
56 | BandHeaderGripper _rightGripper;
57 |
58 | #endregion
59 |
60 | #region Public Properties
61 |
62 | public Band OwnerBand { get; }
63 |
64 | public ListSortDirection? SortDirection
65 | {
66 | get => (ListSortDirection?)GetValue(SortDirectionProperty);
67 | set => SetValue(SortDirectionProperty, value);
68 | }
69 |
70 | #endregion
71 |
72 | #region Private Methods
73 |
74 | private void AttachRightGripperEventHandlers(BandHeaderGripper gripper)
75 | {
76 | if (gripper != null)
77 | {
78 | DetachRightGripperEventHandlers(gripper);
79 |
80 | gripper.DragStarted += OnRightGripperDragStarted;
81 | gripper.DragDelta += OnRightGripperDragDelta;
82 | gripper.DragCompleted += OnRightGripperDragCompleted;
83 | gripper.MouseDoubleClick += OnRightGripperDoubleClicked;
84 | }
85 | }
86 |
87 | private void DetachRightGripperEventHandlers(BandHeaderGripper gripper)
88 | {
89 | gripper.DragStarted -= OnRightGripperDragStarted;
90 | gripper.DragDelta -= OnRightGripperDragDelta;
91 | gripper.DragCompleted -= OnRightGripperDragCompleted;
92 | gripper.MouseDoubleClick -= OnRightGripperDoubleClicked;
93 | }
94 |
95 | #endregion
96 |
97 | #region Private EventHandlers
98 |
99 | private void OnRightGripperDragStarted(object sender, DragStartedEventArgs e) { }
100 |
101 | private void OnRightGripperDragDelta(object sender, DragDeltaEventArgs e)
102 | {
103 | if (OwnerBand != null)
104 | {
105 | var newValue = Math.Max(OwnerBand.MinWidth, ActualWidth + e.HorizontalChange);
106 | OwnerBand.Width = newValue;
107 | }
108 | }
109 |
110 | private void OnRightGripperDragCompleted(object sender, DragCompletedEventArgs e) { }
111 |
112 | private void OnRightGripperDoubleClicked(object sender, MouseButtonEventArgs e)
113 | {
114 | // TODO : Auto Fit Width To BandHeaders.
115 | // not developed yet.
116 | if (OwnerBand != null)
117 | {
118 | var rightBand = OwnerBand;
119 | while (rightBand.Bands.Count > 0)
120 | rightBand = rightBand.Bands[rightBand.Bands.Count - 1];
121 |
122 | //rightBand.Width = double.NaN;
123 | }
124 | }
125 |
126 | #endregion
127 |
128 | #region Public Override Methods
129 |
130 | public override void OnApplyTemplate()
131 | {
132 | base.OnApplyTemplate();
133 |
134 | _rightGripper = GetTemplateChild(RightHeaderGripperPartName) as BandHeaderGripper;
135 | if (_rightGripper != null)
136 | AttachRightGripperEventHandlers(_rightGripper);
137 | }
138 |
139 | #endregion
140 |
141 | #region Protected Override Methods
142 |
143 | protected override void OnClick()
144 | {
145 | base.OnClick();
146 | OwnerBand.PerformSort();
147 | }
148 |
149 | protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
150 | {
151 | base.OnPropertyChanged(e);
152 |
153 | if (e.Property == ActualWidthProperty)
154 | {
155 | var newValue = (double)e.NewValue;
156 | if (OwnerBand != null && OwnerBand.SyncDataGridColumn != null)
157 | OwnerBand.SyncDataGridColumn.Width = new DataGridLength(newValue);
158 | }
159 | }
160 |
161 | #endregion
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/src/KevinComponent/BandHeaderGripper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls.Primitives;
8 |
9 | namespace KevinComponent
10 | {
11 | public sealed class BandHeaderGripper : Thumb
12 | {
13 | static BandHeaderGripper()
14 | {
15 | DefaultStyleKeyProperty.OverrideMetadata(typeof(BandHeaderGripper), new FrameworkPropertyMetadata(typeof(BandHeaderGripper)));
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/KevinComponent/BandHeadersPresenter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Specialized;
4 | using System.Linq;
5 | using System.Runtime.CompilerServices;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 |
11 | namespace KevinComponent
12 | {
13 | [TemplatePart(Name = FrozenBandsWrapperPartName, Type = typeof(Grid))]
14 | [TemplatePart(Name = BandsScrollViewerPartName, Type = typeof(ScrollViewer))]
15 | [TemplatePart(Name = BandsWrapperPartName, Type = typeof(Grid))]
16 | public sealed class BandHeadersPresenter : Control
17 | {
18 | static BandHeadersPresenter()
19 | {
20 | DefaultStyleKeyProperty.OverrideMetadata(typeof(BandHeadersPresenter), new FrameworkPropertyMetadata(typeof(BandHeadersPresenter)));
21 | }
22 |
23 | public BandHeadersPresenter() { }
24 |
25 | #region Contants
26 |
27 | public const string FrozenBandsWrapperPartName = "PART_FrozenBandsWrapper";
28 | public const string BandsScrollViewerPartName = "PART_BandsScrollViewer";
29 | public const string BandsWrapperPartName = "PART_BandsWrapper";
30 |
31 | #endregion
32 |
33 | #region Dependency Properties
34 |
35 | public static readonly DependencyProperty BandsScrollHorizontalOffsetProperty =
36 | DependencyProperty.Register(
37 | "BandsScrollHorizontalOffset",
38 | typeof(double),
39 | typeof(BandHeadersPresenter),
40 | new FrameworkPropertyMetadata(0d, OnBandsScrollHorizontalOffsetPropertyChanged));
41 |
42 | private static void OnBandsScrollHorizontalOffsetPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
43 | {
44 | var presenter = d as BandHeadersPresenter;
45 | presenter?._bandsScrollViewer?.ScrollToHorizontalOffset((double)e.NewValue);
46 | }
47 |
48 | #endregion
49 |
50 | #region Private Variables
51 |
52 | Grid _frozenBandsWrapper;
53 | ScrollViewer _bandsScrollViewer;
54 | Grid _bandsWrapper;
55 | FlexGrid _ownerFlexGrid;
56 |
57 | #endregion
58 |
59 | #region Public Properties
60 |
61 | public FlexGrid OwnerFlexGrid
62 | {
63 | get
64 | {
65 | if (_ownerFlexGrid == null)
66 | _ownerFlexGrid = Utils.FindVisualParent(this);
67 |
68 | return _ownerFlexGrid;
69 | }
70 | }
71 |
72 | public double BandsScrollHorizontalOffset
73 | {
74 | get => (double)GetValue(BandsScrollHorizontalOffsetProperty);
75 | set => SetValue(BandsScrollHorizontalOffsetProperty, value);
76 | }
77 |
78 | #endregion
79 |
80 | #region Internal Methods
81 |
82 | internal void GenerateBandsElements()
83 | {
84 | if (OwnerFlexGrid == null || _frozenBandsWrapper == null || _bandsWrapper == null)
85 | return;
86 |
87 | var frozenBands = OwnerFlexGrid.FrozenBands;
88 | var bands = OwnerFlexGrid.Bands;
89 |
90 | _frozenBandsWrapper.Children.Clear();
91 | _bandsWrapper.Children.Clear();
92 |
93 | int maxRowCount = Math.Max(frozenBands.MaxDepth, bands.MaxDepth);
94 | ApplyColumnsAndRows(_frozenBandsWrapper, maxRowCount, frozenBands.BottomBandsCount);
95 | InsertBandHeaders(_frozenBandsWrapper, frozenBands, maxRowCount, 0, 0);
96 |
97 | ApplyColumnsAndRows(_bandsWrapper, maxRowCount, bands.BottomBandsCount);
98 | InsertBandHeaders(_bandsWrapper, bands, maxRowCount, 0, 0);
99 | }
100 |
101 | #endregion
102 |
103 | #region Private Methods
104 |
105 | private void ApplyColumnsAndRows(Grid wrapper, int rowCount, int colCount)
106 | {
107 | wrapper.ColumnDefinitions.Clear();
108 | wrapper.RowDefinitions.Clear();
109 |
110 | for (int i = 0; i < rowCount - 1; i++)
111 | wrapper.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
112 |
113 | wrapper.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
114 |
115 | for (int i = 0; i < colCount; i++)
116 | wrapper.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
117 |
118 | wrapper.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
119 | }
120 |
121 | private void InsertBandHeaders(Grid wrapper, IEnumerable bands, int maxRowCount, int row, int col)
122 | {
123 | int usedColumnCount = 0;
124 | foreach (var band in bands)
125 | {
126 | if (band is VirtualBand vband)
127 | {
128 | InsertBandHeaders(wrapper, vband.VirtualizedBands, maxRowCount, row, col + usedColumnCount);
129 | }
130 | else
131 | {
132 | InsertBandHeaders(wrapper, band.Bands, maxRowCount - 1, row + 1, col + usedColumnCount);
133 |
134 | var bandHeader = band.BandHeader;
135 | Grid.SetColumn(bandHeader, col + usedColumnCount);
136 | Grid.SetRow(bandHeader, row);
137 |
138 | if (band.Bands.Count > 0)
139 | {
140 | Grid.SetRowSpan(bandHeader, 1);
141 |
142 | var colSpan = band.Bands.BottomBandsCount;
143 | Grid.SetColumnSpan(bandHeader, colSpan);
144 |
145 | usedColumnCount += colSpan;
146 | }
147 | else
148 | {
149 | Grid.SetRowSpan(bandHeader, maxRowCount);
150 |
151 | usedColumnCount++;
152 | }
153 |
154 | wrapper.Children.Add(bandHeader);
155 | }
156 | }
157 | }
158 |
159 | #endregion
160 |
161 | #region Public Override Methods
162 |
163 | public override void OnApplyTemplate()
164 | {
165 | base.OnApplyTemplate();
166 |
167 | _frozenBandsWrapper = GetTemplateChild(FrozenBandsWrapperPartName) as Grid;
168 | _bandsScrollViewer = GetTemplateChild(BandsScrollViewerPartName) as ScrollViewer;
169 | _bandsWrapper = GetTemplateChild(BandsWrapperPartName) as Grid;
170 | }
171 |
172 | #endregion
173 |
174 | #region Protected Override Methods
175 |
176 | protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
177 | {
178 | base.OnPropertyChanged(e);
179 |
180 | if (e.Property == ActualHeightProperty)
181 | {
182 | //GenerateBandsElements();
183 | }
184 | }
185 |
186 | #endregion
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/src/KevinComponent/CheckBoxBand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using System.Windows.Data;
10 |
11 | namespace KevinComponent
12 | {
13 | public sealed class CheckBoxBand : Band
14 | {
15 | static CheckBoxBand()
16 | {
17 | DefaultStyleKeyProperty.OverrideMetadata(typeof(CheckBoxBand), new FrameworkPropertyMetadata(typeof(CheckBoxBand)));
18 | }
19 |
20 | public CheckBoxBand() { }
21 |
22 | internal CheckBoxBand(VirtualCheckBoxBand virtualBand) : base(virtualBand)
23 | {
24 | CheckBinding = virtualBand.CheckBinding;
25 | IsThreeState = virtualBand.IsThreeState;
26 | CheckBoxHorizontalAlignment = virtualBand.CheckBoxHorizontalAlignment;
27 |
28 | if (virtualBand.ContentBinding != null)
29 | BindingOperations.SetBinding(this, ContentProperty, virtualBand.ContentBinding);
30 | else
31 | Content = virtualBand.Content;
32 | }
33 |
34 | #region Dependency Properties
35 |
36 | public static readonly DependencyProperty IsThreeStateProperty =
37 | DependencyProperty.Register(
38 | "IsThreeState",
39 | typeof(bool),
40 | typeof(CheckBoxBand),
41 | new FrameworkPropertyMetadata(false));
42 |
43 | public static readonly DependencyProperty ContentProperty =
44 | DependencyProperty.Register(
45 | "Content",
46 | typeof(object),
47 | typeof(CheckBoxBand),
48 | new FrameworkPropertyMetadata(null));
49 |
50 | public static readonly DependencyProperty CheckBoxHorizontalAlignmentProperty =
51 | DependencyProperty.Register(
52 | "CheckBoxHorizontalAlignment",
53 | typeof(HorizontalAlignment),
54 | typeof(CheckBoxBand),
55 | new FrameworkPropertyMetadata(HorizontalAlignment.Center));
56 |
57 | #endregion
58 |
59 | #region Private Variables
60 |
61 | BindingBase _checkBinding;
62 |
63 | #endregion
64 |
65 | #region Public Properties
66 |
67 | public BindingBase CheckBinding
68 | {
69 | get => _checkBinding;
70 | set
71 | {
72 | if (_checkBinding != value)
73 | {
74 | _checkBinding = value;
75 | OwnerFlexGrid.RefreshCells(this);
76 | }
77 | }
78 | }
79 |
80 | public bool IsThreeState
81 | {
82 | get => (bool)GetValue(IsThreeStateProperty);
83 | set => SetValue(IsThreeStateProperty, value);
84 | }
85 |
86 | public object Content
87 | {
88 | get => GetValue(ContentProperty);
89 | set => SetValue(ContentProperty, value);
90 | }
91 |
92 | public HorizontalAlignment CheckBoxHorizontalAlignment
93 | {
94 | get => (HorizontalAlignment)GetValue(CheckBoxHorizontalAlignmentProperty);
95 | set => SetValue(CheckBoxHorizontalAlignmentProperty, value);
96 | }
97 |
98 | #endregion
99 |
100 | #region Private Methods
101 |
102 | private void SetCheckBoxIsChecked(CheckBox checkBox)
103 | {
104 | // Set IsChecked.
105 | if (CheckBinding != null)
106 | BindingOperations.SetBinding(checkBox, CheckBox.IsCheckedProperty, CheckBinding);
107 | else
108 | BindingOperations.ClearBinding(checkBox, CheckBox.IsCheckedProperty);
109 | }
110 |
111 | private void SetCheckBoxContent(CheckBox checkBox)
112 | {
113 | // Set Content.
114 | var extBinding = BindingOperations.GetBinding(this, ContentProperty);
115 | if (extBinding != null)
116 | {
117 | BindingOperations.SetBinding(checkBox, CheckBox.ContentProperty, extBinding);
118 | }
119 | else
120 | {
121 | var contentBinding = new Binding("Content") { Source = this };
122 | BindingOperations.SetBinding(checkBox, CheckBox.ContentProperty, contentBinding);
123 | }
124 | }
125 |
126 | private void SetCheckBoxIsThreeState(CheckBox checkBox)
127 | {
128 | var extBinding = BindingOperations.GetBinding(this, IsThreeStateProperty);
129 | if (extBinding != null)
130 | {
131 | BindingOperations.SetBinding(checkBox, CheckBox.IsThreeStateProperty, extBinding);
132 | }
133 | else
134 | {
135 | var isThreeStateBinding = new Binding("IsThreeState") { Source = this };
136 | BindingOperations.SetBinding(checkBox, CheckBox.IsThreeStateProperty, isThreeStateBinding);
137 | }
138 | }
139 |
140 | #endregion
141 |
142 | #region Protected Override Methods
143 |
144 | protected override void OnCellTemplateLoaded(ContentPresenter contentPresenter)
145 | {
146 | base.OnCellTemplateLoaded(contentPresenter);
147 |
148 | var checkBox = Utils.FindVisualChild(contentPresenter);
149 | if (checkBox != null)
150 | {
151 | SetCheckBoxIsThreeState(checkBox);
152 | SetCheckBoxIsChecked(checkBox);
153 | SetCheckBoxContent(checkBox);
154 |
155 | // Set Horizontal Alignment.
156 | checkBox.HorizontalAlignment = CheckBoxHorizontalAlignment;
157 | checkBox.VerticalAlignment = VerticalAlignment.Center;
158 | }
159 | }
160 |
161 | protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
162 | {
163 | base.OnPropertyChanged(e);
164 |
165 | if (e.Property == IsThreeStateProperty)
166 | OwnerFlexGrid.RefreshCells(this);
167 | else if (e.Property == ContentProperty)
168 | OwnerFlexGrid.RefreshCells(this);
169 | }
170 |
171 | #endregion
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/src/KevinComponent/ComboBoxBand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.ComponentModel.Design.Serialization;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using System.Windows.Data;
11 | using System.Windows.Documents;
12 | using KevinComponent.Assist;
13 |
14 | namespace KevinComponent
15 | {
16 | public sealed class ComboBoxBand : Band
17 | {
18 | static ComboBoxBand()
19 | {
20 | DefaultStyleKeyProperty.OverrideMetadata(typeof(ComboBoxBand), new FrameworkPropertyMetadata(typeof(ComboBoxBand)));
21 | }
22 |
23 | public ComboBoxBand() { }
24 |
25 | internal ComboBoxBand(VirtualComboBoxBand virtualBand) : base(virtualBand)
26 | {
27 | SelectedItemBinding = virtualBand.SelectedItemBinding;
28 | ItemsSource = virtualBand.ComboBoxItemsSource;
29 | IsEditable = virtualBand.IsEditable;
30 | IsTextSearchEnabled = virtualBand.IsTextSearchEnabled;
31 | IsTextSearchCaseSensitive = virtualBand.IsTextSearchCaseSensitive;
32 | MaxDropDownHeight = virtualBand.MaxDropDownHeight;
33 | ItemTemplate = virtualBand.ItemTemplate;
34 | }
35 |
36 | #region Dependency Properties
37 |
38 | public static readonly DependencyProperty ItemsSourceProperty =
39 | DependencyProperty.Register(
40 | "ItemsSource",
41 | typeof(IEnumerable),
42 | typeof(ComboBoxBand),
43 | new FrameworkPropertyMetadata(null));
44 |
45 | public static readonly DependencyProperty IsEditableProperty =
46 | DependencyProperty.Register(
47 | "IsEditable",
48 | typeof(bool),
49 | typeof(ComboBoxBand),
50 | new FrameworkPropertyMetadata(false));
51 |
52 | public static readonly DependencyProperty IsTextSearchEnabledProperty =
53 | DependencyProperty.Register(
54 | "IsTextSearchEnabled",
55 | typeof(bool),
56 | typeof(ComboBoxBand),
57 | new FrameworkPropertyMetadata(false));
58 |
59 | public static readonly DependencyProperty IsTextSearchCaseSensitiveProperty =
60 | DependencyProperty.Register(
61 | "IsTextSearchCaseSensitive",
62 | typeof(bool),
63 | typeof(ComboBoxBand),
64 | new FrameworkPropertyMetadata(false));
65 |
66 | public static readonly DependencyProperty MaxDropDownHeightProperty =
67 | DependencyProperty.Register(
68 | "MaxDropDownHeight",
69 | typeof(double),
70 | typeof(ComboBoxBand),
71 | new FrameworkPropertyMetadata(double.NaN));
72 |
73 | public static readonly DependencyProperty ItemTemplateProperty =
74 | DependencyProperty.Register(
75 | "ItemTemplate",
76 | typeof(DataTemplate),
77 | typeof(ComboBoxBand),
78 | new FrameworkPropertyMetadata(null, OnItemTemplateChanged));
79 |
80 | private static void OnItemTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
81 | {
82 | var band = (ComboBoxBand)d;
83 | band.OnItemTemplateChanged((DataTemplate)e.OldValue, (DataTemplate)e.NewValue);
84 | }
85 |
86 | #endregion
87 |
88 | #region Private Variables
89 |
90 | BindingBase _selectedItemBinding;
91 |
92 | #endregion
93 |
94 | #region Public Properties
95 |
96 | public BindingBase SelectedItemBinding
97 | {
98 | get => _selectedItemBinding;
99 | set
100 | {
101 | if (_selectedItemBinding != value)
102 | {
103 | _selectedItemBinding = value;
104 | OwnerFlexGrid?.RefreshCells(this);
105 | }
106 | }
107 | }
108 |
109 | public IEnumerable ItemsSource
110 | {
111 | get => (IEnumerable)GetValue(ItemsSourceProperty);
112 | set => SetValue(ItemsSourceProperty, value);
113 | }
114 |
115 | public bool IsEditable
116 | {
117 | get => (bool)GetValue(IsEditableProperty);
118 | set => SetValue(IsEditableProperty, value);
119 | }
120 |
121 | public bool IsTextSearchEnabled
122 | {
123 | get => (bool)GetValue(IsTextSearchEnabledProperty);
124 | set => SetValue(IsTextSearchEnabledProperty, value);
125 | }
126 |
127 | public bool IsTextSearchCaseSensitive
128 | {
129 | get => (bool)GetValue(IsTextSearchCaseSensitiveProperty);
130 | set => SetValue(IsTextSearchCaseSensitiveProperty, value);
131 | }
132 |
133 | public double MaxDropDownHeight
134 | {
135 | get => (double)GetValue(MaxDropDownHeightProperty);
136 | set => SetValue(MaxDropDownHeightProperty, value);
137 | }
138 |
139 | public DataTemplate ItemTemplate
140 | {
141 | get => (DataTemplate)GetValue(ItemTemplateProperty);
142 | set => SetValue(ItemTemplateProperty, value);
143 | }
144 |
145 | #endregion
146 |
147 | #region Private Methods
148 |
149 | private void OnItemTemplateChanged(DataTemplate oldValue, DataTemplate newValue)
150 | {
151 | OwnerFlexGrid?.RefreshCells(this);
152 | }
153 |
154 | private void SetComboBoxSelectedItem(ComboBox comboBox)
155 | {
156 | if (SelectedItemBinding != null)
157 | BindingOperations.SetBinding(comboBox, ComboBox.SelectedItemProperty, SelectedItemBinding);
158 | else
159 | BindingOperations.ClearBinding(comboBox, ComboBox.SelectedItemProperty);
160 | }
161 |
162 | private void SetComboBoxItemsSource(ComboBox comboBox)
163 | {
164 | var extBinding = BindingOperations.GetBinding(this, ItemsSourceProperty);
165 | if (extBinding != null)
166 | {
167 | BindingOperations.SetBinding(comboBox, ComboBox.ItemsSourceProperty, extBinding);
168 | }
169 | else
170 | {
171 | var itemsSourceBinding = new Binding("ItemsSource") { Source = this };
172 | BindingOperations.SetBinding(comboBox, ComboBox.ItemsSourceProperty, itemsSourceBinding);
173 | }
174 | }
175 |
176 | private void SetComboBoxIsEditable(ComboBox comboBox)
177 | {
178 | var extBinding = BindingOperations.GetBinding(this, IsEditableProperty);
179 | if (extBinding != null)
180 | {
181 | BindingOperations.SetBinding(comboBox, ComboBox.IsEditableProperty, extBinding);
182 | }
183 | else
184 | {
185 | var isEditableBinding = new Binding("IsEditable") { Source = this };
186 | BindingOperations.SetBinding(comboBox, ComboBox.IsEditableProperty, isEditableBinding);
187 | }
188 | }
189 |
190 | private void SetComboBoxIsTextSearchEnabled(ComboBox comboBox)
191 | {
192 | var extBinding = BindingOperations.GetBinding(this, IsTextSearchEnabledProperty);
193 | if (extBinding != null)
194 | {
195 | BindingOperations.SetBinding(comboBox, ComboBox.IsTextSearchEnabledProperty, extBinding);
196 | }
197 | else
198 | {
199 | var isTextSearchEnabledBinding = new Binding("IsTextSearchEnabled") { Source = this };
200 | BindingOperations.SetBinding(comboBox, ComboBox.IsTextSearchEnabledProperty, isTextSearchEnabledBinding);
201 | }
202 | }
203 |
204 | private void SetComboBoxIsTextSearchCaseSensitive(ComboBox comboBox)
205 | {
206 | var extBinding = BindingOperations.GetBinding(this, IsTextSearchCaseSensitiveProperty);
207 | if (extBinding != null)
208 | {
209 | BindingOperations.SetBinding(comboBox, ComboBox.IsTextSearchCaseSensitiveProperty, extBinding);
210 | }
211 | else
212 | {
213 | var isTextSearchCaseSensitiveBinding = new Binding("IsTextSearchCaseSensitive") { Source = this };
214 | BindingOperations.SetBinding(comboBox, ComboBox.IsTextSearchCaseSensitiveProperty, isTextSearchCaseSensitiveBinding);
215 | }
216 | }
217 |
218 | private void SetComboBoxMaxDropDownHeight(ComboBox comboBox)
219 | {
220 | var extBinding = BindingOperations.GetBinding(this, MaxDropDownHeightProperty);
221 | if (extBinding != null)
222 | {
223 | BindingOperations.SetBinding(comboBox, ComboBox.MaxDropDownHeightProperty, extBinding);
224 | }
225 | else
226 | {
227 | var maxDropDownHeightBinding = new Binding("MaxDropDownHeight") { Source = this };
228 | BindingOperations.SetBinding(comboBox, ComboBox.MaxDropDownHeightProperty, maxDropDownHeightBinding);
229 | }
230 | }
231 |
232 | private void SetComboBoxDataTemplate(ComboBox comboBox)
233 | {
234 | comboBox.ItemTemplate = ItemTemplate;
235 | }
236 |
237 | #endregion
238 |
239 | #region Protected Override Methods
240 |
241 | protected override void OnCellTemplateLoaded(ContentPresenter contentPresenter)
242 | {
243 | base.OnCellTemplateLoaded(contentPresenter);
244 |
245 | var comboBox = Utils.FindVisualChild(contentPresenter);
246 | if (comboBox != null)
247 | {
248 | SetComboBoxDataTemplate(comboBox);
249 | SetComboBoxItemsSource(comboBox);
250 | SetComboBoxSelectedItem(comboBox);
251 | SetComboBoxIsEditable(comboBox);
252 | SetComboBoxIsTextSearchEnabled(comboBox);
253 | SetComboBoxIsTextSearchCaseSensitive(comboBox);
254 | SetComboBoxMaxDropDownHeight(comboBox);
255 |
256 | comboBox.Width = double.NaN;
257 | comboBox.Height = double.NaN;
258 | }
259 | }
260 |
261 | #endregion
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/src/KevinComponent/FlexGrid.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Collections.Specialized;
5 | using System.Linq;
6 | using System.Reflection;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using System.Windows;
10 | using System.Windows.Controls;
11 | using System.Windows.Input;
12 |
13 | namespace KevinComponent
14 | {
15 | public sealed class FlexGrid : DataGrid
16 | {
17 | static FlexGrid()
18 | {
19 | DefaultStyleKeyProperty.OverrideMetadata(typeof(FlexGrid), new FrameworkPropertyMetadata(typeof(FlexGrid)));
20 | }
21 |
22 | public FlexGrid()
23 | {
24 | AutoGenerateColumns = false;
25 | ExtendHorizontalScrollToFrozenColumns = false;
26 | FrozenBands = new BandCollection(this);
27 | Bands = new BandCollection(this);
28 |
29 | AttachEventHandlers();
30 | AttachEventHandlers(FrozenBands);
31 | AttachEventHandlers(Bands);
32 | }
33 |
34 | #region Depndency Properties
35 |
36 | public static readonly DependencyProperty UnSelectAllByEscapeKeyProperty =
37 | DependencyProperty.Register(
38 | "UnSelectAllByEscapeKey",
39 | typeof(bool),
40 | typeof(FlexGrid));
41 |
42 | ///
43 | /// 열고정 할 경우, 아래 스크롤에 대한 확장 여부
44 | ///
45 | public static readonly DependencyProperty ExtendHorizontalScrollToFrozenColumnsProperty =
46 | DependencyProperty.Register(
47 | "ExtendHorizontalScrollToFrozenColumns",
48 | typeof(bool),
49 | typeof(FlexGrid));
50 |
51 | #endregion
52 |
53 | #region Public Events
54 |
55 | public event EventHandler Committed;
56 |
57 | #endregion
58 |
59 | #region Public Properties
60 |
61 | public BandCollection FrozenBands { get; }
62 |
63 | public BandCollection Bands { get; }
64 |
65 | public bool UnSelectAllByEscapeKey
66 | {
67 | get => (bool)GetValue(UnSelectAllByEscapeKeyProperty);
68 | set => SetValue(UnSelectAllByEscapeKeyProperty, value);
69 | }
70 |
71 | public bool IsEditing
72 | {
73 | get
74 | {
75 | var row = (DataGridRow)ItemContainerGenerator.ContainerFromItem(CurrentCell.Item);
76 | if (row == null)
77 | return false;
78 |
79 | return row.IsEditing;
80 | }
81 | }
82 |
83 | ///
84 | /// 열고정 할 경우, 아래 스크롤에 대한 확장 여부
85 | ///
86 | public bool ExtendHorizontalScrollToFrozenColumns
87 | {
88 | get => (bool)GetValue(ExtendHorizontalScrollToFrozenColumnsProperty);
89 | set => SetValue(ExtendHorizontalScrollToFrozenColumnsProperty, value);
90 | }
91 |
92 | #endregion
93 |
94 | #region Internal Properties
95 |
96 | internal BandHeadersPresenter BandHeadersPresenter { get; private set; }
97 |
98 | #endregion
99 |
100 | #region Internal Methods
101 |
102 | internal void SyncColumnsWithBands()
103 | {
104 | if (!IsLoaded)
105 | return;
106 |
107 | BandHeadersPresenter?.GenerateBandsElements();
108 |
109 | var frozenBottomBands = FrozenBands.GetBottomBands();
110 | var bottomBands = Bands.GetBottomBands();
111 |
112 | FrozenColumnCount = frozenBottomBands.Length;
113 |
114 | var totalBottomBands = new List();
115 | totalBottomBands.AddRange(frozenBottomBands);
116 | totalBottomBands.AddRange(bottomBands);
117 |
118 | var totalBottomBandsHash = totalBottomBands.ToHashSet();
119 |
120 | // Remove Columns Not Needed.
121 | foreach (var column in Columns.ToArray())
122 | {
123 | if (!(column is FlexGridColumn fgc) ||
124 | !totalBottomBandsHash.Contains(fgc.OwnerBand) ||
125 | fgc.OwnerBand.SyncDataGridColumn != fgc)
126 | Columns.Remove(column);
127 | }
128 |
129 | // Insert Columns.
130 | for (int i = 0; i < totalBottomBands.Count; i++)
131 | {
132 | var band = totalBottomBands[i];
133 | var columnIdx = band.SyncDataGridColumn.DisplayIndex;
134 |
135 | if (columnIdx != i)
136 | {
137 | if (columnIdx == -1)
138 | Columns.Insert(i, band.SyncDataGridColumn);
139 | else
140 | Columns.Move(columnIdx, i);
141 |
142 | if (band.SortDirection != null)
143 | PerformSort(band.SyncDataGridColumn);
144 | }
145 | }
146 | }
147 |
148 | #endregion
149 |
150 | #region Private Methods
151 |
152 | private void AttachEventHandlers()
153 | {
154 | Loaded += OnLoaded;
155 | }
156 |
157 | private void AttachEventHandlers(BandCollection bands)
158 | {
159 | DetachEventHandlers(bands);
160 | bands.CollectionChanged += OnBandsCollectionChanged;
161 |
162 | foreach (var band in bands)
163 | AttachEventHandlers(band.Bands);
164 | }
165 |
166 | private void DetachEventHandlers(BandCollection bands)
167 | {
168 | bands.CollectionChanged -= OnBandsCollectionChanged;
169 |
170 | foreach (var band in bands)
171 | DetachEventHandlers(band.Bands);
172 | }
173 |
174 | private void PrepareForSort(DataGridColumn sortColumn)
175 | {
176 | if (Keyboard.IsKeyDown(Key.LeftShift)
177 | || !Columns.Contains(sortColumn))
178 | return;
179 |
180 | if (Columns != null)
181 | {
182 | foreach (DataGridColumn column in Columns)
183 | {
184 | if (column != sortColumn)
185 | column.SortDirection = null;
186 | }
187 | }
188 | }
189 |
190 | #endregion
191 |
192 | #region Private EventHandlers
193 |
194 | private void OnLoaded(object sender, RoutedEventArgs e)
195 | {
196 | BandHeadersPresenter = Utils.FindVisualChild(this);
197 |
198 | Columns.Clear();
199 | SyncColumnsWithBands();
200 | }
201 |
202 | private void OnBandsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
203 | {
204 | var bands = sender as BandCollection;
205 | AttachEventHandlers(bands);
206 |
207 | SyncColumnsWithBands();
208 | }
209 |
210 | #endregion
211 |
212 | #region Public Methods
213 |
214 | public void RefreshCells(Band band)
215 | {
216 | if (band.SyncDataGridColumn == null || ItemsSource == null)
217 | return;
218 |
219 | foreach (var item in ItemsSource)
220 | {
221 | var row = (DataGridRow)ItemContainerGenerator.ContainerFromItem(item);
222 | if (row != null)
223 | band.SyncDataGridColumn.RefreshCellContent(row);
224 | }
225 | }
226 |
227 | public void PerformSort(DataGridColumn sortColumn)
228 | {
229 | if (!CanUserSortColumns)
230 | return;
231 |
232 | if (CommitEdit())
233 | {
234 | PrepareForSort(sortColumn);
235 |
236 | var args = new DataGridSortingEventArgs(sortColumn);
237 | OnSorting(args);
238 |
239 | if (Items.NeedsRefresh)
240 | {
241 | try
242 | {
243 | Items.Refresh();
244 | }
245 | catch
246 | {
247 | Items.SortDescriptions.Clear();
248 | }
249 | }
250 | }
251 | }
252 |
253 | #endregion
254 |
255 | #region Protected Override Methods
256 |
257 | protected override void OnExecutedCommitEdit(ExecutedRoutedEventArgs e)
258 | {
259 | base.OnExecutedCommitEdit(e);
260 |
261 | var propertyInfo = typeof(DataGrid).GetProperty("CurrentCellContainer", BindingFlags.NonPublic | BindingFlags.Instance);
262 | if (propertyInfo.GetValue(this) is DataGridCell cell)
263 | Committed?.Invoke(this, new FlexGridCommittedArgs(new DataGridCellInfo(cell)));
264 | else if (CurrentCell != null)
265 | Committed?.Invoke(this, new FlexGridCommittedArgs(CurrentCell));
266 | }
267 |
268 | protected override void OnPreviewKeyDown(KeyEventArgs e)
269 | {
270 | base.OnPreviewKeyDown(e);
271 |
272 | switch (e.Key)
273 | {
274 | case Key.Enter:
275 | {
276 | // 편집 중 Enter 클릭 시
277 | if (IsEditing)
278 | {
279 | // 적용하고 Enter 이벤트를 처리했다고 표기
280 | CommitEdit(DataGridEditingUnit.Row, true);
281 | e.Handled = true;
282 | }
283 | }
284 | break;
285 | }
286 | }
287 |
288 | protected override void OnKeyDown(KeyEventArgs e)
289 | {
290 | base.OnKeyDown(e);
291 |
292 | switch (e.Key)
293 | {
294 | case Key.Escape:
295 | {
296 | if (UnSelectAllByEscapeKey)
297 | UnselectAllCells();
298 | }
299 | break;
300 | }
301 | }
302 |
303 | #endregion
304 | }
305 | }
306 |
--------------------------------------------------------------------------------
/src/KevinComponent/FlexGridColumn.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Linq;
5 | using System.Security.AccessControl;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using System.Windows.Controls.Primitives;
11 | using System.Windows.Data;
12 |
13 | namespace KevinComponent
14 | {
15 | public sealed class FlexGridColumn : DataGridColumn
16 | {
17 | public FlexGridColumn(Band band)
18 | {
19 | OwnerBand = band;
20 | MinWidth = band.MinWidth;
21 | MaxWidth = band.MaxWidth;
22 | CanUserSort = band.CanUserSort;
23 | SortDirection = band.SortDirection;
24 | SortMemberPath = band.SortMemberPath;
25 | }
26 |
27 | public Band OwnerBand { get; }
28 |
29 | #region Internal Methods
30 |
31 | internal void RefreshCellContent(DataGridRow row)
32 | {
33 | if (DisplayIndex < 0)
34 | return;
35 |
36 | var presenter = Utils.FindVisualChild(row);
37 | if (presenter == null)
38 | return;
39 |
40 | var cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(DisplayIndex);
41 | RefreshCellContent(cell, string.Empty);
42 | }
43 |
44 | #endregion
45 |
46 | #region Protected Override Methods
47 |
48 | protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
49 | {
50 | return OwnerBand.GenerateElement(cell, true);
51 | }
52 |
53 | protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
54 | {
55 | return OwnerBand.GenerateElement(cell, false);
56 | }
57 |
58 | protected override void RefreshCellContent(FrameworkElement element, string propertyName)
59 | {
60 | if (element is DataGridCell cell)
61 | {
62 | var newContent = OwnerBand.GenerateElement(cell, cell.IsEditing);
63 | var oldContent = cell.Content as FrameworkElement;
64 | if (oldContent != null && oldContent != newContent)
65 | {
66 | var oldContentPresenter = oldContent as ContentPresenter;
67 | if (oldContentPresenter == null)
68 | oldContent.SetValue(FrameworkElement.DataContextProperty, null);
69 | else
70 | oldContentPresenter.Content = null;
71 | }
72 |
73 | cell.Content = newContent;
74 | }
75 | }
76 |
77 | protected override bool CommitCellEdit(FrameworkElement editingElement)
78 | {
79 | return base.CommitCellEdit(editingElement);
80 | }
81 |
82 | protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
83 | {
84 | base.OnPropertyChanged(e);
85 |
86 | if (e.Property == SortDirectionProperty)
87 | OwnerBand.SortDirection = (ListSortDirection?)e.NewValue;
88 | }
89 |
90 | #endregion
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/KevinComponent/FlexGridCommittedArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows.Controls;
7 |
8 | namespace KevinComponent
9 | {
10 | public class FlexGridCommittedArgs : EventArgs
11 | {
12 | public FlexGridCommittedArgs(DataGridCellInfo cellInfo)
13 | {
14 | CellInfo = cellInfo;
15 | }
16 |
17 | public DataGridCellInfo CellInfo { get; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/KevinComponent/IVirtualBandBindable.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace KevinComponent
8 | {
9 | public interface IVirtualBandBindable
10 | {
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/KevinComponent/KevinComponent.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net48-windows;netcoreapp3.1;net6.0-windows
5 | true
6 | bin
7 | obj
8 | Debug;Release;GitHub Release
9 | true
10 |
11 |
12 |
13 | KevinComponent net48-windows
14 | KevinComponent netCoreApp3.1
15 | KevinComponent net6.0-windows
16 |
17 |
18 |
19 | FlexGrid is a custom WPF DataGrid with convenient and useful features.
20 | KevinSung
21 | FlexGrid is a custom WPF DataGrid with convenient and useful features.
22 | When developing code using WPF, the Microsoft-supported DataGrid has limited functionality, such as nested and merged column headers and variable columns.
23 | However, with FlexGrid, your DataGrid development environment becomes significantly more convenient!
24 |
25 | Copyright © 2023 KevinSung
26 | https://github.com/soomin-kevin-sung/dotnet-flexgrid
27 | https://github.com/soomin-kevin-sung/dotnet-flexgrid
28 | GitHub
29 | DataGrid;FlexGrid;WPF;C#;Control;.Net;.NetCore;Free;OpenSource;Variable;Variable Columns;WPF UI;Grid;Table;Excel
30 | .net48 .netcoreapp3.1 supported.
31 | $(VersionPrefix)
32 | $(VersionPrefix)
33 | 1.0.6
34 | True
35 | LICENSE.md
36 | True
37 | False
38 | FlexGrid
39 | README.md
40 |
41 |
42 |
43 | True
44 |
45 |
46 |
47 | True
48 |
49 |
50 |
51 | True
52 |
53 |
54 |
55 | True
56 |
57 |
58 |
59 | True
60 |
61 |
62 |
63 | True
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | True
76 | \
77 |
78 |
79 | True
80 | \
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/src/KevinComponent/TemplateBand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows.Controls;
7 |
8 | namespace KevinComponent
9 | {
10 | public sealed class TemplateBand : Band
11 | {
12 | public TemplateBand() { }
13 |
14 | internal TemplateBand(VirtualTemplateBand virtualBand) : base(virtualBand)
15 | {
16 | CellTemplate = virtualBand.CellTemplate;
17 | CellEditingTemplate = virtualBand.CellEditingTemplate;
18 | }
19 |
20 | #region Protected Override Methods
21 |
22 | protected override void PrepareEdit(ContentPresenter contentPresenter)
23 | {
24 | base.PrepareEdit(contentPresenter);
25 |
26 | var textBox = Utils.FindVisualChild(contentPresenter);
27 | if (textBox != null)
28 | {
29 | textBox.Focus();
30 | textBox.SelectAll();
31 | }
32 | }
33 |
34 | #endregion
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/KevinComponent/TextBand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using KevinComponent.Assist;
12 |
13 | namespace KevinComponent
14 | {
15 | public sealed class TextBand : Band
16 | {
17 | static TextBand()
18 | {
19 | DefaultStyleKeyProperty.OverrideMetadata(typeof(TextBand), new FrameworkPropertyMetadata(typeof(TextBand)));
20 | }
21 |
22 | public TextBand() { }
23 |
24 | internal TextBand(VirtualBand virtualBand) : base(virtualBand) { }
25 |
26 | internal TextBand(VirtualTextBand virtualBand) : base(virtualBand)
27 | {
28 | TextBinding = virtualBand.TextBinding;
29 | IsNumeric = virtualBand.IsNumeric;
30 | AllowNegative = virtualBand.AllowNegative;
31 | }
32 |
33 | #region Dependency Properties
34 |
35 | public static readonly DependencyProperty IsNumericProperty =
36 | DependencyProperty.Register(
37 | "IsNumeric",
38 | typeof(bool),
39 | typeof(TextBand),
40 | new FrameworkPropertyMetadata(false));
41 |
42 | public static readonly DependencyProperty AllowNegativeProperty =
43 | DependencyProperty.Register(
44 | "AllowNegative",
45 | typeof(bool),
46 | typeof(TextBand),
47 | new FrameworkPropertyMetadata(true));
48 |
49 | public static readonly DependencyProperty TextAlignmentProperty =
50 | DependencyProperty.Register(
51 | "TextAlignment",
52 | typeof(TextAlignment),
53 | typeof(TextBand),
54 | new FrameworkPropertyMetadata(TextAlignment.Left));
55 |
56 | public static readonly DependencyProperty TextVerticalAlignmentProperty =
57 | DependencyProperty.Register(
58 | "TextVerticalAlignment",
59 | typeof(VerticalAlignment),
60 | typeof(TextBand),
61 | new FrameworkPropertyMetadata(VerticalAlignment.Center));
62 |
63 | public static readonly DependencyProperty OverrideTextAlignmentProperty =
64 | DependencyProperty.Register(
65 | "OverrideTextAlignment",
66 | typeof(bool),
67 | typeof(TextBand),
68 | new FrameworkPropertyMetadata(false));
69 |
70 | #endregion
71 |
72 | #region Private Variables
73 |
74 | BindingBase _textBinding;
75 | BindingBase _editingTextBinding;
76 |
77 | #endregion
78 |
79 | #region Public Properties
80 |
81 | public BindingBase TextBinding
82 | {
83 | get => _textBinding;
84 | set
85 | {
86 | if (_textBinding != value)
87 | {
88 | _textBinding = value;
89 | OwnerFlexGrid.RefreshCells(this);
90 | }
91 | }
92 | }
93 |
94 | public BindingBase EditingTextBinding
95 | {
96 | get => _editingTextBinding;
97 | set
98 | {
99 | if (_editingTextBinding != value)
100 | _editingTextBinding = value;
101 | }
102 | }
103 |
104 | public bool IsNumeric
105 | {
106 | get => (bool)GetValue(IsNumericProperty);
107 | set => SetValue(IsNumericProperty, value);
108 | }
109 |
110 | public bool AllowNegative
111 | {
112 | get => (bool)GetValue(AllowNegativeProperty);
113 | set => SetValue(AllowNegativeProperty, value);
114 | }
115 |
116 | public TextAlignment TextAlignment
117 | {
118 | get => (TextAlignment)GetValue(TextAlignmentProperty);
119 | set => SetValue(TextAlignmentProperty, value);
120 | }
121 |
122 | public VerticalAlignment TextVerticalAlignment
123 | {
124 | get => (VerticalAlignment)GetValue(TextVerticalAlignmentProperty);
125 | set => SetValue(TextVerticalAlignmentProperty, value);
126 | }
127 |
128 | public bool OverrideTextAlignment
129 | {
130 | get => (bool)GetValue(OverrideTextAlignmentProperty);
131 | set => SetValue(OverrideTextAlignmentProperty, value);
132 | }
133 |
134 | #endregion
135 |
136 | #region Private Methods
137 |
138 | private BindingBase GetTextBinding(bool isEditing)
139 | {
140 | if (isEditing && EditingTextBinding != null)
141 | return EditingTextBinding;
142 |
143 | return TextBinding;
144 | }
145 |
146 | private void SetTextBlockText(TextBlock textBlock)
147 | {
148 | var textBinding = GetTextBinding(false);
149 |
150 | // Set TextBinding.
151 | if (textBinding != null)
152 | BindingOperations.SetBinding(textBlock, TextBlock.TextProperty, textBinding);
153 | else
154 | BindingOperations.ClearBinding(textBlock, TextBlock.TextProperty);
155 | }
156 |
157 | private void SetTextBlockTextAlignment(TextBlock textBlock)
158 | {
159 | // Set TextAlignment.
160 | if (IsNumeric && !OverrideTextAlignment)
161 | textBlock.TextAlignment = TextAlignment.Right;
162 | else
163 | textBlock.TextAlignment = TextAlignment;
164 | }
165 |
166 | private void SetTextBlockTextVerticalAlignment(TextBlock textBlock)
167 | {
168 | // Set VerticalAlignment;
169 | textBlock.VerticalAlignment = TextVerticalAlignment;
170 | }
171 |
172 | private void SetTextBoxText(TextBox textBox)
173 | {
174 | var textBinding = GetTextBinding(true);
175 |
176 | // Set TextBinding.
177 | if (textBinding != null)
178 | BindingOperations.SetBinding(textBox, TextBox.TextProperty, textBinding);
179 | else
180 | BindingOperations.ClearBinding(textBox, TextBox.TextProperty);
181 | }
182 |
183 | private void SetTextBoxIsNumeric(TextBox textBox)
184 | {
185 | // Set IsNumeric.
186 | TextBoxAssist.SetIsNumericTextBox(textBox, IsNumeric);
187 | }
188 |
189 | private void SetTextBoxAllowNegative(TextBox textBox)
190 | {
191 | // Set AllowNegative.
192 | TextBoxAssist.SetAllowNegative(textBox, AllowNegative);
193 | }
194 |
195 | private void SetTextBoxTextAlignment(TextBox textBox)
196 | {
197 | // Set Text Alignment.
198 | if (!IsNumeric || (IsNumeric && OverrideTextAlignment))
199 | textBox.TextAlignment = TextAlignment;
200 | }
201 |
202 | private void SetTextBoxTextVerticalAlignment(TextBox textBox)
203 | {
204 | textBox.VerticalContentAlignment = TextVerticalAlignment;
205 | }
206 |
207 | #endregion
208 |
209 | #region Protected Override Methods
210 |
211 | protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
212 | {
213 | base.OnPropertyChanged(e);
214 |
215 | if (e.Property == IsNumericProperty
216 | || e.Property == AllowNegativeProperty
217 | || e.Property == TextAlignmentProperty
218 | || e.Property == TextVerticalAlignmentProperty
219 | || e.Property == OverrideTextAlignmentProperty)
220 | OwnerFlexGrid.RefreshCells(this);
221 | }
222 |
223 | protected override void OnCellTemplateLoaded(ContentPresenter contentPresenter)
224 | {
225 | base.OnCellTemplateLoaded(contentPresenter);
226 |
227 | var textBlock = Utils.FindVisualChild(contentPresenter);
228 | if (textBlock != null)
229 | {
230 | SetTextBlockText(textBlock);
231 | SetTextBlockTextAlignment(textBlock);
232 | SetTextBlockTextVerticalAlignment(textBlock);
233 |
234 | textBlock.HorizontalAlignment = HorizontalAlignment.Stretch;
235 | textBlock.Width = double.NaN;
236 | }
237 | }
238 |
239 | protected override void OnCellEditingTemplateLoaded(ContentPresenter contentPresenter)
240 | {
241 | base.OnCellEditingTemplateLoaded(contentPresenter);
242 |
243 | var textBox = Utils.FindVisualChild(contentPresenter);
244 | if (textBox != null)
245 | {
246 | SetTextBoxText(textBox);
247 | SetTextBoxIsNumeric(textBox);
248 | SetTextBoxAllowNegative(textBox);
249 | SetTextBoxTextAlignment(textBox);
250 | SetTextBoxTextVerticalAlignment(textBox);
251 |
252 | // Set Vertical Alignment.
253 | textBox.VerticalAlignment = VerticalAlignment.Stretch;
254 | textBox.HorizontalAlignment = HorizontalAlignment.Stretch;
255 |
256 | // Set Width & Height.
257 | textBox.Width = double.NaN;
258 | textBox.Height = double.NaN;
259 | }
260 | }
261 |
262 | protected override void PrepareEdit(ContentPresenter contentPresenter)
263 | {
264 | base.PrepareEdit(contentPresenter);
265 |
266 | var textBox = Utils.FindVisualChild(contentPresenter);
267 | if (textBox != null)
268 | {
269 | textBox.Focus();
270 | textBox.SelectAll();
271 | }
272 | }
273 |
274 | #endregion
275 | }
276 | }
--------------------------------------------------------------------------------
/src/KevinComponent/Themes/Generic.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/KevinComponent/Themes/Theme.Colors.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/KevinComponent/Themes/Theme.FlexGrid.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
24 |
25 |
34 |
35 |
51 |
52 |
66 |
67 |
162 |
163 |
192 |
193 |
297 |
--------------------------------------------------------------------------------
/src/KevinComponent/Utils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows.Media;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using System.Windows.Input;
10 | using System.Windows.Data;
11 | using System.Windows.Markup.Primitives;
12 | using System.Xml.Linq;
13 | using System.Reflection;
14 |
15 | namespace KevinComponent
16 | {
17 | public static class Utils
18 | {
19 | public static T FindVisualChild(DependencyObject d) where T : DependencyObject
20 | {
21 | int numOfChildren = VisualTreeHelper.GetChildrenCount(d);
22 |
23 | for (int i = 0; i < numOfChildren; i++)
24 | {
25 | DependencyObject child = VisualTreeHelper.GetChild(d, i);
26 |
27 | if (child is T t)
28 | {
29 | return t;
30 | }
31 | else
32 | {
33 | var childOfChild = FindVisualChild(child);
34 | if (childOfChild != null)
35 | return childOfChild;
36 | }
37 | }
38 |
39 | return null;
40 | }
41 |
42 | public static object FindVisualChild(DependencyObject d, string name)
43 | {
44 | int numOfChildren = VisualTreeHelper.GetChildrenCount(d);
45 |
46 | for (int i = 0; i < numOfChildren; i++)
47 | {
48 | var child = VisualTreeHelper.GetChild(d, i);
49 |
50 | if (child is FrameworkElement element && element.Name == name)
51 | {
52 | return child;
53 | }
54 | else
55 | {
56 | var childOfChild = FindVisualChild(child, name);
57 | if (childOfChild != null)
58 | return childOfChild;
59 | }
60 | }
61 |
62 | return null;
63 | }
64 |
65 | public static T FindVisualParent(DependencyObject child) where T : DependencyObject
66 | {
67 | var parentObject = GetParentObject(child);
68 | if (parentObject == null)
69 | return null;
70 |
71 | if (parentObject is T parent)
72 | return parent;
73 | else
74 | return FindVisualParent(parentObject);
75 | }
76 |
77 | private static DependencyObject GetParentObject(DependencyObject child)
78 | {
79 | if (child == null)
80 | return null;
81 |
82 | if (child is ContentElement contentElement)
83 | {
84 | DependencyObject parent = ContentOperations.GetParent(contentElement);
85 | if (parent != null)
86 | return parent;
87 |
88 | return contentElement is FrameworkContentElement fce ? fce.Parent : null;
89 | }
90 |
91 | if (child is FrameworkElement frameworkElement)
92 | {
93 | DependencyObject parent = frameworkElement.Parent;
94 | if (parent != null)
95 | return parent;
96 | }
97 |
98 | return VisualTreeHelper.GetParent(child);
99 | }
100 |
101 | public static void UpdateVisualState(FrameworkElement element, bool useTransitions)
102 | {
103 | if (Validation.GetHasError(element))
104 | {
105 | if (Keyboard.FocusedElement == element)
106 | VisualStateManager.GoToState(element, "InvalidFocused", useTransitions);
107 | else
108 | VisualStateManager.GoToState(element, "InvalidUnfocused", useTransitions);
109 | }
110 | else
111 | {
112 | VisualStateManager.GoToState(element, "Valid", useTransitions);
113 | }
114 | }
115 |
116 | public static (DependencyObject Element, DependencyProperty Property, T Binding)[] GetBindings(DependencyObject d)
117 | {
118 | var result = new List<(DependencyObject, DependencyProperty, T)>();
119 | var numOfChildren = VisualTreeHelper.GetChildrenCount(d);
120 |
121 | for (int i = 0; i < numOfChildren; i++)
122 | {
123 | var child = VisualTreeHelper.GetChild(d, i);
124 | result.AddRange(GetBindings(child));
125 | }
126 |
127 | var fields = d.GetType().GetFields(
128 | BindingFlags.Public
129 | | BindingFlags.FlattenHierarchy
130 | | BindingFlags.Static).Where(f => f.FieldType == typeof(DependencyProperty));
131 |
132 | foreach (var field in fields)
133 | {
134 | var dp = field.GetValue(null) as DependencyProperty;
135 | if (dp == null)
136 | continue;
137 |
138 | if (BindingOperations.IsDataBound(d, dp))
139 | {
140 | if (BindingOperations.GetBindingBase(d, dp) is T binding)
141 | result.Add((d, dp, binding));
142 | }
143 | }
144 |
145 | return result.ToArray();
146 | }
147 |
148 | public static bool GetIndexerValue(object target, object[] parameters, out object result)
149 | {
150 | result = null;
151 |
152 | var parameterTypes = parameters.Select(t => t?.GetType()).ToArray();
153 | var indexer = GetIndexProperty(target.GetType(), parameterTypes);
154 | if (indexer == null)
155 | return false;
156 |
157 | result = indexer.GetValue(target, parameters);
158 | return true;
159 | }
160 |
161 | private static PropertyInfo GetIndexProperty(Type targetType, Type[] parameterTypes)
162 | {
163 | var properties = from p in targetType.GetProperties()
164 | where p.Name == "Item"
165 | select p;
166 |
167 | foreach (var property in properties)
168 | {
169 | var requestParamTypes = property.GetIndexParameters().Select(t => t.ParameterType).ToArray();
170 | if (requestParamTypes.Length == parameterTypes.Length)
171 | {
172 | bool flag = true;
173 | for (int i = 0; i < requestParamTypes.Length; i++)
174 | {
175 | if (!requestParamTypes[i].IsAssignableFrom(parameterTypes[i]))
176 | {
177 | flag = false;
178 | break;
179 | }
180 | }
181 |
182 | // If there is no different type parameter.
183 | if (flag)
184 | return property;
185 | }
186 | }
187 |
188 | return null;
189 | }
190 |
191 | public static BindingBase CloneBinding(BindingBase bindingBase)
192 | {
193 | if (bindingBase is Binding binding)
194 | return CloneBinding(binding);
195 |
196 | if (bindingBase is MultiBinding multiBinding)
197 | return CloneBinding(multiBinding);
198 |
199 | if (bindingBase is PriorityBinding priorityBinding)
200 | return CloneBinding(priorityBinding);
201 |
202 | throw new NotSupportedException("Failed to clone binding");
203 | }
204 |
205 | private static Binding CloneBinding(Binding binding)
206 | {
207 | var result = new Binding
208 | {
209 | AsyncState = binding.AsyncState,
210 | BindingGroupName = binding.BindingGroupName,
211 | BindsDirectlyToSource = binding.BindsDirectlyToSource,
212 | Converter = binding.Converter,
213 | ConverterCulture = binding.ConverterCulture,
214 | ConverterParameter = binding.ConverterParameter,
215 | FallbackValue = binding.FallbackValue,
216 | IsAsync = binding.IsAsync,
217 | Mode = binding.Mode,
218 | NotifyOnSourceUpdated = binding.NotifyOnSourceUpdated,
219 | NotifyOnTargetUpdated = binding.NotifyOnTargetUpdated,
220 | NotifyOnValidationError = binding.NotifyOnValidationError,
221 | Path = binding.Path,
222 | StringFormat = binding.StringFormat,
223 | TargetNullValue = binding.TargetNullValue,
224 | UpdateSourceExceptionFilter = binding.UpdateSourceExceptionFilter,
225 | UpdateSourceTrigger = binding.UpdateSourceTrigger,
226 | ValidatesOnDataErrors = binding.ValidatesOnDataErrors,
227 | ValidatesOnExceptions = binding.ValidatesOnExceptions,
228 | XPath = binding.XPath,
229 | };
230 |
231 | if (binding.Source != null)
232 | result.Source = binding.Source;
233 | else if (binding.RelativeSource != null)
234 | result.RelativeSource = binding.RelativeSource;
235 | else if (binding.ElementName != null)
236 | result.ElementName = binding.ElementName;
237 |
238 | foreach (var validationRule in binding.ValidationRules)
239 | result.ValidationRules.Add(validationRule);
240 |
241 | return result;
242 | }
243 |
244 | private static MultiBinding CloneBinding(MultiBinding multiBinding)
245 | {
246 | var result = new MultiBinding
247 | {
248 | BindingGroupName = multiBinding.BindingGroupName,
249 | Converter = multiBinding.Converter,
250 | ConverterCulture = multiBinding.ConverterCulture,
251 | ConverterParameter = multiBinding.ConverterParameter,
252 | FallbackValue = multiBinding.FallbackValue,
253 | Mode = multiBinding.Mode,
254 | NotifyOnSourceUpdated = multiBinding.NotifyOnSourceUpdated,
255 | NotifyOnTargetUpdated = multiBinding.NotifyOnTargetUpdated,
256 | NotifyOnValidationError = multiBinding.NotifyOnValidationError,
257 | StringFormat = multiBinding.StringFormat,
258 | TargetNullValue = multiBinding.TargetNullValue,
259 | UpdateSourceExceptionFilter = multiBinding.UpdateSourceExceptionFilter,
260 | UpdateSourceTrigger = multiBinding.UpdateSourceTrigger,
261 | ValidatesOnDataErrors = multiBinding.ValidatesOnDataErrors,
262 | ValidatesOnExceptions = multiBinding.ValidatesOnDataErrors,
263 | };
264 |
265 | foreach (var validationRule in multiBinding.ValidationRules)
266 | result.ValidationRules.Add(validationRule);
267 |
268 | foreach (var childBinding in multiBinding.Bindings)
269 | result.Bindings.Add(CloneBinding(childBinding));
270 |
271 | return result;
272 | }
273 |
274 | private static PriorityBinding CloneBinding(PriorityBinding priorityBinding)
275 | {
276 | var result = new PriorityBinding
277 | {
278 | BindingGroupName = priorityBinding.BindingGroupName,
279 | FallbackValue = priorityBinding.FallbackValue,
280 | StringFormat = priorityBinding.StringFormat,
281 | TargetNullValue = priorityBinding.TargetNullValue,
282 | };
283 |
284 | foreach (var childBinding in priorityBinding.Bindings)
285 | {
286 | result.Bindings.Add(CloneBinding(childBinding));
287 | }
288 |
289 | return result;
290 | }
291 |
292 | public static bool IsEqualValue(object a, object b)
293 | {
294 | if (a == null && b == null)
295 | return true;
296 |
297 | if (a == null)
298 | return b.Equals(a);
299 |
300 | return a.Equals(b);
301 | }
302 | }
303 | }
304 |
--------------------------------------------------------------------------------
/src/KevinComponent/VirtualBand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Collections.ObjectModel;
5 | using System.Collections.Specialized;
6 | using System.ComponentModel;
7 | using System.Globalization;
8 | using System.Linq;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 | using System.Windows;
12 | using System.Windows.Controls.Primitives;
13 | using System.Windows.Data;
14 |
15 | namespace KevinComponent
16 | {
17 | public abstract class VirtualBand : Band
18 | {
19 | protected VirtualBand()
20 | {
21 | _virtualizedBands = new ObservableCollection();
22 | }
23 |
24 | #region Dependency Properties
25 |
26 | public static readonly DependencyProperty ItemsSourceProperty =
27 | DependencyProperty.Register(
28 | "ItemsSource",
29 | typeof(IEnumerable),
30 | typeof(VirtualBand),
31 | new FrameworkPropertyMetadata(null, OnItemsSourcePropertyChanged));
32 |
33 | private static void OnItemsSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
34 | {
35 | var band = (VirtualBand)d;
36 |
37 | if (e.OldValue is INotifyCollectionChanged oldCollection)
38 | band.DetachEventHandlers(oldCollection);
39 |
40 | if (e.NewValue is INotifyCollectionChanged newCollection)
41 | band.AttachEventHandlers(newCollection);
42 |
43 | if (band.ItemsSource != null)
44 | band.VirtualizeBands();
45 |
46 | band.ItemsSourceChanged?.Invoke(d, e);
47 | }
48 |
49 | #endregion
50 |
51 | #region Private Variables
52 |
53 | BindingBase _headerBinding;
54 | Band _dummyBand;
55 | ObservableCollection _virtualizedBands;
56 |
57 | #endregion
58 |
59 | #region Public Events
60 |
61 | public event DependencyPropertyChangedEventHandler ItemsSourceChanged;
62 | public event NotifyCollectionChangedEventHandler ItemsSourceCollectionChanged;
63 |
64 | #endregion
65 |
66 | #region Internal Properties
67 |
68 | internal Band DummyBand
69 | {
70 | get
71 | {
72 | if (_dummyBand == null)
73 | {
74 | _dummyBand = new TextBand(this);
75 | _dummyBand.IsReadOnly = true;
76 | }
77 |
78 | return _dummyBand;
79 | }
80 | }
81 |
82 | #endregion
83 |
84 | #region Public Properties
85 |
86 | public BindingBase HeaderBinding
87 | {
88 | get => _headerBinding;
89 | set
90 | {
91 | if (_headerBinding != value)
92 | {
93 | _headerBinding = value;
94 | SetHeaderBinding(value);
95 | }
96 | }
97 | }
98 |
99 | public IEnumerable ItemsSource
100 | {
101 | get => (IEnumerable)GetValue(ItemsSourceProperty);
102 | set => SetValue(ItemsSourceProperty, value);
103 | }
104 |
105 | public Band[] VirtualizedBands
106 | {
107 | get
108 | {
109 | if (_virtualizedBands.Count == 0)
110 | return new Band[] { DummyBand };
111 | else
112 | return _virtualizedBands.ToArray();
113 | }
114 | }
115 |
116 | #endregion
117 |
118 | #region Private Methods
119 |
120 | private void AttachEventHandlers(INotifyCollectionChanged collection)
121 | {
122 | DetachEventHandlers(collection);
123 | collection.CollectionChanged += OnItemsSourceCollectionChanged;
124 | }
125 |
126 | private void DetachEventHandlers(INotifyCollectionChanged collection)
127 | {
128 | collection.CollectionChanged -= OnItemsSourceCollectionChanged;
129 | }
130 |
131 | private void VirtualizeBands()
132 | {
133 | if (ItemsSource == null)
134 | {
135 | _virtualizedBands.Clear();
136 | return;
137 | }
138 |
139 | var items = ItemsSource.Cast().ToArray();
140 | SyncVirtualizedBandsCount(items);
141 | for (int i = 0; i < items.Length; i++)
142 | {
143 | if (!Utils.IsEqualValue(_virtualizedBands[i].DataContext, items[i]))
144 | {
145 | _virtualizedBands[i].DataContext = items[i];
146 | OwnerFlexGrid.RefreshCells(_virtualizedBands[i]);
147 | }
148 | }
149 |
150 | OwnerFlexGrid.SyncColumnsWithBands();
151 | }
152 |
153 | private void SyncVirtualizedBandsCount(object[] items)
154 | {
155 | while (_virtualizedBands.Count > items.Length)
156 | _virtualizedBands.RemoveAt(_virtualizedBands.Count - 1);
157 |
158 | while (_virtualizedBands.Count < items.Length)
159 | {
160 | var band = CreateVirtualizedBand();
161 | _virtualizedBands.Add(band);
162 | }
163 | }
164 |
165 | private void SetParentBand(Band newValue)
166 | {
167 | foreach (var band in VirtualizedBands)
168 | band.ParentBand = newValue;
169 | }
170 |
171 | private void SetHeader(object newValue)
172 | {
173 | if (HeaderBinding != null)
174 | return;
175 |
176 | foreach (var band in VirtualizedBands)
177 | {
178 | band.Header = newValue;
179 | }
180 | }
181 |
182 | private void SetHeaderBinding(BindingBase newValue)
183 | {
184 | foreach (var band in VirtualizedBands)
185 | {
186 | if (newValue != null)
187 | BindingOperations.SetBinding(band, HeaderProperty, newValue);
188 | else
189 | BindingOperations.ClearBinding(band, HeaderProperty);
190 | }
191 |
192 | if (newValue == null)
193 | SetHeader(Header);
194 | }
195 |
196 | private void SetCellTemplate(DataTemplate newValue)
197 | {
198 | foreach (var band in VirtualizedBands)
199 | band.CellTemplate = newValue;
200 | }
201 |
202 | private void SetCellEditingTemplate(DataTemplate newValue)
203 | {
204 | foreach (var band in VirtualizedBands)
205 | band.CellEditingTemplate = newValue;
206 | }
207 |
208 | private void SetCellStyle(Style newValue)
209 | {
210 | foreach (var band in VirtualizedBands)
211 | band.CellStyle = newValue;
212 | }
213 |
214 | private void SetWidth(double newValue)
215 | {
216 | foreach (var band in VirtualizedBands)
217 | band.Width = newValue;
218 | }
219 |
220 | private void SetMaxWidth(double newValue)
221 | {
222 | foreach (var band in VirtualizedBands)
223 | band.MaxWidth = newValue;
224 | }
225 |
226 | private void SetMinWidth(double newValue)
227 | {
228 | foreach (var band in VirtualizedBands)
229 | band.MinWidth = newValue;
230 | }
231 |
232 | private void SetHorizontalHeaderAlignment(HorizontalAlignment newValue)
233 | {
234 | foreach (var band in VirtualizedBands)
235 | band.HorizontalHeaderAlignment = newValue;
236 | }
237 |
238 | private void SetVerticalHeaderAlignment(VerticalAlignment newValue)
239 | {
240 | foreach (var band in VirtualizedBands)
241 | band.VerticalHeaderAlignment = newValue;
242 | }
243 |
244 | #endregion
245 |
246 | #region Private EventHandlers
247 |
248 | private void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
249 | {
250 | VirtualizeBands();
251 | ItemsSourceCollectionChanged?.Invoke(this, e);
252 | }
253 |
254 | #endregion
255 |
256 | #region Protected Abstract Methods
257 |
258 | protected abstract Band CreateVirtualizedBand();
259 |
260 | #endregion
261 |
262 | #region Protected Override Methods
263 |
264 | protected override void OnParentBandChanged(Band oldParent, Band newParent)
265 | {
266 | base.OnParentBandChanged(oldParent, newParent);
267 |
268 | SetParentBand(newParent);
269 | }
270 |
271 | protected override void OnOwnerFlexGridChanged(FlexGrid oldOwnerFlexGrid, FlexGrid newOwnerFlexGrid)
272 | {
273 | base.OnOwnerFlexGridChanged(oldOwnerFlexGrid, newOwnerFlexGrid);
274 |
275 | foreach (var vband in VirtualizedBands)
276 | vband.OwnerFlexGrid = newOwnerFlexGrid;
277 | }
278 |
279 | protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
280 | {
281 | base.OnPropertyChanged(e);
282 |
283 | if (e.Property == WidthProperty)
284 | SetWidth((double)e.NewValue);
285 | else if (e.Property == MaxWidthProperty)
286 | SetMaxWidth((double)e.NewValue);
287 | else if (e.Property == MinWidthProperty)
288 | SetMinWidth((double)e.NewValue);
289 | else if (e.Property == HeaderProperty)
290 | SetHeader(e.NewValue);
291 | else if (e.Property == CellTemplateProperty)
292 | SetCellTemplate((DataTemplate)e.NewValue);
293 | else if (e.Property == CellEditingTemplateProperty)
294 | SetCellEditingTemplate((DataTemplate)e.NewValue);
295 | else if (e.Property == CellStyleProperty)
296 | SetCellStyle((Style)e.NewValue);
297 | else if (e.Property == HorizontalHeaderAlignmentProperty)
298 | SetHorizontalHeaderAlignment((HorizontalAlignment)e.NewValue);
299 | else if (e.Property == VerticalHeaderAlignmentProperty)
300 | SetVerticalHeaderAlignment((VerticalAlignment)e.NewValue);
301 | }
302 |
303 | #endregion
304 | }
305 | }
306 |
--------------------------------------------------------------------------------
/src/KevinComponent/VirtualBandBinding.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Data;
8 |
9 | namespace KevinComponent
10 | {
11 | public sealed class VirtualBandBinding : Binding, IVirtualBandBindable
12 | {
13 | public VirtualBandBinding() : base("")
14 | {
15 | }
16 |
17 | public VirtualBandBinding(string path) : base(path)
18 | {
19 | }
20 |
21 | public VirtualBandBinding(string path, BindingMode mode, UpdateSourceTrigger updateSourceTrigger) : base(path)
22 | {
23 | Mode = mode;
24 | UpdateSourceTrigger = updateSourceTrigger;
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/KevinComponent/VirtualBandMultiBinding.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows.Data;
7 |
8 | namespace KevinComponent
9 | {
10 | public sealed class VirtualBandMultiBinding : MultiBinding, IVirtualBandBindable
11 | {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/KevinComponent/VirtualBandPriorityBinding.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows.Data;
7 |
8 | namespace KevinComponent
9 | {
10 | public sealed class VirtualBandPriorityBinding : PriorityBinding, IVirtualBandBindable
11 | {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/KevinComponent/VirtualCheckBoxBand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Data;
8 |
9 | namespace KevinComponent
10 | {
11 | public sealed class VirtualCheckBoxBand : VirtualBand
12 | {
13 | #region Dependency Properties
14 |
15 | public static readonly DependencyProperty IsThreeStateProperty =
16 | DependencyProperty.Register(
17 | "IsThreeState",
18 | typeof(bool),
19 | typeof(VirtualCheckBoxBand),
20 | new FrameworkPropertyMetadata(false));
21 |
22 | public static readonly DependencyProperty ContentProperty =
23 | DependencyProperty.Register(
24 | "Content",
25 | typeof(object),
26 | typeof(VirtualCheckBoxBand),
27 | new FrameworkPropertyMetadata(null));
28 |
29 | public static readonly DependencyProperty CheckBoxHorizontalAlignmentProperty =
30 | DependencyProperty.Register(
31 | "CheckBoxHorizontalAlignment",
32 | typeof(HorizontalAlignment),
33 | typeof(VirtualCheckBoxBand),
34 | new FrameworkPropertyMetadata(HorizontalAlignment.Center));
35 |
36 | #endregion
37 |
38 | #region Private Variables
39 |
40 | BindingBase _checkBinding;
41 | BindingBase _contentBinding;
42 |
43 | #endregion
44 |
45 | #region Public Properties
46 |
47 | public BindingBase CheckBinding
48 | {
49 | get => _checkBinding;
50 | set
51 | {
52 | if (_checkBinding != value)
53 | {
54 | _checkBinding = value;
55 | SetCheckBinding(value);
56 | }
57 | }
58 | }
59 |
60 | public bool IsThreeState
61 | {
62 | get => (bool)GetValue(IsThreeStateProperty);
63 | set => SetValue(IsThreeStateProperty, value);
64 | }
65 |
66 | public object Content
67 | {
68 | get => GetValue(ContentProperty);
69 | set => SetValue(ContentProperty, value);
70 | }
71 |
72 | public BindingBase ContentBinding
73 | {
74 | get => _contentBinding;
75 | set
76 | {
77 | if (_contentBinding != value)
78 | {
79 | _contentBinding = value;
80 | SetContentBinding(value);
81 | }
82 | }
83 | }
84 |
85 | public HorizontalAlignment CheckBoxHorizontalAlignment
86 | {
87 | get => (HorizontalAlignment)GetValue(CheckBoxHorizontalAlignmentProperty);
88 | set => SetValue(CheckBoxHorizontalAlignmentProperty, value);
89 | }
90 |
91 | #endregion
92 |
93 | #region Private Methods
94 |
95 | private void SetCheckBinding(BindingBase newValue)
96 | {
97 | foreach (var band in VirtualizedBands)
98 | {
99 | if (band is CheckBoxBand checkBoxBand)
100 | checkBoxBand.CheckBinding = newValue;
101 | }
102 | }
103 |
104 | private void SetIsThreeState(bool newValue)
105 | {
106 | foreach (var band in VirtualizedBands)
107 | {
108 | if (band is CheckBoxBand checkBoxBand)
109 | checkBoxBand.IsThreeState = newValue;
110 | }
111 | }
112 |
113 | private void SetContent(object newValue)
114 | {
115 | if (ContentBinding != null)
116 | return;
117 |
118 | foreach (var band in VirtualizedBands)
119 | {
120 | if (band is CheckBoxBand checkBoxBand)
121 | checkBoxBand.Content = newValue;
122 | }
123 | }
124 |
125 | private void SetContentBinding(BindingBase newValue)
126 | {
127 | foreach (var band in VirtualizedBands)
128 | {
129 | if (newValue != null)
130 | BindingOperations.SetBinding(band, CheckBoxBand.ContentProperty, newValue);
131 | else
132 | BindingOperations.ClearBinding(band, CheckBoxBand.ContentProperty);
133 | }
134 |
135 | if (newValue == null)
136 | SetContent(Content);
137 | }
138 |
139 | private void SetCheckBoxHorizontalAlignment(HorizontalAlignment newValue)
140 | {
141 | foreach (var band in VirtualizedBands)
142 | {
143 | if (band is CheckBoxBand checkBoxBand)
144 | checkBoxBand.CheckBoxHorizontalAlignment = newValue;
145 | }
146 | }
147 |
148 | #endregion
149 |
150 | #region Protected Override Methods
151 |
152 | protected override Band CreateVirtualizedBand()
153 | {
154 | return new CheckBoxBand(this);
155 | }
156 |
157 | protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
158 | {
159 | base.OnPropertyChanged(e);
160 |
161 | if (e.Property == IsThreeStateProperty)
162 | SetIsThreeState((bool)e.NewValue);
163 | else if (e.Property == ContentProperty)
164 | SetContent(e.NewValue);
165 | else if (e.Property == CheckBoxHorizontalAlignmentProperty)
166 | SetCheckBoxHorizontalAlignment((HorizontalAlignment)e.NewValue);
167 | }
168 |
169 | #endregion
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/src/KevinComponent/VirtualComboBoxBand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using System.Windows.Data;
10 |
11 | namespace KevinComponent
12 | {
13 | public sealed class VirtualComboBoxBand : VirtualBand
14 | {
15 | #region Dependency Properties
16 |
17 | public static readonly DependencyProperty ComboBoxItemsSourceProperty =
18 | DependencyProperty.Register(
19 | "ComboBoxItemsSource",
20 | typeof(IEnumerable),
21 | typeof(VirtualComboBoxBand),
22 | new FrameworkPropertyMetadata(null));
23 |
24 | public static readonly DependencyProperty IsEditableProperty =
25 | DependencyProperty.Register(
26 | "IsEditable",
27 | typeof(bool),
28 | typeof(VirtualComboBoxBand),
29 | new FrameworkPropertyMetadata(false));
30 |
31 | public static readonly DependencyProperty IsTextSearchEnabledProperty =
32 | DependencyProperty.Register(
33 | "IsTextSearchEnabled",
34 | typeof(bool),
35 | typeof(VirtualComboBoxBand),
36 | new FrameworkPropertyMetadata(false));
37 |
38 | public static readonly DependencyProperty IsTextSearchCaseSensitiveProperty =
39 | DependencyProperty.Register(
40 | "IsTextSearchCaseSensitive",
41 | typeof(bool),
42 | typeof(VirtualComboBoxBand),
43 | new FrameworkPropertyMetadata(false));
44 |
45 | public static readonly DependencyProperty MaxDropDownHeightProperty =
46 | DependencyProperty.Register(
47 | "MaxDropDownHeight",
48 | typeof(double),
49 | typeof(VirtualComboBoxBand),
50 | new FrameworkPropertyMetadata(double.NaN));
51 |
52 | public static readonly DependencyProperty ItemTemplateProperty =
53 | DependencyProperty.Register(
54 | "ItemTemplate",
55 | typeof(DataTemplate),
56 | typeof(VirtualComboBoxBand),
57 | new FrameworkPropertyMetadata(null));
58 |
59 | #endregion
60 |
61 | #region Private Variables
62 |
63 | BindingBase _selectedItemBinding;
64 |
65 | #endregion
66 |
67 | #region Public Properties
68 |
69 | public BindingBase SelectedItemBinding
70 | {
71 | get => _selectedItemBinding;
72 | set
73 | {
74 | if (_selectedItemBinding != value)
75 | {
76 | _selectedItemBinding = value;
77 | SetSelectedItemBinding(value);
78 | }
79 | }
80 | }
81 |
82 | public IEnumerable ComboBoxItemsSource
83 | {
84 | get => (IEnumerable)GetValue(ComboBoxItemsSourceProperty);
85 | set => SetValue(ComboBoxItemsSourceProperty, value);
86 | }
87 |
88 | public bool IsEditable
89 | {
90 | get => (bool)GetValue(IsEditableProperty);
91 | set => SetValue(IsEditableProperty, value);
92 | }
93 |
94 | public bool IsTextSearchEnabled
95 | {
96 | get => (bool)GetValue(IsTextSearchEnabledProperty);
97 | set => SetValue(IsTextSearchEnabledProperty, value);
98 | }
99 |
100 | public bool IsTextSearchCaseSensitive
101 | {
102 | get => (bool)GetValue(IsTextSearchCaseSensitiveProperty);
103 | set => SetValue(IsTextSearchCaseSensitiveProperty, value);
104 | }
105 |
106 | public double MaxDropDownHeight
107 | {
108 | get => (double)GetValue(MaxDropDownHeightProperty);
109 | set => SetValue(MaxDropDownHeightProperty, value);
110 | }
111 |
112 | public DataTemplate ItemTemplate
113 | {
114 | get => (DataTemplate)GetValue(ItemTemplateProperty);
115 | set => SetValue(ItemTemplateProperty, value);
116 | }
117 |
118 | #endregion
119 |
120 | #region Private Methods
121 |
122 | private void SetSelectedItemBinding(BindingBase newValue)
123 | {
124 | foreach (var band in VirtualizedBands)
125 | {
126 | if (band is ComboBoxBand comboBoxBand)
127 | comboBoxBand.SelectedItemBinding = newValue;
128 | }
129 | }
130 |
131 | private void SetItemsSource(IEnumerable newValue)
132 | {
133 | foreach (var band in VirtualizedBands)
134 | {
135 | if (band is ComboBoxBand comboBoxBand)
136 | comboBoxBand.ItemsSource = newValue;
137 | }
138 | }
139 |
140 | private void SetIsEditable(bool newValue)
141 | {
142 | foreach (var band in VirtualizedBands)
143 | {
144 | if (band is ComboBoxBand comboBoxBand)
145 | comboBoxBand.IsEditable = newValue;
146 | }
147 | }
148 |
149 | private void SetIsTextSearchEnabled(bool newValue)
150 | {
151 | foreach (ComboBoxBand band in VirtualizedBands)
152 | band.IsTextSearchEnabled = newValue;
153 | }
154 |
155 | private void SetIsTextSearchCaseSensitive(bool newValue)
156 | {
157 | foreach (ComboBoxBand band in VirtualizedBands)
158 | band.IsTextSearchCaseSensitive = newValue;
159 | }
160 |
161 | private void SetMaxDropDownHeight(double newValue)
162 | {
163 | foreach (ComboBoxBand band in VirtualizedBands)
164 | band.MaxDropDownHeight = newValue;
165 | }
166 |
167 | private void SetItemTemplate(DataTemplate newValue)
168 | {
169 | foreach (ComboBoxBand band in VirtualizedBands)
170 | band.ItemTemplate = newValue;
171 | }
172 |
173 | #endregion
174 |
175 | #region Protected Override Methods
176 |
177 | protected override Band CreateVirtualizedBand()
178 | {
179 | return new ComboBoxBand(this);
180 | }
181 |
182 | protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
183 | {
184 | base.OnPropertyChanged(e);
185 |
186 | if (e.Property == ComboBoxItemsSourceProperty)
187 | SetItemsSource((IEnumerable)e.NewValue);
188 | else if (e.Property == IsEditableProperty)
189 | SetIsEditable((bool)e.NewValue);
190 | else if (e.Property == IsTextSearchEnabledProperty)
191 | SetIsTextSearchEnabled((bool)e.NewValue);
192 | else if (e.Property == IsTextSearchCaseSensitiveProperty)
193 | SetIsTextSearchCaseSensitive((bool)e.NewValue);
194 | else if (e.Property == MaxDropDownHeightProperty)
195 | SetMaxDropDownHeight((double)e.NewValue);
196 | else if (e.Property == ItemTemplateProperty)
197 | SetItemTemplate((DataTemplate)e.NewValue);
198 | }
199 |
200 | #endregion
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/src/KevinComponent/VirtualTemplateBand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 |
8 | namespace KevinComponent
9 | {
10 | public sealed class VirtualTemplateBand : VirtualBand
11 | {
12 | #region Private Methods
13 |
14 | private void SetCellTemplate(DataTemplate newValue)
15 | {
16 | foreach (var band in VirtualizedBands)
17 | {
18 | if (band is TemplateBand templateBand)
19 | templateBand.CellTemplate = newValue;
20 | }
21 | }
22 |
23 | private void SetCellEditingTemplate(DataTemplate newValue)
24 | {
25 | foreach (var band in VirtualizedBands)
26 | {
27 | if (band is TemplateBand templateBand)
28 | templateBand.CellEditingTemplate = newValue;
29 | }
30 | }
31 |
32 | #endregion
33 |
34 | #region Protected Override Methods
35 |
36 | protected override Band CreateVirtualizedBand()
37 | {
38 | return new TemplateBand(this);
39 | }
40 |
41 | protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
42 | {
43 | base.OnPropertyChanged(e);
44 |
45 | if (e.Property == CellTemplateProperty)
46 | SetCellTemplate((DataTemplate)e.NewValue);
47 | else if (e.Property == CellEditingTemplateProperty)
48 | SetCellEditingTemplate((DataTemplate)e.NewValue);
49 | }
50 |
51 | #endregion
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/KevinComponent/VirtualTextBand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Data;
8 |
9 | namespace KevinComponent
10 | {
11 | public sealed class VirtualTextBand : VirtualBand
12 | {
13 | #region Dependency Properties
14 |
15 | public static readonly DependencyProperty IsNumericProperty =
16 | DependencyProperty.Register(
17 | "IsNumeric",
18 | typeof(bool),
19 | typeof(VirtualTextBand),
20 | new FrameworkPropertyMetadata(false));
21 |
22 | public static readonly DependencyProperty AllowNegativeProperty =
23 | DependencyProperty.Register(
24 | "AllowNegative",
25 | typeof(bool),
26 | typeof(VirtualTextBand),
27 | new FrameworkPropertyMetadata(false));
28 |
29 | public static readonly DependencyProperty TextAlignmentProperty =
30 | DependencyProperty.Register(
31 | "TextAlignment",
32 | typeof(TextAlignment),
33 | typeof(VirtualTextBand),
34 | new FrameworkPropertyMetadata(TextAlignment.Left));
35 |
36 | public static readonly DependencyProperty TextVerticalAlignmentProperty =
37 | DependencyProperty.Register(
38 | "TextVerticalAlignment",
39 | typeof(VerticalAlignment),
40 | typeof(VirtualTextBand),
41 | new FrameworkPropertyMetadata(VerticalAlignment.Center));
42 |
43 | public static readonly DependencyProperty OverrideTextAlignmentProperty =
44 | DependencyProperty.Register(
45 | "OverrideTextAlignment",
46 | typeof(bool),
47 | typeof(VirtualTextBand),
48 | new FrameworkPropertyMetadata(false));
49 |
50 | #endregion
51 |
52 | #region Private Variables
53 |
54 | BindingBase _textBinding;
55 | BindingBase _editingTextBinding;
56 |
57 | #endregion
58 |
59 | #region Public Properties
60 |
61 | public BindingBase TextBinding
62 | {
63 | get => _textBinding;
64 | set
65 | {
66 | if (_textBinding != value)
67 | {
68 | _textBinding = value;
69 | SetTextBinding(value);
70 | }
71 | }
72 | }
73 |
74 | public BindingBase EditingTextBinding
75 | {
76 | get => _editingTextBinding;
77 | set
78 | {
79 | if (_editingTextBinding != value)
80 | {
81 | _editingTextBinding = value;
82 | SetEditingTextBinding(value);
83 | }
84 | }
85 | }
86 |
87 | public bool IsNumeric
88 | {
89 | get => (bool)GetValue(IsNumericProperty);
90 | set => SetValue(IsNumericProperty, value);
91 | }
92 |
93 | public bool AllowNegative
94 | {
95 | get => (bool)GetValue(AllowNegativeProperty);
96 | set => SetValue(AllowNegativeProperty, value);
97 | }
98 |
99 | public TextAlignment TextAlignment
100 | {
101 | get => (TextAlignment)GetValue(TextAlignmentProperty);
102 | set => SetValue(TextAlignmentProperty, value);
103 | }
104 |
105 | public VerticalAlignment TextVerticalAlignment
106 | {
107 | get => (VerticalAlignment)GetValue(TextVerticalAlignmentProperty);
108 | set => SetValue(TextVerticalAlignmentProperty, value);
109 | }
110 |
111 | public bool OverrideTextAlignment
112 | {
113 | get => (bool)GetValue(OverrideTextAlignmentProperty);
114 | set => SetValue(OverrideTextAlignmentProperty, value);
115 | }
116 |
117 | #endregion
118 |
119 | #region Private Methods
120 |
121 | private void SetTextBinding(BindingBase newValue)
122 | {
123 | foreach (var band in VirtualizedBands)
124 | {
125 | if (band is TextBand textBand)
126 | textBand.TextBinding = newValue;
127 | }
128 | }
129 |
130 | private void SetEditingTextBinding(BindingBase newValue)
131 | {
132 | foreach (var band in VirtualizedBands)
133 | {
134 | if (band is TextBand textBand)
135 | textBand.EditingTextBinding = newValue;
136 | }
137 | }
138 |
139 | private void SetIsNumeric(bool newValue)
140 | {
141 | foreach (var band in VirtualizedBands)
142 | {
143 | if (band is TextBand textBand)
144 | textBand.IsNumeric = newValue;
145 | }
146 | }
147 |
148 | private void SetAllowNegative(bool newValue)
149 | {
150 | foreach (var band in VirtualizedBands)
151 | {
152 | if (band is TextBand textBand)
153 | textBand.AllowNegative = newValue;
154 | }
155 | }
156 |
157 | private void SetTextAlignment(TextAlignment newValue)
158 | {
159 | foreach (var band in VirtualizedBands)
160 | {
161 | if (band is TextBand textBand)
162 | textBand.TextAlignment = newValue;
163 | }
164 | }
165 |
166 | private void SetTextVerticalAlignment(VerticalAlignment newValue)
167 | {
168 | foreach (var band in VirtualizedBands)
169 | {
170 | if (band is TextBand textBand)
171 | textBand.TextVerticalAlignment = newValue;
172 | }
173 | }
174 |
175 | private void SetOverrideTextAlignment(bool newValue)
176 | {
177 | foreach (var band in VirtualizedBands)
178 | {
179 | if (band is TextBand textBand)
180 | textBand.OverrideTextAlignment = newValue;
181 | }
182 | }
183 |
184 | #endregion
185 |
186 | #region Protected Override Methods
187 |
188 | protected override Band CreateVirtualizedBand()
189 | {
190 | return new TextBand(this);
191 | }
192 |
193 | protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
194 | {
195 | base.OnPropertyChanged(e);
196 |
197 | if (e.Property == IsNumericProperty)
198 | SetIsNumeric((bool)e.NewValue);
199 | else if (e.Property == AllowNegativeProperty)
200 | SetAllowNegative((bool)e.NewValue);
201 | else if (e.Property == TextAlignmentProperty)
202 | SetTextAlignment((TextAlignment)e.NewValue);
203 | else if (e.Property == TextVerticalAlignmentProperty)
204 | SetTextVerticalAlignment((VerticalAlignment)e.NewValue);
205 | else if (e.Property == OverrideTextAlignmentProperty)
206 | SetOverrideTextAlignment((bool)e.NewValue);
207 | }
208 |
209 | #endregion
210 | }
211 | }
212 |
--------------------------------------------------------------------------------