Compare commits

..

1335 Commits

Author SHA1 Message Date
Dimitrij
455d897b9f Merge pull request #3146 from mRemoteNG/renovate/microsoft.web.webview2-1.x
Update dependency Microsoft.Web.WebView2 to 1.0.3800.47
2026-02-16 12:13:11 +00:00
renovate[bot]
3d4d1136f0 Update dependency Microsoft.Web.WebView2 to 1.0.3800.47 2026-02-16 10:47:26 +00:00
Dimitrij
598136f089 Update MSBuild version to '18.0' 2026-02-15 14:45:44 +00:00
Dimitrij
865bddfb04 Merge pull request #3145 from mRemoteNG/renovate/coverlet.collector-8.x
Update dependency coverlet.collector to v8
2026-02-15 14:40:49 +00:00
Dimitrij
3b2bc028cc Merge pull request #3144 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.75
2026-02-15 14:40:29 +00:00
Dimitrij
71c746f801 Update MSBuild version range in workflow
vsVersion: 18.0
2026-02-15 14:38:43 +00:00
renovate[bot]
079814751e Update dependency coverlet.collector to v8 2026-02-14 23:51:10 +00:00
Dimitrij
1de9ce6ef5 Merge pull request #3143 from jcefoli/v1.78.2-dev
AlwaysShowPanelTabs Setting Not Respected at Application…
2026-02-14 23:50:44 +00:00
renovate[bot]
01df0e295e Update dependency AWSSDK.EC2 to 4.0.75 2026-02-14 00:58:14 +00:00
Joe Cefoli
2631926eda Fixes #3142: AlwaysShowPanelTabs Setting Not Respected at Application Launch 2026-02-13 13:54:13 -05:00
Dimitrij
db1496d4a2 Merge pull request #3140 from mRemoteNG/renovate/actions-setup-dotnet-5.x
Update actions/setup-dotnet action to v5
2026-02-13 10:29:59 +00:00
renovate[bot]
a8b12c9ba1 Update actions/setup-dotnet action to v5 2026-02-12 23:12:53 +00:00
Dimitrij
a2b408e537 Update Build_mR-NB.yml
add .net 10 install
2026-02-12 23:12:17 +00:00
Dimitrij
a871624ab7 Update MSBuild version range in workflow
set ver for lower
2026-02-12 23:03:10 +00:00
Dimitrij
e40a800bc4 Update Build_mR-NB.yml
fix build tools ver
2026-02-12 22:58:46 +00:00
Dimitrij
48cb1ce770 Merge pull request #3139 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.74
2026-02-12 22:56:04 +00:00
Kvarkas
6733d758aa upd action 2026-02-12 22:55:25 +00:00
Dimitrij
f78b9bf51c Refactor self-contained build process in workflow
fix msbuild
2026-02-12 21:53:24 +00:00
renovate[bot]
213ea6a4d3 Update dependency AWSSDK.EC2 to 4.0.74 2026-02-12 21:17:33 +00:00
Kvarkas
ac3d7e6366 added new option - build Self-Contained, this allow to run mR without installing .NET 2026-02-12 21:16:31 +00:00
Dimitrij
9fae2e066e Merge pull request #3138 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2026-02-12 10:30:06 +00:00
renovate[bot]
cf66e84d31 Update aws-sdk-net monorepo 2026-02-11 22:12:21 +00:00
Dimitrij
f7326aff62 Merge pull request #3137 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.Core to 4.0.3.13
2026-02-11 09:22:09 +00:00
Kvarkas
d4bca6b03d small fixes 2026-02-10 22:03:02 +00:00
renovate[bot]
1d86015f9d Update dependency AWSSDK.Core to 4.0.3.13 2026-02-10 21:19:54 +00:00
Dimitrij
5efcc653eb Merge pull request #3038 from mRemoteNG/copilot/fix-command-injection-vulnerability
Fix command injection vulnerabilities in Process.Start calls
2026-02-10 21:18:59 +00:00
Kvarkas
333588e101 Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev 2026-02-10 21:18:29 +00:00
Kvarkas
4046681fc5 remove dependency what are now included with .Net 10
small fixes
2026-02-10 21:18:17 +00:00
Dimitrij
91c7df22b2 Update mRemoteNGTests/Connection/Protocol/ProtocolAnydeskTests.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-10 21:10:35 +00:00
Dimitrij
173b208eb1 Update mRemoteNG/UI/Forms/FrmAbout.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-10 21:10:12 +00:00
Dimitrij
50de37c3a4 Update mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-10 21:09:56 +00:00
Dimitrij
380e91de07 Update mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-10 21:09:06 +00:00
Dimitrij
ba97933f33 Update mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-10 21:08:46 +00:00
Dimitrij
539b761199 Merge pull request #3136 from mRemoteNG/renovate/major-github-artifact-actions
Update GitHub Artifact Actions (major)
2026-02-10 19:58:22 +00:00
renovate[bot]
aaff6e4548 Update GitHub Artifact Actions 2026-02-10 18:31:58 +00:00
Kvarkas
a1e3b34580 do release 2026-02-10 18:30:52 +00:00
Kvarkas
276585e379 upgrade so release include self contained version 2026-02-10 18:27:03 +00:00
Dimitrij
9242dc0faf Merge pull request #3135 from mRemoteNG/renovate/dotnet-monorepo
Update dotnet monorepo to 10.0.3
2026-02-10 18:13:22 +00:00
renovate[bot]
14d08d8d62 Update dotnet monorepo to 10.0.3 2026-02-10 17:56:13 +00:00
Dimitrij
421d8eb581 Merge pull request #3132 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.72
2026-02-10 11:37:36 +00:00
renovate[bot]
aed0006f1d Update dependency AWSSDK.EC2 to 4.0.72 2026-02-10 01:15:15 +00:00
Dimitrij
86d986a633 Merge pull request #3101 from mRemoteNG/copilot/fix-new-connection-error
Show error popup when connecting without hostname/IP
2026-02-01 20:18:51 +00:00
Dimitrij
5163aeb4d2 Change error message to warning for blank hostname 2026-02-01 20:18:11 +00:00
copilot-swe-agent[bot]
a103939c64 Address code review: fix typos, improve test robustness, use Language resources
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-01 19:59:39 +00:00
copilot-swe-agent[bot]
bcb8e05698 Update documentation for new popup error default setting
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-01 19:58:03 +00:00
copilot-swe-agent[bot]
2e74313f07 Change hostname validation to ErrorMsg and enable popup errors by default
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-01 19:56:53 +00:00
copilot-swe-agent[bot]
c9791454ec Initial plan 2026-02-01 19:52:25 +00:00
Dimitrij
b1c1696acb Merge pull request #3099 from jafin/fix/frmInputBox-autosize
Fix FrmInputBox to use dynamic sizing instead of fixed dimensions
2026-02-01 19:24:31 +00:00
Dimitrij
ab668ac677 Merge pull request #3097 from mRemoteNG/renovate/chromiumembeddedframework.runtime.win-x64-144.x
Update dependency chromiumembeddedframework.runtime.win-x64 to v144
2026-02-01 19:23:58 +00:00
Dimitrij
1777c4840a Merge pull request #3096 from mRemoteNG/renovate/chromiumembeddedframework.runtime.win-arm64-144.x
Update dependency chromiumembeddedframework.runtime.win-arm64 to v144
2026-02-01 19:23:44 +00:00
Dimitrij
90fcd672d8 Merge pull request #3095 from mRemoteNG/renovate/protobuf-monorepo
Update dependency Google.Protobuf to 3.33.5
2026-02-01 19:23:27 +00:00
Dimitrij
4226396cbf Merge pull request #3094 from mRemoteNG/renovate/microsoft.web.webview2-1.x
Update dependency Microsoft.Web.WebView2 to 1.0.3719.77
2026-02-01 19:23:11 +00:00
Dimitrij
3591ca0f4c Merge pull request #3093 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2026-02-01 19:22:55 +00:00
Jason Finch
09cbcccf30 Fix FrmInputBox to use dynamic sizing instead of fixed dimensions 2026-02-01 13:32:00 +10:00
renovate[bot]
4e36b5666e Update dependency chromiumembeddedframework.runtime.win-x64 to v144 2026-01-31 05:48:20 +00:00
renovate[bot]
308253a325 Update dependency chromiumembeddedframework.runtime.win-arm64 to v144 2026-01-31 05:48:13 +00:00
renovate[bot]
30bb4016b4 Update dependency Google.Protobuf to 3.33.5 2026-01-30 00:59:36 +00:00
renovate[bot]
e616ae16e1 Update aws-sdk-net monorepo 2026-01-30 00:59:30 +00:00
renovate[bot]
469528a07a Update dependency Microsoft.Web.WebView2 to 1.0.3719.77 2026-01-27 12:45:38 +00:00
Dimitrij
c7f831e9f9 Merge pull request #3089 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.67
2026-01-26 12:51:28 +00:00
Dimitrij
dd9922be45 Merge pull request #3087 from mRemoteNG/renovate/mysql.data-9.x
Update dependency MySql.Data to 9.6.0
2026-01-26 12:51:11 +00:00
Dimitrij
d0a468c22b Merge pull request #3090 from mRemoteNG/renovate/cucumber.messages-32.x
Update dependency Cucumber.Messages to v32
2026-01-26 12:50:56 +00:00
Dimitrij
ff5dbc88fe Merge pull request #3091 from mRemoteNG/renovate/gherkin-38.x
Update dependency Gherkin to v38
2026-01-26 12:50:42 +00:00
renovate[bot]
3a4ae9b098 Update dependency Cucumber.Messages to v32 2026-01-25 13:44:37 +00:00
renovate[bot]
2de24e534c Update dependency AWSSDK.EC2 to 4.0.67 2026-01-22 21:05:19 +00:00
renovate[bot]
59412a65e1 Update dependency Gherkin to v38 2026-01-22 02:30:57 +00:00
renovate[bot]
adedb6962f Update dependency MySql.Data to 9.6.0 2026-01-21 10:47:43 +00:00
Dimitrij
d3fa608ae9 Merge pull request #3086 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2026-01-21 10:47:03 +00:00
renovate[bot]
3159903875 Update aws-sdk-net monorepo 2026-01-21 01:59:43 +00:00
Dimitrij
31c28c4917 Merge pull request #3084 from mRemoteNG/renovate/microsoft.data.sqlclient-6.x
Update dependency Microsoft.Data.SqlClient to 6.1.4
2026-01-16 09:59:33 +00:00
Dimitrij
2c13f7c3a7 Merge pull request #3085 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2026-01-16 09:59:18 +00:00
renovate[bot]
725ee92147 Update aws-sdk-net monorepo 2026-01-16 01:27:24 +00:00
renovate[bot]
14406f79a2 Update dependency Microsoft.Data.SqlClient to 6.1.4 2026-01-15 18:42:41 +00:00
Kvarkas
3e202c3a19 lib update 2026-01-14 10:26:47 +00:00
Dimitrij
412c727e4c Merge pull request #3081 from mRemoteNG/renovate/dotnet-monorepo
Update dotnet monorepo to 10.0.2
2026-01-13 19:19:31 +00:00
renovate[bot]
31e7b9e443 Update dotnet monorepo to 10.0.2 2026-01-13 18:40:14 +00:00
Dimitrij
a18e292765 Merge pull request #3078 from mRemoteNG/renovate/protobuf-monorepo
Update dependency Google.Protobuf to 3.33.4
2026-01-13 11:23:46 +00:00
Dimitrij
258ea87f90 Merge pull request #3079 from mRemoteNG/renovate/cucumber.messages-31.x
Update dependency Cucumber.Messages to 31.2.0
2026-01-13 10:38:07 +00:00
renovate[bot]
fd9eabe1e6 Update dependency Google.Protobuf to 3.33.4 2026-01-12 21:55:32 +00:00
renovate[bot]
a9dd06df45 Update dependency Cucumber.Messages to 31.2.0 2026-01-12 00:24:29 +00:00
Dimitrij
dfc24b0cb2 Merge pull request #3076 from mRemoteNG/renovate/nunit3testadapter-6.x
Update dependency NUnit3TestAdapter to 6.1.0
2026-01-08 09:46:33 +00:00
Dimitrij
63f5325f29 Merge pull request #3075 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2026-01-08 09:46:04 +00:00
renovate[bot]
6f7949214b Update dependency NUnit3TestAdapter to 6.1.0 2026-01-07 21:23:22 +00:00
renovate[bot]
6cfec060a0 Update aws-sdk-net monorepo 2026-01-07 21:23:16 +00:00
Dimitrij
db733424ca Merge pull request #3074 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.65.1
2026-01-06 10:49:59 +00:00
renovate[bot]
49eab4d377 Update dependency AWSSDK.EC2 to 4.0.65.1 2026-01-06 01:42:04 +00:00
Dimitrij
53e5396031 Merge pull request #3073 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.Core to 4.0.3.7
2026-01-05 21:17:14 +00:00
renovate[bot]
99d01130bf Update dependency AWSSDK.Core to 4.0.3.7 2026-01-05 21:04:09 +00:00
Dimitrij
62ce6cd6e7 Merge pull request #3071 from mRemoteNG/renovate/nunit.consolerunner-3.x
Update dependency NUnit.ConsoleRunner to 3.22.0
2026-01-04 17:53:30 +00:00
renovate[bot]
35c66b0e4a Update dependency NUnit.ConsoleRunner to 3.22.0 2026-01-04 17:53:20 +00:00
Dimitrij
dafc05dc42 Merge pull request #3072 from mRemoteNG/renovate/zstdsharp.port-0.x
Update dependency ZstdSharp.Port to 0.8.7
2026-01-04 17:53:13 +00:00
Dimitrij
458a05ea5f Merge pull request #3070 from mRemoteNG/renovate/nunit.console-3.x
Update dependency NUnit.Console to 3.22.0
2026-01-04 17:52:43 +00:00
renovate[bot]
38acc1e960 Update dependency ZstdSharp.Port to 0.8.7 2026-01-03 17:58:37 +00:00
renovate[bot]
f83209a2b9 Update dependency NUnit.Console to 3.22.0 2026-01-03 01:59:37 +00:00
Dimitrij
9d1546c8b7 Merge pull request #3068 from mRemoteNG/copilot/fix-sql-injection-issue
Fix WQL injection vulnerability in InstalledWindowsUpdateChecker
2025-12-30 12:30:53 +00:00
copilot-swe-agent[bot]
dca2517cf0 Normalize digit-only KB IDs to include KB prefix
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-12-30 12:19:35 +00:00
copilot-swe-agent[bot]
ff54ca9015 Fix inaccurate comment in SanitizeKbId method
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-12-30 12:17:52 +00:00
copilot-swe-agent[bot]
76cb0a1e0b Address code review feedback - optimize regex and use Array.Empty
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-12-30 12:16:52 +00:00
copilot-swe-agent[bot]
c683854678 Add WQL injection prevention via KB ID sanitization
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-12-30 12:15:31 +00:00
copilot-swe-agent[bot]
e9d0a8aa69 Initial plan 2025-12-30 12:11:00 +00:00
Dimitrij
30cb1de711 Merge pull request #3064 from mRemoteNG/copilot/fix-ldap-query-injection
Fix LDAP injection vulnerability in Active Directory integration
2025-12-30 12:06:35 +00:00
Dimitrij
699b93e175 Merge pull request #3066 from mRemoteNG/copilot/setup-copilot-instructions
Add GitHub Copilot instructions for repository
2025-12-30 12:05:30 +00:00
copilot-swe-agent[bot]
c7df6f3715 Add comprehensive GitHub Copilot instructions
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-12-29 13:17:42 +00:00
copilot-swe-agent[bot]
f1d1a19779 Initial plan 2025-12-29 13:13:24 +00:00
copilot-swe-agent[bot]
1c2b5fadc4 Fix code review feedback: consistent hex formatting and test names
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-12-29 10:06:39 +00:00
copilot-swe-agent[bot]
97f5daa13d Add LDAP injection protection with sanitization utility
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-12-29 10:04:43 +00:00
copilot-swe-agent[bot]
63e100c233 Initial plan 2025-12-29 09:59:26 +00:00
Dimitrij
92a710e72a Merge pull request #3061 from koen-lee/make-1password-integration-more-tolerant
Make 1password integration more tolerant
2025-12-29 09:57:47 +00:00
Koen van Leeuwen
fe82146f47 server type items have username id fields by default which may have localized labels 2025-12-24 21:40:54 +01:00
Koen van Leeuwen
64b0496ef7 Add (self) documentation 2025-12-24 21:21:27 +01:00
Koen van Leeuwen
7d21b3de9e Fall back to a username/password by label 2025-12-24 20:48:51 +01:00
Dimitrij
68a8caea34 Merge pull request #3059 from mRemoteNG/renovate/gherkin-37.x
Update dependency Gherkin to 37.0.1
2025-12-24 10:18:21 +00:00
Dimitrij
aee18bae3b Merge pull request #3058 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.65
2025-12-24 10:18:09 +00:00
Dimitrij
ff58cae677 Merge pull request #3060 from mRemoteNG/renovate/cucumber.messages-31.x
Update dependency Cucumber.Messages to 31.1.0
2025-12-24 10:17:54 +00:00
renovate[bot]
548f938757 Update dependency Cucumber.Messages to 31.1.0 2025-12-23 00:55:31 +00:00
renovate[bot]
b9c2e82af1 Update dependency Gherkin to 37.0.1 2025-12-22 21:56:51 +00:00
renovate[bot]
23bb5ec7be Update dependency AWSSDK.EC2 to 4.0.65 2025-12-22 21:56:45 +00:00
Dimitrij
0b7b87d84a Merge pull request #3057 from mRemoteNG/renovate/nunit3testadapter-6.x
Update dependency NUnit3TestAdapter to 6.0.1
2025-12-20 15:44:58 +00:00
Dimitrij
b84a49e99c Merge pull request #3056 from mRemoteNG/renovate/nunit.consolerunner-3.x
Update dependency NUnit.ConsoleRunner to 3.21.1
2025-12-20 15:40:31 +00:00
renovate[bot]
327cc46c8d Update dependency NUnit3TestAdapter to 6.0.1 2025-12-20 15:28:10 +00:00
renovate[bot]
8540434594 Update dependency NUnit.ConsoleRunner to 3.21.1 2025-12-20 15:28:07 +00:00
Dimitrij
41536ba971 Merge pull request #3055 from mRemoteNG/renovate/nunit.console-3.x
Update dependency NUnit.Console to 3.21.1
2025-12-20 15:27:25 +00:00
renovate[bot]
8ddaf024ff Update dependency NUnit.Console to 3.21.1 2025-12-20 04:34:08 +00:00
Dimitrij
7e8f647f54 Merge pull request #3053 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.64
2025-12-19 10:15:21 +00:00
renovate[bot]
9a32a7c68c Update dependency AWSSDK.EC2 to 4.0.64 2025-12-19 01:41:58 +00:00
Dimitrij
280a930792 Merge pull request #3052 from mRemoteNG/renovate/cucumber.messages-31.x
Update dependency Cucumber.Messages to 31.0.1
2025-12-18 20:41:57 +00:00
renovate[bot]
2540d39dd8 Update dependency Cucumber.Messages to 31.0.1 2025-12-18 18:41:46 +00:00
Dimitrij
2b00fa0ad9 Merge pull request #3050 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.63
2025-12-17 22:34:25 +00:00
renovate[bot]
810aad90ed Update dependency AWSSDK.EC2 to 4.0.63 2025-12-15 22:25:49 +00:00
Dimitrij
59c8968d8a Merge pull request #3048 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2025-12-13 21:03:19 +00:00
renovate[bot]
4bfed87d81 Update aws-sdk-net monorepo 2025-12-12 22:00:10 +00:00
Dimitrij
5c36477b1f Merge pull request #3047 from mRemoteNG/renovate/chromiumembeddedframework.runtime.win-x64-143.x
Update dependency chromiumembeddedframework.runtime.win-x64 to v143
2025-12-12 11:42:19 +00:00
Dimitrij
a529b978e1 Merge pull request #3045 from mRemoteNG/renovate/actions-cache-5.x
Update actions/cache action to v5
2025-12-12 11:42:01 +00:00
Dimitrij
eae83eadab Merge pull request #3042 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2025-12-12 11:41:49 +00:00
Dimitrij
297260af9a Merge pull request #3046 from mRemoteNG/renovate/chromiumembeddedframework.runtime.win-arm64-143.x
Update dependency chromiumembeddedframework.runtime.win-arm64 to v143
2025-12-12 11:41:32 +00:00
renovate[bot]
f5d38c282f Update dependency chromiumembeddedframework.runtime.win-x64 to v143 2025-12-12 11:13:59 +00:00
renovate[bot]
493e5eda6a Update dependency chromiumembeddedframework.runtime.win-arm64 to v143 2025-12-12 11:13:54 +00:00
renovate[bot]
88b8201384 Update actions/cache action to v5 2025-12-12 04:11:47 +00:00
renovate[bot]
b88204c261 Update aws-sdk-net monorepo 2025-12-10 22:42:33 +00:00
Dimitrij
d39fb10b47 Merge pull request #3041 from mRemoteNG/renovate/dotnet-monorepo
Update dotnet monorepo to 10.0.1
2025-12-09 23:04:07 +00:00
Dimitrij
96e7313b69 Merge pull request #3040 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.62
2025-12-09 23:03:43 +00:00
Dimitrij
02be546c5b Merge pull request #3039 from mRemoteNG/renovate/microsoft.web.webview2-1.x
Update dependency Microsoft.Web.WebView2 to 1.0.3650.58
2025-12-09 23:02:57 +00:00
renovate[bot]
c5b98c6cc8 Update dotnet monorepo to 10.0.1 2025-12-09 19:55:45 +00:00
renovate[bot]
687a4088ab Update dependency AWSSDK.EC2 to 4.0.62 2025-12-09 03:37:36 +00:00
renovate[bot]
8c63843c22 Update dependency Microsoft.Web.WebView2 to 1.0.3650.58 2025-12-08 14:52:30 +00:00
copilot-swe-agent[bot]
469ca48592 Fix compilation issues and improve code review feedback
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-12-08 13:12:15 +00:00
copilot-swe-agent[bot]
208ce663b2 Address code review feedback - improve security validations
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-12-08 13:08:50 +00:00
copilot-swe-agent[bot]
843243c75e Add comprehensive tests for AnyDesk ID validation
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-12-08 13:06:16 +00:00
copilot-swe-agent[bot]
b7ed5a300d Fix command injection vulnerabilities in Process.Start calls
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-12-08 13:03:54 +00:00
copilot-swe-agent[bot]
415a649a76 Initial plan 2025-12-08 12:55:04 +00:00
Dimitrij
462d9c39af Merge pull request #3035 from mRemoteNG/renovate/nunit.console-3.x
Update dependency NUnit.Console to 3.21.0
2025-12-08 12:15:46 +00:00
Dimitrij
d52d79dd69 Merge pull request #3034 from mRemoteNG/renovate/protobuf-monorepo
Update dependency Google.Protobuf to 3.33.2
2025-12-08 12:15:25 +00:00
Dimitrij
5dbc6e74e0 Merge pull request #3033 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2025-12-08 12:15:02 +00:00
renovate[bot]
986b0084bf Update dependency NUnit.Console to 3.21.0 2025-12-08 12:14:52 +00:00
Dimitrij
78bdf02c14 Merge pull request #3036 from mRemoteNG/renovate/nunit.consolerunner-3.x
Update dependency NUnit.ConsoleRunner to 3.21.0
2025-12-08 12:14:31 +00:00
Dimitrij
b4ea8f00ad Merge pull request #3037 from mRemoteNG/renovate/nunit3testadapter-6.x
Update dependency NUnit3TestAdapter to v6
2025-12-08 12:14:16 +00:00
renovate[bot]
7fd992e9f1 Update dependency NUnit3TestAdapter to v6 2025-12-06 21:14:25 +00:00
renovate[bot]
76e739cdc1 Update dependency NUnit.ConsoleRunner to 3.21.0 2025-12-06 18:10:34 +00:00
renovate[bot]
0e0cc1c884 Update dependency Google.Protobuf to 3.33.2 2025-12-06 03:05:36 +00:00
renovate[bot]
21fba7f371 Update aws-sdk-net monorepo 2025-12-05 21:29:40 +00:00
Dimitrij
3f3a0c3c21 Merge pull request #3032 from mRemoteNG/renovate/reportgenerator-5.x
Update dependency ReportGenerator to 5.5.1
2025-12-04 10:24:31 +00:00
Kvarkas
1db87b18de update links to vc++ 2025-12-03 21:59:40 +00:00
renovate[bot]
44b01bca96 Update dependency ReportGenerator to 5.5.1 2025-12-03 21:35:59 +00:00
Dimitrij
441b13bed6 Merge pull request #3029 from mRemoteNG/renovate/softprops-action-gh-release-digest
Update softprops/action-gh-release digest to a06a81a
2025-12-03 17:10:28 +00:00
renovate[bot]
dd72e9edb7 Update softprops/action-gh-release digest to a06a81a 2025-12-01 09:07:48 +00:00
Kvarkas
8bbcc9b877 update to .net 10 2025-11-26 09:21:53 +00:00
Dimitrij
98580a8dc6 Merge pull request #3026 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.61
2025-11-26 09:21:00 +00:00
renovate[bot]
3600905a39 Update dependency AWSSDK.EC2 to 4.0.61 2025-11-25 21:38:27 +00:00
Dimitrij
c207685b91 Merge pull request #3025 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2025-11-24 10:28:24 +00:00
renovate[bot]
683f3bcb80 Update aws-sdk-net monorepo 2025-11-22 02:26:25 +00:00
Dimitrij
23f964ba28 Merge pull request #3023 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.59
2025-11-21 10:10:26 +00:00
renovate[bot]
56271ba346 Update dependency AWSSDK.EC2 to 4.0.59 2025-11-21 01:24:18 +00:00
Dimitrij
252a402f4f Merge pull request #3022 from mRemoteNG/renovate/actions-checkout-6.x
Update actions/checkout action to v6
2025-11-20 19:08:30 +00:00
renovate[bot]
d2d0722ac2 Update actions/checkout action to v6 2025-11-20 16:57:14 +00:00
Dimitrij
ce9bdccc4d Merge pull request #3019 from mRemoteNG/renovate/cucumber.messages-31.x
Update dependency Cucumber.Messages to v31
2025-11-20 14:52:12 +00:00
Dimitrij
2a992aa1d8 Merge pull request #3016 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2025-11-20 14:51:54 +00:00
Dimitrij
f73729bbe8 Merge pull request #3021 from mRemoteNG/renovate/gherkin-37.x
Update dependency Gherkin to v37
2025-11-20 14:51:31 +00:00
renovate[bot]
8634edbd8c Update aws-sdk-net monorepo 2025-11-20 00:30:33 +00:00
renovate[bot]
cc3ead0799 Update dependency Gherkin to v37 2025-11-19 20:43:09 +00:00
renovate[bot]
566cd26e73 Update dependency Cucumber.Messages to v31 2025-11-19 00:41:03 +00:00
Dimitrij
2354d5a8a4 Merge pull request #3015 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.54
2025-11-14 22:24:43 +00:00
renovate[bot]
778067a590 Update dependency AWSSDK.EC2 to 4.0.54 2025-11-13 22:56:16 +00:00
Dimitrij
d8d9c844d1 Merge pull request #3014 from mRemoteNG/renovate/protobuf-monorepo
Update dependency Google.Protobuf to 3.33.1
2025-11-13 22:55:42 +00:00
renovate[bot]
dc6655514a Update dependency Google.Protobuf to 3.33.1 2025-11-13 20:38:21 +00:00
Dimitrij
feb3b72fd0 Merge pull request #3011 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2025-11-13 10:58:16 +00:00
Dimitrij
479e18b204 Merge pull request #3012 from mRemoteNG/renovate/microsoft.data.sqlclient-6.x
Update dependency Microsoft.Data.SqlClient to 6.1.3
2025-11-13 10:57:59 +00:00
renovate[bot]
b1c169d0b8 Update dependency Microsoft.Data.SqlClient to 6.1.3 2025-11-13 00:43:12 +00:00
renovate[bot]
7f75fadb31 Update aws-sdk-net monorepo 2025-11-13 00:43:07 +00:00
Dimitrij
6af70543ea Merge pull request #3010 from mRemoteNG/renovate/reportgenerator-5.x
Update dependency ReportGenerator to 5.5.0
2025-11-12 09:28:27 +00:00
Dimitrij
59d5d575ad Merge pull request #3009 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.52
2025-11-12 09:26:54 +00:00
renovate[bot]
05f2b84cf5 Update dependency ReportGenerator to 5.5.0 2025-11-11 22:41:56 +00:00
renovate[bot]
a39d4a3063 Update dependency AWSSDK.EC2 to 4.0.52 2025-11-11 22:41:50 +00:00
Dimitrij
1da019575e Merge pull request #3008 from mRemoteNG/renovate/major-dotnet-monorepo
Update dotnet monorepo to v10 (major)
2025-11-11 18:37:15 +00:00
renovate[bot]
195c4be4e2 Update dotnet monorepo to v10 2025-11-11 18:36:53 +00:00
Dimitrij
4dd672e600 Merge pull request #3006 from mRemoteNG/renovate/vstest-monorepo
Update dependency Microsoft.NET.Test.Sdk to 18.0.1
2025-11-11 18:36:17 +00:00
renovate[bot]
f3a14c5ede Update dependency Microsoft.NET.Test.Sdk to 18.0.1 2025-11-11 18:36:04 +00:00
Dimitrij
af696a96aa Merge pull request #3007 from mRemoteNG/renovate/dotnet-monorepo
Update dotnet monorepo to 9.0.11
2025-11-11 18:35:29 +00:00
renovate[bot]
d7de52fb4c Update dotnet monorepo to 9.0.11 2025-11-11 16:53:18 +00:00
Dimitrij
ae66a8fcfc Merge pull request #3004 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.51
2025-11-11 09:11:25 +00:00
renovate[bot]
56d18774a8 Update dependency AWSSDK.EC2 to 4.0.51 2025-11-11 03:41:19 +00:00
Dimitrij
7ba22c5555 Merge pull request #2995 from joubertdj/fix/issue-2960-panel-tab-visibility
Fix panel tab header visibility issue when single panel is docked
2025-11-09 18:39:55 +00:00
Dimitrij
dab7715eb7 Merge pull request #3002 from mRemoteNG/renovate/softprops-action-gh-release-digest
Update softprops/action-gh-release digest to 5be0e66
2025-11-09 18:39:28 +00:00
Dimitrij
ec8bac933d Merge pull request #2999 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2025-11-09 18:38:46 +00:00
renovate[bot]
958ec65b17 Update softprops/action-gh-release digest to 5be0e66 2025-11-08 21:58:44 +00:00
renovate[bot]
e775acc209 Update aws-sdk-net monorepo 2025-11-07 23:29:42 +00:00
Dimitrij
d23631faef Merge pull request #2996 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.49
2025-11-07 09:14:03 +00:00
renovate[bot]
518401b2c4 Update dependency AWSSDK.EC2 to 4.0.49 2025-11-06 22:05:23 +00:00
Dawie Joubert
20c3ebd662 Fix panel tab header visibility issue when single panel is docked
Changed DockPanel DocumentStyle from DockingSdi to DockingWindow to ensure
panel tab headers remain visible even when only one panel exists in a dock area.

This resolves the issue where users could not access panel controls (close button,
pin/auto-hide, dropdown menu) after moving panels to new locations, as the tab
strip was completely hidden with DockingSdi when only one panel was present.

With DockingWindow style:
- Panel name/title always visible
- Close button (X) always accessible
- Pin/auto-hide button always available
- Dropdown menu always accessible
- No more "lost" panels requiring layout reset

Fixes #2960

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-06 16:40:29 +02:00
Dimitrij
8d0d03e152 Merge pull request #2990 from joubertdj/fix/rdp-automatic-resize-issue-2971
Fix RDP automatic resize not working when manually dragging window edges (maximize/minimize/etc)
2025-11-06 12:10:22 +00:00
Dimitrij
632cfca71f Update mRemoteNGTests/Connection/Protocol/RdpProtocol8ResizeTests.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-06 12:09:27 +00:00
Dimitrij
221ff38a66 Merge pull request #2991 from joubertdj/fix/issue-2907-options-panel-fixes
Fix issue #2907: Options panel freezing, disposal, and performance is…
2025-11-06 12:08:18 +00:00
Dimitrij
3ac7365c9b Update mRemoteNGTests/UI/Forms/OptionsFormTests.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-06 12:06:36 +00:00
Dimitrij
be53755010 Update mRemoteNG/UI/Forms/frmOptions.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-06 12:06:18 +00:00
Dimitrij
30eafa3efa Update mRemoteNG/UI/Window/OptionsWindow.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-06 12:06:10 +00:00
Dimitrij
7c0a6a51d4 Merge pull request #2992 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2025-11-06 11:59:51 +00:00
Dimitrij
181f96823e Merge pull request #2993 from mRemoteNG/renovate/gherkin-36.x
Update dependency Gherkin to 36.1.0
2025-11-06 11:59:33 +00:00
renovate[bot]
7b4e9781ac Update dependency Gherkin to 36.1.0 2025-11-05 22:29:32 +00:00
renovate[bot]
7a4ff98ba8 Update aws-sdk-net monorepo 2025-11-05 22:29:26 +00:00
Dawie Joubert
100c2e3078 Fix issue #2907: Options panel freezing, disposal, and performance issues
This commit resolves multiple related issues with the Options dialog that caused freezing, crashes, and slow performance:

**Problem 1: Infinite Recursive Loop**
- Symptom: Options dialog would freeze when navigating between pages
- Cause: LstOptionPages_SelectedIndexChanged event handler triggering itself infinitely
- Fix: Added _isHandlingSelectionChange guard flag to prevent recursive calls

**Problem 2: Disposed Object Exception**
- Symptom: "Cannot access a disposed object" error after SSH connection workflow
- Cause: Static FrmOptions instance was disposed but still referenced
- Fix: Enhanced OptionsWindow.LoadOptionsForm() to detect disposal before use
- Fix: Added FrmMain.RecreateOptionsForm() to recreate disposed forms transparently

**Problem 3: Index Out of Range**
- Symptom: "index must be less than 0" when accessing empty lstOptionPages
- Cause: SetActivatedPage() tried to access Items[0] when collection was empty
- Fix: Added bounds checking before accessing lstOptionPages.Items

**Problem 4: NullReferenceException in OptionsPages**
- Symptom: NullReferenceException in LoadRegistrySettings() across all pages
- Cause: pageRegSettingsInstance was null when registry settings didn't exist
- Fix: Added null checks and default instance creation in 8 OptionsPages

**Problem 5: Slow Page Loading on Recreation**
- Symptom: Second Options dialog open showed staggered page loading (~2.2 seconds)
- Cause: Application.Idle async pattern loaded pages one-by-one
- Fix: Replaced async loading with synchronous batch loading using BeginUpdate/EndUpdate

**Files Modified:**
- mRemoteNG/UI/Forms/frmOptions.cs
- mRemoteNG/UI/Window/OptionsWindow.cs
- mRemoteNG/UI/Forms/frmMain.cs
- mRemoteNG/UI/Forms/OptionsPages/StartupExitPage.cs
- mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
- mRemoteNG/UI/Forms/OptionsPages/AppearancePage.cs
- mRemoteNG/UI/Forms/OptionsPages/SecurityPage.cs
- mRemoteNG/UI/Forms/OptionsPages/ConnectionsPage.cs
- mRemoteNG/UI/Forms/OptionsPages/CredentialsPage.cs
- mRemoteNG/UI/Forms/OptionsPages/TabsPanelsPage.cs
- mRemoteNG/UI/Forms/OptionsPages/UpdatesPage.cs

**Additional Changes:**
- Replaced all Debug.WriteLine with Logger.Instance.Log for consistent logging
- Added comprehensive debug logging throughout Options form lifecycle
- Improved defensive programming with guard flags and validation checks

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 19:49:56 +02:00
Dawie Joubert
cc6f07d943 Fix RDP automatic resize not working when manually dragging window edges
Fixes #2971

This fix addresses the issue where RDP connections with "Automatic resize"
enabled weren't resizing the remote desktop when users manually dragged
window edges - only when changing window states (Maximize/Restore).

Changes:
- RdpProtocol8.cs: Fixed resize logic to use InterfaceControl.Size instead
  of Control.Size, added 300ms debounce to reduce flickering, and registered
  ResizeEnd event handler
- RdpProtocol9.cs: Added null safety checks in UpdateSessionDisplaySettings
- NotificationPanelMessageWriter.cs: Added exception handling for shutdown
  scenarios to prevent InvalidAsynchronousStateException
- RdpProtocol8ResizeTests.cs: Added 12 comprehensive unit tests covering
  all resize scenarios including debounce mechanism
- TabColorConverterTests.cs: Added missing System namespace import

The fix works for all RDP versions (8, 9, 10, 11) through inheritance.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 17:34:45 +02:00
Dimitrij
cc3a9c3a00 Merge pull request #2985 from mRemoteNG/copilot/fix-vnc-connection-issue
Fix VNC connection failure due to TCP client resource leak
2025-11-03 22:49:06 +00:00
Dimitrij
ce1d82c730 Merge pull request #2986 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.47
2025-11-03 22:42:17 +00:00
renovate[bot]
ec3a01de70 Update dependency AWSSDK.EC2 to 4.0.47 2025-11-03 22:13:04 +00:00
Dimitrij
af894964fc Update mRemoteNG/Connection/Protocol/VNC/Connection.Protocol.VNC.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-03 21:39:58 +00:00
copilot-swe-agent[bot]
5cebc4d418 Reset socket exception before other operations for safety
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-11-03 21:25:15 +00:00
copilot-swe-agent[bot]
04e0144004 Preserve exception stack trace using ExceptionDispatchInfo
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-11-03 21:23:55 +00:00
copilot-swe-agent[bot]
c33c9814c2 Address code review feedback: improve comment and add thread safety
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-11-03 21:22:07 +00:00
copilot-swe-agent[bot]
3a946e5810 Fix VNC connection TCP client resource leak
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-11-03 21:18:50 +00:00
copilot-swe-agent[bot]
6a6a894a1c Initial plan 2025-11-03 21:14:03 +00:00
Dimitrij
e1ffb9262d Merge pull request #2977 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2025-11-03 11:48:00 +00:00
Dimitrij
b3eb6904f9 Merge pull request #2983 from mRemoteNG/renovate/microsoft.web.webview2-1.x
Update dependency Microsoft.Web.WebView2 to 1.0.3595.46
2025-11-03 11:47:37 +00:00
Dimitrij
72cbade402 Merge pull request #2979 from MaxPlap/feature/VaultOpenbao-Connector
Feature/vault openbao connector
2025-11-03 11:10:12 +00:00
Dimitrij
6fd008c2e8 Merge pull request #2978 from mRemoteNG/renovate/ssh.net-2025.x
Update dependency SSH.NET to 2025.1.0
2025-11-03 11:09:54 +00:00
renovate[bot]
25f26f08de Update dependency Microsoft.Web.WebView2 to 1.0.3595.46 2025-11-03 10:00:32 +00:00
renovate[bot]
7ebc62d8e3 Update aws-sdk-net monorepo 2025-10-31 20:57:59 +00:00
massimo.antonello
e04ba4f9e0 removed debug log 2025-10-28 15:46:38 +01:00
massimo.antonello
0d4324b009 Read OTP SSH 2025-10-28 15:12:32 +01:00
massimo.antonello
6404956a62 new option 2025-10-28 14:45:26 +01:00
renovate[bot]
769db78ee8 Update dependency SSH.NET to 2025.1.0 2025-10-27 23:27:13 +00:00
Dimitrij
8bb8d52b1d Merge pull request #2974 from mRemoteNG/renovate/reportgenerator-5.x
Update dependency ReportGenerator to 5.4.18
2025-10-26 13:20:05 +00:00
Dimitrij
e990659d05 Merge pull request #2975 from mRemoteNG/renovate/chromiumembeddedframework.runtime.win-arm64-141.x
Update dependency chromiumembeddedframework.runtime.win-arm64 to v141
2025-10-26 13:19:45 +00:00
Dimitrij
0fcef353ae Merge pull request #2976 from mRemoteNG/renovate/chromiumembeddedframework.runtime.win-x64-141.x
Update dependency chromiumembeddedframework.runtime.win-x64 to v141
2025-10-26 13:18:40 +00:00
renovate[bot]
bf91a8a709 Update dependency chromiumembeddedframework.runtime.win-x64 to v141 2025-10-26 00:55:06 +00:00
renovate[bot]
d81a608b3a Update dependency chromiumembeddedframework.runtime.win-arm64 to v141 2025-10-26 00:55:02 +00:00
renovate[bot]
8413ee77d4 Update dependency ReportGenerator to 5.4.18 2025-10-25 16:48:20 +00:00
Dimitrij
4d92a30089 Merge pull request #2970 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2025-10-24 09:16:23 +01:00
renovate[bot]
57f7e09bb6 Update aws-sdk-net monorepo 2025-10-24 01:12:48 +00:00
Dimitrij
19fdccc540 Merge pull request #2969 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.44
2025-10-23 17:44:02 +01:00
renovate[bot]
83c2f2a7a8 Update dependency AWSSDK.EC2 to 4.0.44 2025-10-23 02:53:43 +00:00
Dimitrij
9dcea7ed6e Merge pull request #2968 from mRemoteNG/renovate/mysql.data-9.x
Update dependency MySql.Data to 9.5.0
2025-10-22 10:29:24 +01:00
renovate[bot]
6ad3de91e9 Update dependency MySql.Data to 9.5.0 2025-10-22 08:26:36 +00:00
Dimitrij
2bbbd166f9 Merge pull request #2967 from mRemoteNG/copilot/fix-command-injection-vulnerability
Fix command injection vulnerabilities in Process.Start calls
2025-10-22 09:25:51 +01:00
copilot-swe-agent[bot]
327ba3f4e1 Add path validation to PuttyBase, IntegratedProgram, and AnyDesk protocols
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-22 08:21:20 +00:00
copilot-swe-agent[bot]
c9b77b6616 Add comprehensive security tests for path validation and command injection prevention
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-22 08:17:58 +00:00
copilot-swe-agent[bot]
a94e58c83e Add path validation to prevent command injection in Process.Start calls
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-22 08:16:25 +00:00
copilot-swe-agent[bot]
d40794d7a0 Initial plan 2025-10-22 08:10:43 +00:00
Dimitrij
7dd916dd80 Merge pull request #2966 from mRemoteNG/copilot/fix-option-panel-issue
Fix options panel hanging on second open
2025-10-22 00:51:38 +01:00
copilot-swe-agent[bot]
d16d76d0fa Improve fix to avoid interfering with initial load
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-21 23:41:25 +00:00
copilot-swe-agent[bot]
a896949752 Fix options panel not opening on second call
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-21 23:40:06 +00:00
copilot-swe-agent[bot]
f63186a7a6 Initial plan 2025-10-21 23:35:44 +00:00
Dimitrij
ee88e835ed in additional to #2963 logic fixes 2025-10-22 00:16:26 +01:00
Dimitrij
1288a274f3 code tidy up 2025-10-21 22:19:22 +01:00
Dimitrij
facc523aae Merge pull request #2963 from mRemoteNG/copilot/add-bind-connections-config
Add option to bind Connections and Config panels together when auto-hidden
2025-10-21 21:00:15 +01:00
Dimitrij
11dfdd797d Merge pull request #2962 from mRemoteNG/copilot/fix-remote-code-execution
Remove insecure BinaryFormatter deserialization code to prevent RCE vulnerabilities
2025-10-21 20:58:55 +01:00
copilot-swe-agent[bot]
3dc37d0359 Refine PanelBinder implementation and add documentation
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-21 17:33:38 +00:00
copilot-swe-agent[bot]
b00185e1dc Add panel binding feature with UI option
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-21 17:31:29 +00:00
copilot-swe-agent[bot]
44c65775ad Remove insecure BinaryFormatter code and imports
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-21 17:24:50 +00:00
copilot-swe-agent[bot]
9c31ee28b6 Initial plan 2025-10-21 17:20:45 +00:00
copilot-swe-agent[bot]
5868f91d2d Initial plan 2025-10-21 17:17:46 +00:00
Dimitrij
ee3735904d Merge pull request #2957 from mRemoteNG/copilot/add-anydesk-connection-plugin
Add native AnyDesk protocol support to mRemoteNG
2025-10-20 22:35:51 +01:00
Dimitrij
2b39dde523 Update mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-20 22:34:26 +01:00
Dimitrij
611fdcebe9 Update mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-20 22:34:06 +01:00
Dimitrij
b6c21bb21f Update mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-20 22:31:42 +01:00
copilot-swe-agent[bot]
ed2fee3195 Add AnyDesk documentation and improve implementation comments
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-20 17:39:05 +00:00
copilot-swe-agent[bot]
2d7a897a4d Improve AnyDesk protocol implementation - better window handling and cleanup
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-20 17:36:21 +00:00
copilot-swe-agent[bot]
4f14c78b58 Add AnyDesk protocol support - initial implementation
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-20 17:33:56 +00:00
copilot-swe-agent[bot]
8b8c1d919e Initial plan 2025-10-20 17:27:45 +00:00
Dimitrij
e960353de6 Merge pull request #2955 from mRemoteNG/fix/aikido-security-sast-8911151-ato3
[Aikido] AI Fix for Path traversal attack possible
2025-10-20 10:02:10 +01:00
aikido-autofix[bot]
e563a4c319 fix(security): autofix Path traversal attack possible 2025-10-20 09:01:19 +00:00
Dimitrij
131aef7069 Merge pull request #2954 from mRemoteNG/copilot/add-wsl-connection-protocol
Add WSL protocol support with installation check
2025-10-19 21:55:47 +01:00
Dimitrij
73c3e078d1 Merge branch 'v1.78.2-dev' into copilot/add-wsl-connection-protocol 2025-10-19 21:55:39 +01:00
Dimitrij
8858d55970 Merge pull request #2952 from mRemoteNG/copilot/add-protocol-to-connect-terminal
Add Terminal protocol to connect to command prompt
2025-10-19 21:53:24 +01:00
copilot-swe-agent[bot]
f983f5de02 Improve timeout handling and error messaging in Terminal protocol
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-19 20:40:22 +00:00
copilot-swe-agent[bot]
a8efba1ed4 Fix handle creation wait loop in Terminal protocol
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-19 20:38:36 +00:00
copilot-swe-agent[bot]
8fc3682cc9 Address code review feedback - improve Terminal protocol implementation
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-19 20:36:52 +00:00
copilot-swe-agent[bot]
4ade871aba Add WSL translations to language resource files
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-19 20:34:40 +00:00
copilot-swe-agent[bot]
0a381e0a44 Improve Terminal protocol to use cmd.exe instead of wt.exe
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-19 20:34:22 +00:00
copilot-swe-agent[bot]
895e16d4cb Add Terminal language entries to all language files
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-19 20:33:20 +00:00
copilot-swe-agent[bot]
6657fa3ad0 Update attributes and tests for WSL protocol
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-19 20:32:40 +00:00
copilot-swe-agent[bot]
f5fcc7c206 Add WSL protocol support with installation check
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-19 20:30:48 +00:00
copilot-swe-agent[bot]
435e6f46c1 Add Terminal protocol support to mRemoteNG
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-19 20:29:42 +00:00
copilot-swe-agent[bot]
9ef81e6e72 Initial plan 2025-10-19 20:23:35 +00:00
copilot-swe-agent[bot]
e4b4400aa5 Initial plan 2025-10-19 20:22:29 +00:00
Dimitrij
1d9e082a4a Merge pull request #2950 from mRemoteNG/copilot/fix-log4net-not-found
Fix unhandled exception during startup: log4net assembly not found
2025-10-19 20:34:43 +01:00
copilot-swe-agent[bot]
43d11a5de2 Remove log4net assembly-level attribute causing startup crash
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-19 19:21:14 +00:00
copilot-swe-agent[bot]
94db6caa2c Initial plan 2025-10-19 19:16:54 +00:00
Dimitrij
11366e5d8d upd 2025-10-19 00:43:44 +01:00
Dimitrij
a2967fa999 Merge pull request #2946 from mRemoteNG/copilot/add-margin-to-connection-frame
Fix connection frame color visibility by adding padding to prevent content overlap
2025-10-19 00:36:04 +01:00
copilot-swe-agent[bot]
12920bc6c6 Update IntegratedProgram and PowerShell protocols to respect connection frame padding
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-18 23:20:09 +00:00
copilot-swe-agent[bot]
55fec45b58 Add padding to InterfaceControl to make connection frame color visible
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-18 23:18:15 +00:00
copilot-swe-agent[bot]
045aa631e4 Initial plan 2025-10-18 23:12:37 +00:00
Dimitrij
3cf3a70fc7 fix 2025-10-19 00:01:59 +01:00
Dimitrij
290dab29f1 Merge pull request #2944 from mRemoteNG/copilot/add-attribute-to-store-tags
Add EnvironmentTags attribute to store environment categorization tags
2025-10-18 23:57:05 +01:00
copilot-swe-agent[bot]
79b252f839 Add EnvironmentTags to test helper
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-18 22:51:46 +00:00
copilot-swe-agent[bot]
49bd76f197 Add EnvironmentTags property to connection info
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-18 22:50:02 +00:00
copilot-swe-agent[bot]
54492bedfb Initial plan 2025-10-18 22:39:41 +00:00
Dimitrij
2d1c2df2cb Merge pull request #2941 from mRemoteNG/copilot/add-ctrl-tab-tab-switching
Add Ctrl+Tab keyboard shortcuts for tab navigation
2025-10-18 22:41:54 +01:00
Dimitrij
1187e1c95e Merge pull request #2942 from mRemoteNG/copilot/add-production-color-frame
Add Connection Frame Color feature to visually distinguish production and other environments
2025-10-18 22:41:23 +01:00
copilot-swe-agent[bot]
664a63211e Add visual examples for ConnectionFrameColor feature
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-18 21:35:35 +00:00
copilot-swe-agent[bot]
b7df1e80c8 Add implementation notes and summary for ConnectionFrameColor feature
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-18 21:34:43 +00:00
copilot-swe-agent[bot]
2d6563022e Add documentation for ConnectionFrameColor feature
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-18 21:33:31 +00:00
copilot-swe-agent[bot]
5423c84eed Update CSV serializer to include ConnectionFrameColor field
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-18 21:31:51 +00:00
copilot-swe-agent[bot]
d263fb1b80 Add ConnectionFrameColor feature for visual environment distinction
- Added ConnectionFrameColor enum with None, Red, Yellow, Green, Blue, Purple options
- Added ConnectionFrameColor property to AbstractConnectionRecord with inheritance support
- Updated XML serialization to save/load ConnectionFrameColor
- Added language resources for ConnectionFrameColor
- Implemented visual border rendering in InterfaceControl based on ConnectionFrameColor

Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-18 21:29:59 +00:00
copilot-swe-agent[bot]
c72bd2a107 Consolidate debug log messages to single line
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-18 21:29:29 +00:00
copilot-swe-agent[bot]
8e4627c872 Add debug logging for edge case in tab navigation
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-18 21:27:56 +00:00
copilot-swe-agent[bot]
ccbe99f828 Add edge case handling for tab navigation
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-18 21:25:57 +00:00
copilot-swe-agent[bot]
ee483e3b2b Add Ctrl+Tab keyboard shortcuts for tab navigation
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-18 21:25:20 +00:00
copilot-swe-agent[bot]
5b4b13828b Initial plan 2025-10-18 21:23:39 +00:00
copilot-swe-agent[bot]
4b0bc71fb0 Initial plan 2025-10-18 21:18:55 +00:00
Dimitrij
c28ae335b2 Merge pull request #2939 from mRemoteNG/copilot/fix-sql-injection-issue
Fix SQL injection vulnerabilities via parameterized queries
2025-10-18 22:11:18 +01:00
copilot-swe-agent[bot]
2d3830b747 Clarify comment about database-specific datetime types
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-18 20:38:22 +00:00
copilot-swe-agent[bot]
fe4e205772 Add clarifying comment about datetime parameter usage
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-18 20:36:32 +00:00
copilot-swe-agent[bot]
5e2fc8b0dc Fix SQL injection in SqlConnectionsSaver UpdateUpdatesTable method
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-18 20:33:47 +00:00
copilot-swe-agent[bot]
6403573e36 Fix SQL injection vulnerabilities using parameterized queries
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-18 20:32:21 +00:00
Dimitrij
39b863b57e Merge pull request #2940 from mRemoteNG/fix/aikido-security-sast-8815714-tnsC
[Aikido] AI Fix for Possible command injection via Process.Start
2025-10-18 21:31:15 +01:00
aikido-autofix[bot]
8e5cdcb6e5 fix(security): autofix Possible command injection via Process.Start 2025-10-18 20:29:05 +00:00
copilot-swe-agent[bot]
f5b7cd6fb1 Initial plan 2025-10-18 20:27:45 +00:00
Dimitrij
493ddc1f1f Merge pull request #2932 from mRemoteNG/copilot/fix-https-login-issue
Fix HTTP/HTTPS protocol to support multiple concurrent connections
2025-10-18 21:13:16 +01:00
Dimitrij
5ad338bdcc Merge pull request #2937 from mRemoteNG/renovate/nunit.console-3.x
Update dependency NUnit.Console to 3.20.2
2025-10-18 14:29:58 +01:00
renovate[bot]
d1a1adb994 Update dependency NUnit.Console to 3.20.2 2025-10-18 13:28:59 +00:00
Dimitrij
c879b030ad Merge pull request #2936 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.43.2
2025-10-18 14:28:40 +01:00
Dimitrij
8d1d55c38e Merge pull request #2938 from mRemoteNG/renovate/nunit.consolerunner-3.x
Update dependency NUnit.ConsoleRunner to 3.20.2
2025-10-18 14:28:21 +01:00
renovate[bot]
aef576d290 Update dependency NUnit.ConsoleRunner to 3.20.2 2025-10-18 05:11:19 +00:00
renovate[bot]
e34b0fded3 Update dependency AWSSDK.EC2 to 4.0.43.2 2025-10-18 02:13:16 +00:00
copilot-swe-agent[bot]
14e5e6a715 Add proper thread marshalling and improve cleanup safety
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-17 17:57:51 +00:00
copilot-swe-agent[bot]
0579273964 Fix async deadlock issues and improve error handling
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-17 17:56:04 +00:00
copilot-swe-agent[bot]
5839a836c8 Address code review feedback - improve async handling and safety
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-17 17:54:16 +00:00
copilot-swe-agent[bot]
236fff8014 Fix HTTP/HTTPS protocol to support multiple concurrent connections
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-17 17:51:36 +00:00
copilot-swe-agent[bot]
c1efefae81 Initial plan 2025-10-17 17:46:16 +00:00
Dimitrij
1e5bd44332 Merge pull request #2931 from MaxPlap/feature/VaultOpenbao-Connector
Feature/vault openbao connector
2025-10-17 16:59:18 +01:00
massimo.antonello
7478537f3e Revert RootNode Properties 2025-10-17 16:18:57 +02:00
massimo.antonello
b0f50b825f used winform window for credentials like other connectors 2025-10-17 16:14:06 +02:00
massimo.antonello
7509fcda52 Merge remote-tracking branch 'origin/v1.78.2-dev' into feature/VaultOpenbao-Connector
# Conflicts:
#	mRemoteNG/Language/Language.resx
2025-10-17 14:58:16 +02:00
massimo.antonello
0991cf74ef Secret Engine Property 2025-10-17 14:53:01 +02:00
massimo.antonello
61dd2ec8db started working 2025-10-17 11:49:17 +02:00
massimo.antonello
77643848b6 new properties 2025-10-17 11:48:40 +02:00
Dimitrij
21ab172041 fix 2025-10-17 09:12:57 +01:00
Dimitrij
d6c2c2158a Merge pull request #2930 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.43.1
2025-10-16 23:26:54 +01:00
renovate[bot]
0c50e2d78e Update dependency AWSSDK.EC2 to 4.0.43.1 2025-10-16 21:33:36 +00:00
Dimitrij
64183e8e7d Merge pull request #2929 from mRemoteNG/copilot/fix-panel-visibility-issue
Fix panel visibility issue when opening first connection
2025-10-16 22:32:56 +01:00
copilot-swe-agent[bot]
b9595d1650 Fix panel visibility check to use DockState
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-16 21:28:45 +00:00
copilot-swe-agent[bot]
fa3be420b8 Initial plan 2025-10-16 21:21:53 +00:00
Dimitrij
e2d1929553 Merge pull request #2925 from mRemoteNG/copilot/fix-options-panel-display
Fix Options panel corruption when toggling "Always show panel tabs"
2025-10-16 22:12:01 +01:00
Dimitrij
4f05e8dbe9 Merge pull request #2927 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.Core to 4.0.1.1
2025-10-16 22:11:38 +01:00
renovate[bot]
ea9e79d930 Update dependency AWSSDK.Core to 4.0.1.1 2025-10-16 21:08:10 +00:00
Dimitrij
e1a8d60cd7 Merge pull request #2926 from mRemoteNG/copilot/fix-empty-space-in-tabs
[WIP] Fix empty space issue between settings in Tabs & Panels
2025-10-16 22:07:31 +01:00
copilot-swe-agent[bot]
4e20049ea3 Fix Options panel corruption when toggling 'Always show panel tabs'
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-16 21:05:51 +00:00
copilot-swe-agent[bot]
89ccd33fd1 Fix empty space in Tabs & Panels settings by adjusting control positions
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-16 21:04:59 +00:00
copilot-swe-agent[bot]
4668104e36 Initial plan 2025-10-16 21:01:20 +00:00
copilot-swe-agent[bot]
b4aa30de23 Initial plan 2025-10-16 20:55:28 +00:00
Dimitrij
526cb1fb47 small fixes 2025-10-16 21:50:59 +01:00
Dimitrij
03b395ee15 Merge pull request #2924 from mRemoteNG/copilot/fix-first-connection-panel-issue
Fix connection panel visibility issue on first connection
2025-10-16 21:45:50 +01:00
copilot-swe-agent[bot]
4125344254 Fix panel visibility issue when opening first connection
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-16 20:40:36 +00:00
copilot-swe-agent[bot]
531e117731 Initial plan 2025-10-16 20:31:42 +00:00
Dimitrij
588fdca4d7 Merge pull request #2922 from mRemoteNG/copilot/add-options-dialog-prompt
Add confirmation prompt when closing Options dialog with unsaved changes
2025-10-16 21:24:59 +01:00
Dimitrij
285ca46c18 Merge branch 'v1.78.2-dev' into copilot/add-options-dialog-prompt 2025-10-16 21:24:41 +01:00
Dimitrij
8bdb83eb73 Merge pull request #2919 from mRemoteNG/copilot/fix-empty-option-panel
Fix empty Options panel when Theme is canceled
2025-10-16 21:21:52 +01:00
Dimitrij
d38b71d5ea Merge branch 'v1.78.2-dev' into copilot/fix-empty-option-panel 2025-10-16 21:21:06 +01:00
copilot-swe-agent[bot]
687f06937d Fix change tracking to ignore initial loading and add tests
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-16 20:20:00 +00:00
copilot-swe-agent[bot]
e0799a6772 Add change tracking and prompt for unsaved options
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-16 20:18:11 +00:00
Dimitrij
4943dce1a8 Merge pull request #2921 from mRemoteNG/copilot/fix-options-freezing-issue
[WIP] Fix freezing issue when opening and closing Options
2025-10-16 21:16:33 +01:00
copilot-swe-agent[bot]
8dee5c0d3c Also fix event handler accumulation in OptionsWindow
- Add _isInitialized flag to OptionsWindow to prevent multiple ThemeChanged subscriptions
- Ensures ThemeChanged event is only subscribed once

Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-16 20:14:34 +00:00
Dimitrij
018b07299e Merge pull request #2920 from mRemoteNG/copilot/fix-rdp-settings-alignment
Fix RDP settings fields alignment in Connections options page
2025-10-16 21:13:21 +01:00
copilot-swe-agent[bot]
6f52b82a6d Fix Options dialog freezing when opening/closing multiple times
- Add _isInitialized flag to prevent multiple event subscriptions
- Skip re-initialization in FrmOptions_Load when form is reused
- Properly clean up Application.Idle handler in FormClosing
- Add test to verify form can be shown/hidden multiple times

Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-16 20:13:01 +00:00
copilot-swe-agent[bot]
e1ee905286 Initial plan 2025-10-16 20:12:40 +00:00
copilot-swe-agent[bot]
6853b158ce Fix empty Options panel when Theme is canceled
- Call RevertSettings() on all option pages when Cancel is clicked
- Enhanced ThemePage.RevertSettings() to properly revert theme changes:
  - Store original active theme in LoadSettings
  - Clear modifiedThemes list on cancel
  - Restore original theme selection
- Add VisibleChanged handler to ensure panel is populated when form is shown
- Fixes issue where Options panel was empty after canceling theme selection

Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-16 20:09:02 +00:00
copilot-swe-agent[bot]
d705b8a45d Fix RDP settings fields alignment in Connections options page
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-16 20:08:58 +00:00
copilot-swe-agent[bot]
cfb707d20f Initial plan 2025-10-16 20:07:55 +00:00
copilot-swe-agent[bot]
3aedc938cb Initial plan 2025-10-16 20:05:10 +00:00
copilot-swe-agent[bot]
c70e4c66e6 Initial plan 2025-10-16 20:02:27 +00:00
Dimitrij
b727dbd604 Merge pull request #2918 from mRemoteNG/copilot/fix-connection-options-setting
[WIP] Fix connection options displaying incorrect value
2025-10-16 20:54:27 +01:00
copilot-swe-agent[bot]
cd7f237920 Add Settings.Default.Save() to ConnectionContextMenu disconnect
Found another location where ConfirmCloseConnection is changed without immediately saving.
This ensures all four code paths that change this setting persist it immediately.

Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-16 19:52:57 +00:00
Dimitrij
a1db763060 Merge pull request #2915 from mRemoteNG/copilot/fix-password-visibility-issue
Fix password with special characters not passed correctly to External Tools
2025-10-16 20:52:35 +01:00
copilot-swe-agent[bot]
01e22740ce Fix Options page not showing correct ConfirmCloseConnection value
- Add VisibleChanged event handler to ConnectionsPage to reload settings when page becomes visible
- Save settings immediately when user checks "Don't show this message again" checkbox during connection close
- Ensures Options dialog always displays current setting value, even if changed outside the dialog

Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-16 19:51:02 +00:00
copilot-swe-agent[bot]
6b52b6b062 Initial plan 2025-10-16 19:43:33 +00:00
Dimitrij
6781fd57af Merge pull request #2916 from mRemoteNG/copilot/fix-connection-warning-option
Fix: Set ConfirmCloseConnection to Never when "Do not show this dialog again" is checked
2025-10-16 19:38:33 +01:00
copilot-swe-agent[bot]
8b22c7812d Fix: Set ConfirmCloseConnection to Never instead of decrementing
When user checks "Do not show this dialog again" while closing a connection,
the ConfirmCloseConnection setting should be set to Never (1) instead of being
decremented. The decrement approach was incorrectly changing All (4) to Multiple (3)
instead of Never (1), which didn't match user expectations.

Fixed in:
- ConnectionTab.cs
- ConnectionWindow.cs (2 occurrences)
- ConnectionContextMenu.cs
- frmMain.cs

Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-16 18:27:54 +00:00
copilot-swe-agent[bot]
80c1391361 Fix password passing issue in External Tools
- Changed SetProcessProperties to use Arguments property instead of splitting by space and using ArgumentList
- Added tests to verify passwords with special characters like '=' are passed correctly
- This fixes the issue where passwords like 'Z-3=Wv99/Aq' were being split incorrectly

Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-16 18:26:41 +00:00
copilot-swe-agent[bot]
c77e6b8616 Initial plan 2025-10-16 18:22:19 +00:00
copilot-swe-agent[bot]
e04ace4820 Initial plan 2025-10-16 18:20:37 +00:00
Dimitrij
d89ea96b34 Merge pull request #2904 from mRemoteNG/copilot/fix-encryption-issue-windows-11
Fix connection file encryption failure when password protection is enabled
2025-10-16 19:19:33 +01:00
massimo.antonello
0e83e525fe Properties 2025-10-16 15:06:16 +02:00
copilot-swe-agent[bot]
d5d9a8bf03 Fix PasswordString getter to never return empty password
Added defensive check in PasswordString getter to ensure it returns the default password when _customPassword is empty, even if Password property is true. This prevents encryption failures when Password is set inconsistently.

Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-16 04:12:08 +00:00
copilot-swe-agent[bot]
ecb935868d Add tests for Password property edge cases
Added tests to verify that full file encryption works correctly when Password property is set directly without setting PasswordString, ensuring the fix prevents encryption failures.

Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-16 04:08:46 +00:00
copilot-swe-agent[bot]
d736d3c388 Fix connection file encryption with password protection
Changed CreateProtectedAttribute to use PasswordString as source of truth instead of Password property. This prevents encryption failures when Password property is true but _customPassword is empty.

Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-16 04:07:26 +00:00
copilot-swe-agent[bot]
2520ccd6be Initial plan 2025-10-16 03:57:26 +00:00
Dimitrij
4307dd043e Merge pull request #2903 from mRemoteNG/copilot/fix-about-tab-close-dialog
Fix About tab showing connection close dialog when closed
2025-10-16 04:54:20 +01:00
copilot-swe-agent[bot]
4ae8e4252b Convert frmAbout from Form to BaseWindow to prevent connection close dialog
- Changed frmAbout to inherit from BaseWindow instead of Form
- Updated HelpMenu to show About window in DockPanel
- Removed Form-specific properties (TopMost, StartPosition, FormBorderStyle, etc.)
- Removed Hide() behavior in OnFormClosing to allow proper tab closure
- Added TabText property for proper tab display

Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-16 03:49:45 +00:00
copilot-swe-agent[bot]
995f03d03e Initial plan 2025-10-16 03:38:46 +00:00
Dimitrij
2965f598ac Merge pull request #2902 from mRemoteNG/renovate/protobuf-monorepo
Update dependency Google.Protobuf to 3.33.0
2025-10-16 04:29:48 +01:00
Dimitrij
0ad6f5aea1 Merge pull request #2901 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.43
2025-10-16 04:29:35 +01:00
renovate[bot]
c6fe8ff1f1 Update dependency Google.Protobuf to 3.33.0 2025-10-15 21:36:14 +00:00
renovate[bot]
a3a6917bf8 Update dependency AWSSDK.EC2 to 4.0.43 2025-10-15 21:36:08 +00:00
Dimitrij
1d8484957e fix menu issues and add translations 2025-10-15 19:04:42 +01:00
massimo.antonello
0fc3fb4a58 Merge remote-tracking branch 'origin/v1.78.2-dev' into feature/VaultOpenbao-Connector
# Conflicts:
#	mRemoteNG/Language/Language.de.resx
#	mRemoteNG/Language/Language.el.resx
#	mRemoteNG/Language/Language.es.resx
#	mRemoteNG/Language/Language.fi-FI.resx
#	mRemoteNG/Language/Language.fr.resx
#	mRemoteNG/Language/Language.hu.resx
#	mRemoteNG/Language/Language.it.resx
#	mRemoteNG/Language/Language.ja-JP.resx
#	mRemoteNG/Language/Language.ko-KR.resx
#	mRemoteNG/Language/Language.lt.resx
#	mRemoteNG/Language/Language.nb-NO.resx
#	mRemoteNG/Language/Language.nl.resx
#	mRemoteNG/Language/Language.pl.resx
#	mRemoteNG/Language/Language.pt-BR.resx
#	mRemoteNG/Language/Language.pt.resx
#	mRemoteNG/Language/Language.resx
#	mRemoteNG/Language/Language.sv-SE.resx
#	mRemoteNG/Language/Language.ta.resx
#	mRemoteNG/Language/Language.tr-TR.resx
#	mRemoteNG/Language/Language.uk.resx
#	mRemoteNG/Language/Language.zh-CN.resx
#	mRemoteNG/Language/Language.zh-TW.resx
2025-10-15 14:52:58 +02:00
Dimitrij
6ab67ee844 Merge pull request #2900 from mRemoteNG/copilot/fix-report-bug-link
Add "Report a Bug" menu item to Help menu and update bug report URL to GitHub issues
2025-10-15 11:11:33 +01:00
copilot-swe-agent[bot]
11fa059b1b Add Report a Bug menu item and update bug URL to GitHub issues
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-15 10:01:51 +00:00
copilot-swe-agent[bot]
847115abd6 Initial plan 2025-10-15 09:56:41 +00:00
Dimitrij
a5a0a310ce Update CREDITS.md
upd
2025-10-14 22:33:36 +01:00
Dimitrij
a72582b10f Merge pull request #2898 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.42
2025-10-14 22:06:24 +01:00
Dimitrij
929e22dc5a Merge pull request #2897 from mRemoteNG/renovate/dotnet-monorepo
Update dependency Microsoft.Extensions.DependencyModel to 9.0.10
2025-10-14 22:06:12 +01:00
renovate[bot]
ec4bca9bc0 Update dependency Microsoft.Extensions.DependencyModel to 9.0.10 2025-10-14 21:05:25 +00:00
Dimitrij
0648541ce1 lib update 2025-10-14 22:04:45 +01:00
renovate[bot]
77529f29f5 Update dependency AWSSDK.EC2 to 4.0.42 2025-10-14 20:49:17 +00:00
Dimitrij
02cf1739c0 Fix language msgs 2025-10-14 19:51:16 +01:00
massimo.antonello
903a5f5799 begin 2025-10-14 17:57:05 +02:00
Dimitrij
ccd955ca7e Merge pull request #2895 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.41
2025-10-14 08:12:08 +01:00
renovate[bot]
1af3b03fa8 Update dependency AWSSDK.EC2 to 4.0.41 2025-10-13 21:01:18 +00:00
Dimitrij
cc28186f73 Merge pull request #2894 from mRemoteNG/copilot/fix-options-panel-reopening
Fix Options panel not reopening after closing by clicking X on tab
2025-10-13 10:20:53 +01:00
Dimitrij
103bc8bd8b Update README.md
upd
2025-10-12 18:41:13 +01:00
copilot-swe-agent[bot]
9bcf7af467 Fix Options panel not reopening after closing
Set HideOnClose = true for OptionsWindow to prevent disposal when closed, and ensure embedded form is shown when window becomes visible again.

Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-12 17:02:36 +00:00
copilot-swe-agent[bot]
8d10980875 Initial plan 2025-10-12 16:55:17 +00:00
Dimitrij
57d4945d71 Merge pull request #2890 from mRemoteNG/renovate/softprops-action-gh-release-digest
Update softprops/action-gh-release digest to 6da8fa9
2025-10-11 18:53:18 +01:00
renovate[bot]
11febb93bd Update softprops/action-gh-release digest to 6da8fa9 2025-10-11 17:51:41 +00:00
Dimitrij
cc9b83b0f8 Merge pull request #2889 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2025-10-10 21:49:40 +01:00
renovate[bot]
54386924d7 Update aws-sdk-net monorepo 2025-10-10 20:47:56 +00:00
Dimitrij
35417bc05d Merge pull request #2888 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2025-10-10 09:04:39 +01:00
renovate[bot]
8b212dd87f Update aws-sdk-net monorepo 2025-10-09 23:04:22 +00:00
Dimitrij
488035e124 Merge pull request #2887 from mRemoteNG/copilot/add-warning-for-disconnect-option
Add confirmation dialog when disconnecting connections from context menu
2025-10-09 19:27:33 +01:00
copilot-swe-agent[bot]
9129edb725 Add confirmation dialog for disconnect operation from context menu
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-09 17:57:17 +00:00
copilot-swe-agent[bot]
6f3882e0ca Initial plan 2025-10-09 17:51:26 +00:00
Dimitrij
f810f6aa85 Merge pull request #2885 from mRemoteNG/copilot/add-dependency-check-texts
Add dependency check texts to language pack for internationalization
2025-10-09 11:15:37 +01:00
Dimitrij
6f13111c4c Merge pull request #2886 from mRemoteNG/copilot/add-reenable-checkbox-option
[WIP] Add option to reenable 'Do not show this message again' checkbox
2025-10-09 11:13:43 +01:00
copilot-swe-agent[bot]
fcca1a1fa3 Add dependency check texts to language pack
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-09 10:12:02 +00:00
copilot-swe-agent[bot]
ac0aa9e33b Enable ConfirmCloseConnection setting in Options dialog
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-09 10:10:50 +00:00
copilot-swe-agent[bot]
6109ff7783 Initial plan 2025-10-09 10:05:22 +00:00
copilot-swe-agent[bot]
4d2d24efb3 Initial plan 2025-10-09 10:04:47 +00:00
Dimitrij
e2b7081b9e Merge pull request #2883 from heing/v1.78.2-dev
Improve 1Password logging
2025-10-09 10:47:55 +01:00
Dimitrij
6ba7fedc18 Merge pull request #2882 from mRemoteNG/renovate/gherkin-36.x
Update dependency Gherkin to v36
2025-10-09 10:47:40 +01:00
Hein Gustavsen
3973fae8ef Improve 1Password logging 2025-10-09 09:44:08 +02:00
renovate[bot]
77752f494f Update dependency Gherkin to v36 2025-10-09 05:26:22 +00:00
Simon Monai
71574b62cb Update README.md - Minimum Requirements
Update Minimum Requirements download links and formatting to match results of #2870. 

Additionally fixed one typo.
2025-10-08 23:06:49 +02:00
Dimitrij
546ba16366 Merge pull request #2875 from mRemoteNG/renovate/cucumber.messages-30.x
Update dependency Cucumber.Messages to 30.1.0
2025-10-08 16:57:47 +01:00
renovate[bot]
f61ca2ad17 Update dependency Cucumber.Messages to 30.1.0 2025-10-08 15:55:24 +00:00
Dimitrij
781afd1c3b Merge pull request #2874 from heing/v1.78.2-dev
Add 1Password integration
2025-10-08 16:54:47 +01:00
Hein Gustavsen
2bfd8495ab Fix formatting in CHANGELOG.md
Removed an empty line in the Added section.
2025-10-08 16:02:28 +02:00
Hein Gustavsen
094fa65ad6 Update CHANGELOG, add 1Password integration 2025-10-08 16:01:01 +02:00
Hein Gustavsen
ec2a652d74 Add missing ECPOnePassword in Language.Designer.cs 2025-10-08 14:21:00 +02:00
Hein Gustavsen
1b55645b81 Add 1Password integration 2025-10-08 13:56:25 +02:00
Dimitrij
5c02581c81 Merge pull request #2872 from mRemoteNG/renovate/microsoft.data.sqlclient-6.x
Update dependency Microsoft.Data.SqlClient to 6.1.2
2025-10-08 07:54:42 +01:00
renovate[bot]
62cc4780ca Update dependency Microsoft.Data.SqlClient to 6.1.2 2025-10-08 05:57:32 +00:00
Dimitrij
8f26d57f40 NB release 2025-10-08 00:01:23 +01:00
Dimitrij
3bd2fe889a upd to for check x64 vc++ 2025-10-07 23:56:48 +01:00
Dimitrij
492a2629c2 Replace to x64 version 2025-10-07 23:32:21 +01:00
Dimitrij
b64ddf32ff fix 2025-10-07 23:31:28 +01:00
Dimitrij
e86a550985 Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev 2025-10-07 23:31:12 +01:00
Dimitrij
fcccdacb99 Remove to update 2025-10-07 23:31:05 +01:00
Dimitrij
487de4c29b Merge pull request #2871 from simonai1254/v1.78.2-dev
Update README.md featuring Icon Project
2025-10-07 22:36:13 +01:00
Dimitrij
d36c6cb067 Merge pull request #2869 from mRemoteNG/copilot/fix-color-selection-for-panel-tabs
Fix Color property converter and add missing Display category attributes for TabColor
2025-10-07 22:31:30 +01:00
Simon Monai
a4b704252b Update README.md
Include Link to Fancy Icon Collection of @bearlikelion
2025-10-07 23:29:12 +02:00
Dimitrij
93e8d26a75 Merge branch 'v1.78.2-dev' into copilot/fix-color-selection-for-panel-tabs 2025-10-07 22:26:04 +01:00
copilot-swe-agent[bot]
0a3ecaac64 Fix Color property converter and add missing category attributes
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 21:17:26 +00:00
Simon Monai
ea6b762021 Update README.md
Add Visual C++ Dependency back in
2025-10-07 23:16:12 +02:00
Dimitrij
265a43e31c fix 2025-10-07 22:12:15 +01:00
copilot-swe-agent[bot]
6d156586ac Initial plan 2025-10-07 21:11:51 +00:00
Simon Monai
68e3f607a3 Update README.md
Fix Formatting Issue with Dependency Link
2025-10-07 22:40:48 +02:00
Dimitrij
0b240a3902 Merge pull request #2864 from mRemoteNG/copilot/add-color-support-for-connection-folders
Add Color property to connections and folders with inheritance support
2025-10-07 21:25:20 +01:00
Dimitrij
4082761606 Merge branch 'v1.78.2-dev' into copilot/add-color-support-for-connection-folders 2025-10-07 21:24:50 +01:00
Dimitrij
e68c42ba64 Merge pull request #2867 from mRemoteNG/copilot/fix-tab-color-selection-error
[WIP] Fix tab color dropdown selection error
2025-10-07 21:17:04 +01:00
copilot-swe-agent[bot]
347546ee0e Add TabColorConverter to fix Color to String conversion issue
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 20:15:32 +00:00
Dimitrij
ca717d6b80 Merge pull request #2863 from mRemoteNG/copilot/add-ard-connection-support
Add ARD (Apple Remote Desktop) protocol support for macOS connections
2025-10-07 21:12:17 +01:00
copilot-swe-agent[bot]
5d623d80eb Initial plan 2025-10-07 20:10:54 +00:00
Dimitrij
a2edbd9934 Merge pull request #2865 from mRemoteNG/copilot/add-connection-tab-colors
Add configurable connection tab colors to distinguish environments
2025-10-07 21:02:05 +01:00
copilot-swe-agent[bot]
5830f39d50 Add documentation for Tab Color feature
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 19:51:26 +00:00
copilot-swe-agent[bot]
0aa0b59635 Add documentation for Color property in folders and inheritance
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 19:50:10 +00:00
copilot-swe-agent[bot]
3c6a485647 Add TabColor property to connection info and implement tab coloring
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 19:49:32 +00:00
copilot-swe-agent[bot]
bbe1fa8416 Add Color property to Language.Designer.cs and add inheritance tests
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 19:49:21 +00:00
copilot-swe-agent[bot]
ac4469bb4a Add ARD protocol support to UI, port scanning, and tests
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 19:48:57 +00:00
copilot-swe-agent[bot]
9e61e8eafa Add Color property to connections and folders with inheritance support
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 19:47:07 +00:00
copilot-swe-agent[bot]
b193199268 Add ARD protocol to VNC property attributes and default port handling
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 19:45:38 +00:00
copilot-swe-agent[bot]
f8b7d37af1 Add ARD (Apple Remote Desktop) protocol support
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 19:42:31 +00:00
Dimitrij
b3e9202d72 Merge pull request #2862 from mRemoteNG/copilot/fix-unhandled-exception-panel-closure
Fix unhandled exception when closing panel with active connections
2025-10-07 20:40:35 +01:00
copilot-swe-agent[bot]
0f819ade56 Initial plan 2025-10-07 19:39:14 +00:00
copilot-swe-agent[bot]
d682afcde2 Initial plan 2025-10-07 19:39:03 +00:00
copilot-swe-agent[bot]
e67754ee9f Fix unhandled exception when closing panel with connections
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 19:35:18 +00:00
copilot-swe-agent[bot]
4897771fbf Initial plan 2025-10-07 19:35:06 +00:00
copilot-swe-agent[bot]
4128f3404a Initial plan 2025-10-07 19:31:35 +00:00
Dimitrij
7bc25ceb38 Merge pull request #2861 from mRemoteNG/copilot/add-autofocus-to-password-field
[WIP] Add autofocus to password field on startup
2025-10-07 20:29:40 +01:00
Dimitrij
f77f0f5e04 Merge pull request #2859 from mRemoteNG/copilot/fix-username-field-visibility
Fix Username field visibility for External Tool protocol
2025-10-07 20:28:48 +01:00
copilot-swe-agent[bot]
0e666efaad Add autofocus to password field in FrmPassword form
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 19:26:14 +00:00
copilot-swe-agent[bot]
e2893b9516 Add IntApp to Username property supported protocols
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 19:24:01 +00:00
copilot-swe-agent[bot]
fb86b13948 Initial plan 2025-10-07 19:22:15 +00:00
copilot-swe-agent[bot]
e22cc6921d Initial plan 2025-10-07 19:19:17 +00:00
Dimitrij
42fdd91206 Merge pull request #2857 from mRemoteNG/copilot/fix-default-panel-opening
Fix Update panel auto-loading on startup
2025-10-07 20:14:21 +01:00
copilot-swe-agent[bot]
2329d95002 Remove automatic Update panel display at startup
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 16:49:36 +00:00
copilot-swe-agent[bot]
8dda6ba13f Fix Update panel auto-checking on load
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 16:47:29 +00:00
copilot-swe-agent[bot]
156e2b8056 Initial plan 2025-10-07 16:41:34 +00:00
Dimitrij
aa48324b6d NB release 2025-10-07 17:10:32 +01:00
Dimitrij
d2b05ef7c3 renaming of folder to better represent its purpose 2025-10-07 17:01:48 +01:00
Dimitrij
75545e60b3 Merge pull request #2855 from mRemoteNG/copilot/fix-login-name-display-issue
Fix missing Username field for HTTP and HTTPS protocols
2025-10-07 17:00:27 +01:00
Dimitrij
b3d0b30b56 Merge pull request #2854 from mRemoteNG/copilot/refactor-settings-popup-to-panel
Refactor settings dialog to dockable panel for consistency
2025-10-07 16:54:54 +01:00
Dimitrij
3ed3729fc2 Merge pull request #2852 from mRemoteNG/copilot/fix-object-deserialization-vulnerability
Fix XML External Entity (XXE) vulnerability in XML deserialization
2025-10-07 16:51:36 +01:00
copilot-swe-agent[bot]
cb7ba46be6 Add comprehensive security tests for SecureXmlHelper
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 15:24:35 +00:00
copilot-swe-agent[bot]
933b21598e Add Username field support for HTTP and HTTPS protocols
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 15:23:46 +00:00
copilot-swe-agent[bot]
7a8442d9ea Handle FrmOptions visibility and window lifecycle
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 15:23:39 +00:00
copilot-swe-agent[bot]
c405186533 Update test files to use SecureXmlHelper for consistency
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 15:23:39 +00:00
copilot-swe-agent[bot]
5d150115a8 Create OptionsWindow for docked settings panel
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 15:22:20 +00:00
copilot-swe-agent[bot]
26bc38cf8c Add SecureXmlHelper and update all XML deserialization to prevent XXE attacks
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 15:20:14 +00:00
copilot-swe-agent[bot]
8f769cdda3 Initial plan 2025-10-07 15:16:42 +00:00
copilot-swe-agent[bot]
6b35cd3aee Initial plan 2025-10-07 15:16:03 +00:00
copilot-swe-agent[bot]
4ae1281a3a Initial plan 2025-10-07 15:06:32 +00:00
Dimitrij
9984d4bc8f Merge pull request #2850 from mRemoteNG/copilot/fix-d3d20276-2cee-4d8f-af65-24f3320db36a
Fix password dialog appearing behind splash screen on startup
2025-10-07 16:05:53 +01:00
Dimitrij
c987ee9fd3 Merge pull request #2851 from mRemoteNG/copilot/fix-3efd67f4-15e3-43e9-9b58-50941b183621
Fix path traversal vulnerability in file operations
2025-10-07 16:04:10 +01:00
copilot-swe-agent[bot]
a031e6f5f7 Add path validation to prevent path traversal attacks
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 14:04:00 +00:00
copilot-swe-agent[bot]
da386c3119 Close splash screen before loading connections to fix password dialog z-order
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-10-07 14:02:00 +00:00
copilot-swe-agent[bot]
bfd29fc0fc Initial plan 2025-10-07 13:57:18 +00:00
copilot-swe-agent[bot]
9617776be1 Initial plan 2025-10-07 13:55:19 +00:00
Dimitrij
765f2abc40 Merge pull request #2848 from mRemoteNG/renovate/softprops-action-gh-release-digest
chore(deps): update softprops/action-gh-release digest to aec2ec5
2025-10-07 08:12:44 +01:00
Dimitrij
519bc42e0a Merge pull request #2847 from mRemoteNG/renovate/nunit3testadapter-5.x
chore(deps): update dependency nunit3testadapter to 5.2.0
2025-10-07 08:12:03 +01:00
Dimitrij
6537e6bce6 Merge pull request #2846 from mRemoteNG/renovate/reportgenerator-5.x
chore(deps): update dependency reportgenerator to 5.4.17
2025-10-07 08:11:47 +01:00
renovate[bot]
494aff1bd3 chore(deps): update softprops/action-gh-release digest to aec2ec5 2025-10-07 04:37:18 +00:00
renovate[bot]
927527b888 chore(deps): update dependency nunit3testadapter to 5.2.0 2025-10-06 22:40:49 +00:00
renovate[bot]
08054e4873 chore(deps): update dependency reportgenerator to 5.4.17 2025-10-06 22:40:43 +00:00
Dimitrij
a092fef575 Merge pull request #2845 from mRemoteNG/renovate/microsoft.web.webview2-1.x
chore(deps): update dependency microsoft.web.webview2 to 1.0.3537.50
2025-10-06 17:35:02 +01:00
renovate[bot]
2808b3d3da chore(deps): update dependency microsoft.web.webview2 to 1.0.3537.50 2025-10-06 16:28:54 +00:00
Dimitrij
ef3b236d73 fix element placement for #2842 2025-10-06 17:27:25 +01:00
Dimitrij
26f0365026 Upd translation 2025-10-06 13:51:34 +01:00
Dimitrij
25ebfee6e2 Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev 2025-10-06 13:51:16 +01:00
Dimitrij
c1ada03db1 Fix for #2841 - verify if vc++ distributed x86 installed 2025-10-06 13:51:08 +01:00
Dimitrij
0b17360346 Update README.md
upd
2025-10-06 11:54:40 +01:00
Dimitrij
da18b37a54 Fix markdown formatting for Nightly release badge
upd
2025-10-06 11:52:36 +01:00
Dimitrij
4cf040c01e Update README.md
upd
2025-10-06 11:50:38 +01:00
Dimitrij
95b77515dc Update Nightly build link and download badge
upd
2025-10-06 11:47:59 +01:00
Dimitrij
65bbd8ca05 Update README.md
upd
2025-10-06 11:44:36 +01:00
Dimitrij
dbba954522 small fixes 2025-10-03 22:19:39 +01:00
Dimitrij
26d46be243 dependencies are deprecated 2025-10-03 22:18:08 +01:00
Dimitrij
135f8290ec Merge pull request #2839 from mRemoteNG/fix/aikido-security-sast-8173389-4nsd
[Aikido] AI Fix for Possible command injection via Process.Start
2025-10-03 22:00:36 +01:00
Dimitrij
a8b4e1178d Merge pull request #2837 from mRemoteNG/renovate/cucumber.messages-30.x
chore(deps): update dependency cucumber.messages to v30
2025-10-03 22:00:14 +01:00
Dimitrij
8bdf66ef02 Merge pull request #2838 from mRemoteNG/fix/aikido-security-sast-8173368-kjEd
[Aikido] AI Fix for Potential SQL injection via string-based query concatenation
2025-10-03 21:59:48 +01:00
aikido-autofix[bot]
5209b08709 fix(security): autofix Possible command injection via Process.Start 2025-10-03 20:58:27 +00:00
aikido-autofix[bot]
933247dc2f fix(security): autofix Potential SQL injection via string-based query concatenation 2025-10-03 20:57:07 +00:00
renovate[bot]
4e43bf9d8f chore(deps): update dependency cucumber.messages to v30 2025-10-03 16:58:47 +00:00
Dimitrij
0f6a5816db Merge pull request #2835 from mRemoteNG/renovate/aws-sdk-net-monorepo
chore(deps): update aws-sdk-net monorepo
2025-10-02 22:31:54 +01:00
Dimitrij
8021678995 Merge pull request #2836 from mRemoteNG/renovate/major-vstest-monorepo
chore(deps): update dependency microsoft.net.test.sdk to v18
2025-10-02 22:24:41 +01:00
renovate[bot]
147b5edec4 chore(deps): update dependency microsoft.net.test.sdk to v18 2025-10-02 11:24:09 +00:00
renovate[bot]
2edbf3222e chore(deps): update aws-sdk-net monorepo 2025-10-01 22:05:23 +00:00
Dimitrij
f6bf8c229c Merge pull request #2834 from mRemoteNG/renovate/aws-sdk-net-monorepo
chore(deps): update aws-sdk-net monorepo
2025-09-30 10:16:42 +01:00
renovate[bot]
c30b90af44 chore(deps): update aws-sdk-net monorepo 2025-09-29 21:07:53 +00:00
Dimitrij
3b72e7af3d Merge pull request #2832 from mRemoteNG/renovate/aws-sdk-net-monorepo
chore(deps): update dependency awssdk.ec2 to 4.0.40.1
2025-09-29 22:07:17 +01:00
renovate[bot]
37004647eb chore(deps): update dependency awssdk.ec2 to 4.0.40.1 2025-09-29 16:46:13 +00:00
Dimitrij
c85cb84e31 Merge pull request #2829 from mRemoteNG/renovate/configure
chore: Configure Renovate
2025-09-29 17:45:28 +01:00
renovate[bot]
d504143d7e Add renovate.json 2025-09-26 18:08:44 +00:00
Dimitrij
db1c7b9708 Merge pull request #2827 from mRemoteNG/renovate/chromiumembeddedframework.runtime.win-arm64-140.x
chore(deps): update dependency chromiumembeddedframework.runtime.win-arm64 to v140
2025-09-25 09:36:29 +01:00
Dimitrij
064cb34705 Merge pull request #2828 from mRemoteNG/renovate/chromiumembeddedframework.runtime.win-x64-140.x
chore(deps): update dependency chromiumembeddedframework.runtime.win-x64 to v140
2025-09-25 09:36:09 +01:00
Dimitrij
02c442ef99 Merge pull request #2825 from mRemoteNG/renovate/reportgenerator-5.x
chore(deps): update dependency reportgenerator to 5.4.16
2025-09-25 09:35:54 +01:00
renovate[bot]
e358cafb3f chore(deps): update dependency reportgenerator to 5.4.16 2025-09-24 20:44:07 +00:00
renovate[bot]
40e447bc05 chore(deps): update dependency chromiumembeddedframework.runtime.win-x64 to v140 2025-09-24 15:31:30 +00:00
renovate[bot]
f0bac9ee78 chore(deps): update dependency chromiumembeddedframework.runtime.win-arm64 to v140 2025-09-24 15:31:22 +00:00
Dimitrij
a75fbad0ea Merge pull request #2823 from mRemoteNG/renovate/awssdk.core-4.x
chore(deps): update dependency awssdk.core to 4.0.0.29
2025-09-23 23:57:32 +01:00
renovate[bot]
ae5a919421 chore(deps): update dependency awssdk.core to 4.0.0.29 2025-09-23 22:57:10 +00:00
Dimitrij
749cb7578a Merge pull request #2824 from mRemoteNG/renovate/awssdk.ec2-4.x
chore(deps): update dependency awssdk.ec2 to 4.0.40
2025-09-23 23:56:38 +01:00
renovate[bot]
622ea20819 chore(deps): update dependency awssdk.ec2 to 4.0.40 2025-09-23 22:08:58 +00:00
Dimitrij
a9374a5eb1 Merge pull request #2822 from mRemoteNG/copilot/fix-3abc8ac6-985e-4856-8afb-a91a0dca6d12
Remove insecure pull_request_target trigger from add_PR_2_chlog.yml workflow
2025-09-23 16:25:10 +01:00
copilot-swe-agent[bot]
1b78b68e33 Remove insecure pull_request_target trigger from workflow
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-09-23 09:30:17 +00:00
copilot-swe-agent[bot]
3e97fe0490 Initial plan 2025-09-23 09:26:03 +00:00
Dimitrij
2bb34607cc Merge pull request #2813 from mRemoteNG/renovate/gherkin-35.x
chore(deps): update dependency gherkin to 35.1.0
2025-09-21 22:08:02 +01:00
Dimitrij
fe7fca180e Merge pull request #2814 from mRemoteNG/renovate/reportgenerator-5.x
chore(deps): update dependency reportgenerator to 5.4.14
2025-09-21 22:07:49 +01:00
renovate[bot]
0a86b45d59 chore(deps): update dependency reportgenerator to 5.4.14 2025-09-21 13:56:20 +00:00
Dimitrij
62e0dd365b Merge pull request #2788 from mRemoteNG/renovate/reportgenerator-5.x
chore(deps): update dependency reportgenerator to 5.4.14
2025-09-21 14:55:49 +01:00
renovate[bot]
b39c561b72 chore(deps): update dependency gherkin to 35.1.0 2025-09-20 13:43:33 +00:00
Dimitrij
02d099629f Merge pull request #2812 from ciis0/cschulz/fix-refocus
Fix RDP refocus issues
2025-09-19 18:12:15 +01:00
Dimitrij
0f55cd2395 Update mRemoteNG/Connection/Protocol/RDP/RdpProtocol.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-19 11:39:23 +01:00
Christoph Schulz
119451e4f6 Fix RDP refocus issues
Fixes #1535
2025-09-19 09:25:40 +02:00
Dimitrij
0f6d4e5760 Merge pull request #2811 from mRemoteNG/renovate/awssdk.ec2-4.x
chore(deps): update dependency awssdk.ec2 to 4.0.39
2025-09-18 23:43:55 +01:00
renovate[bot]
452ed6a754 chore(deps): update dependency awssdk.ec2 to 4.0.39 2025-09-18 22:07:33 +00:00
Dimitrij
40928b8ab0 Merge pull request #2808 from mRemoteNG/renovate/awssdk.ec2-4.x
chore(deps): update dependency awssdk.ec2 to 4.0.38
2025-09-17 21:46:43 +01:00
renovate[bot]
f1ed380b42 chore(deps): update dependency awssdk.ec2 to 4.0.38 2025-09-17 20:34:27 +00:00
Dimitrij
e28ae8f2ba Merge pull request #2805 from mRemoteNG/renovate/awssdk.core-4.x
chore(deps): update dependency awssdk.core to 4.0.0.28
2025-09-17 10:57:26 +01:00
renovate[bot]
d8c6d9d558 chore(deps): update dependency awssdk.core to 4.0.0.28 2025-09-17 09:56:31 +00:00
Dimitrij
607123f0be Merge pull request #2806 from mRemoteNG/renovate/awssdk.ec2-4.x
chore(deps): update dependency awssdk.ec2 to 4.0.37.1
2025-09-17 10:56:00 +01:00
Dimitrij
0ff72ae665 Merge pull request #2807 from Greenie0701/arm64_dotnet
Add ARM64 support for .NET runtime detection
2025-09-17 10:55:37 +01:00
Dimitrij
552cee1c24 Update mRemoteNG/App/Update/DotNetRuntimeCheck.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-17 10:55:28 +01:00
MS-GITS
44f05a2968 Add ARM64 support for .NET runtime detection 2025-09-17 14:52:54 +05:30
MS-GITS
244fbf56d1 Add ARM64 support for .NET runtime detection 2025-09-17 14:50:41 +05:30
renovate[bot]
24845039f1 chore(deps): update dependency awssdk.ec2 to 4.0.37.1 2025-09-16 21:07:05 +00:00
Dimitrij
2e5be847ce Merge pull request #2804 from mRemoteNG/fix/aikido-security-sast--7464421-c51u
[Aikido] AI Fix for Path traversal attack possible
2025-09-16 20:44:12 +01:00
Dimitrij
9e947f7a36 Merge pull request #2803 from mRemoteNG/fix/aikido-security-sast--7464399-kZ9j
[Aikido] AI Fix for Path traversal attack possible
2025-09-16 20:43:59 +01:00
Dimitrij
87a3c60330 Merge pull request #2802 from mRemoteNG/fix/aikido-security-sast--7464375-mF7U
[Aikido] AI Fix for Path traversal attack possible
2025-09-16 20:43:47 +01:00
Dimitrij
0d460d543b Merge pull request #2801 from mRemoteNG/fix/aikido-security-sast--7464275-6ovw
[Aikido] AI Fix for Path traversal attack possible
2025-09-16 20:43:33 +01:00
Dimitrij
eb535cad2c Merge pull request #2800 from mRemoteNG/fix/aikido-security-sast--7464234-hobo
[Aikido] AI Fix for Path traversal attack possible
2025-09-16 20:43:01 +01:00
Dimitrij
eff00033b0 Merge pull request #2799 from mRemoteNG/fix/aikido-security-sast--7464163-cUFL
[Aikido] AI Fix for 3rd party Github Actions should be pinned
2025-09-16 20:42:19 +01:00
aikido-autofix[bot]
b591f97297 fix(security): autofix Path traversal attack possible 2025-09-16 16:09:27 +00:00
aikido-autofix[bot]
9501bb4428 fix(security): autofix Path traversal attack possible 2025-09-16 16:09:01 +00:00
aikido-autofix[bot]
4e9757c743 fix(security): autofix Path traversal attack possible 2025-09-16 16:08:34 +00:00
aikido-autofix[bot]
8f35b2ef71 fix(security): autofix Path traversal attack possible 2025-09-16 16:05:48 +00:00
aikido-autofix[bot]
3782eeeaf1 fix(security): autofix Path traversal attack possible 2025-09-16 16:04:39 +00:00
aikido-autofix[bot]
ec78de7a74 fix(security): autofix 3rd party Github Actions should be pinned 2025-09-16 16:02:43 +00:00
Dimitrij
bd1f311d09 CET compatibility
NB release
2025-09-16 12:03:15 +01:00
Dimitrij
31bf36db89 Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev 2025-09-16 11:09:45 +01:00
Dimitrij
a96d344c22 adding missed தமிழ் (ta) Translation update #2690 from 1.77.3 branch 2025-09-16 11:09:37 +01:00
Dimitrij
dac4988514 Merge pull request #2797 from mRemoteNG/renovate/newtonsoft.json-13.x
chore(deps): update dependency newtonsoft.json to 13.0.4
2025-09-16 10:33:04 +01:00
renovate[bot]
a1f50f152c chore(deps): update dependency newtonsoft.json to 13.0.4 2025-09-16 08:54:55 +00:00
Dimitrij
06a47f4a06 Merge pull request #2795 from simonai1254/patch-1
Update minimum requirements
2025-09-16 09:40:32 +01:00
Simon Monai
f7aa9f7b92 Update README.md - Minimum Requirements
Update minimum Requirements to properly show .Net 9.0 and clarify Terminal Service Client requirements
2025-09-16 02:11:41 +02:00
Simon Monai
894b11704c Update README.md
Update .NET Runtime Requirements (9.0) for new version and remove Visual C++ Redistributables as the appear to no longer be necessary
2025-09-16 02:04:51 +02:00
Dimitrij
5b1b6ddc73 Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev 2025-09-15 23:40:21 +01:00
Dimitrij
baa5abab2e clenup 2025-09-15 23:40:13 +01:00
Dimitrij
1b2ae29f4a Merge pull request #2793 from mRemoteNG/renovate/gherkin-35.x
chore(deps): update dependency gherkin to v35
2025-09-15 23:34:18 +01:00
Dimitrij
99a2b968d5 Merge pull request #2792 from mRemoteNG/renovate/actions-checkout-5.x
chore(deps): update actions/checkout action to v5
2025-09-15 23:34:04 +01:00
Dimitrij
029672a907 lib update, NB release 2025-09-15 23:32:42 +01:00
Dimitrij
31ecbaa977 .Net version detection fix
optimization
2025-09-15 23:32:06 +01:00
Dimitrij
6580e1b6db Enhance Build_mR-NB.yml with workflow_dispatch
Add manual dispatch option with release flag to workflow
2025-09-15 21:14:20 +01:00
renovate[bot]
16bbe4ccc0 chore(deps): update dependency gherkin to v35 2025-09-14 09:57:40 +00:00
renovate[bot]
6ea3642700 chore(deps): update actions/checkout action to v5 2025-09-14 09:57:36 +00:00
renovate[bot]
b8270d2264 chore(deps): update dependency reportgenerator to 5.4.13 2025-09-13 21:15:42 +00:00
Dimitrij
7b7fe1b062 Merge pull request #2787 from mRemoteNG/renovate/google.protobuf-3.x
chore(deps): update dependency google.protobuf to 3.32.1
2025-09-13 21:44:52 +01:00
Dimitrij
e67c97cc21 Update workflow to trigger on specific branch 2025-09-13 21:40:37 +01:00
Dimitrij
1033214658 Merge pull request #2786 from mRemoteNG/renovate/awssdk.core-4.x
chore(deps): update dependency awssdk.core to 4.0.0.27
2025-09-13 21:38:16 +01:00
Dimitrij
55552ad118 Update workflow to handle default branch and PR merges 2025-09-13 21:32:40 +01:00
renovate[bot]
552c53b15d chore(deps): update dependency google.protobuf to 3.32.1 2025-09-13 19:30:23 +00:00
renovate[bot]
278fc30ae8 chore(deps): update dependency awssdk.core to 4.0.0.27 2025-09-13 19:30:19 +00:00
Dimitrij
80fb676763 Merge pull request #2779 from mRemoteNG/renovate/system.io.pipelines-9.x
chore(deps): update dependency system.io.pipelines to 9.0.9
2025-09-13 15:31:41 +01:00
Dimitrij
c511fd1895 Merge branch 'v1.78.2-dev' into renovate/system.io.pipelines-9.x 2025-09-13 15:31:35 +01:00
Dimitrij
e43a644eb0 Merge pull request #2777 from mRemoteNG/renovate/system.drawing.common-9.x
chore(deps): update dependency system.drawing.common to 9.0.9
2025-09-13 15:30:57 +01:00
Dimitrij
70c16caf6b Merge branch 'v1.78.2-dev' into renovate/system.drawing.common-9.x 2025-09-13 15:30:50 +01:00
Dimitrij
21dcfc9435 Merge pull request #2775 from mRemoteNG/renovate/system.diagnostics.eventlog-9.x
chore(deps): update dependency system.diagnostics.eventlog to 9.0.9
2025-09-13 15:29:45 +01:00
Dimitrij
56ff019185 Merge branch 'v1.78.2-dev' into renovate/system.diagnostics.eventlog-9.x 2025-09-13 15:29:39 +01:00
Dimitrij
44a6cb31aa Merge pull request #2780 from mRemoteNG/renovate/system.management-9.x
chore(deps): update dependency system.management to 9.0.9
2025-09-13 15:28:14 +01:00
Dimitrij
a01ebdaa9d Merge pull request #2781 from mRemoteNG/renovate/system.reflection.metadata-9.x
chore(deps): update dependency system.reflection.metadata to 9.0.9
2025-09-13 15:27:57 +01:00
Dimitrij
651b742cc2 Merge pull request #2778 from mRemoteNG/renovate/system.formats.asn1-9.x
chore(deps): update dependency system.formats.asn1 to 9.0.9
2025-09-13 15:27:46 +01:00
Dimitrij
e2b1eb6e4a Merge pull request #2783 from mRemoteNG/renovate/system.security.cryptography.protecteddata-9.x
chore(deps): update dependency system.security.cryptography.protecteddata to 9.0.9
2025-09-13 15:27:25 +01:00
Dimitrij
2546620c8d Merge pull request #2784 from mRemoteNG/renovate/system.security.permissions-9.x
chore(deps): update dependency system.security.permissions to 9.0.9
2025-09-13 15:26:46 +01:00
Dimitrij
826f75348c Merge pull request #2776 from mRemoteNG/renovate/system.directoryservices-9.x
chore(deps): update dependency system.directoryservices to 9.0.9
2025-09-13 15:26:33 +01:00
Dimitrij
b27462da05 Merge pull request #2774 from mRemoteNG/renovate/system.diagnostics.diagnosticsource-9.x
chore(deps): update dependency system.diagnostics.diagnosticsource to 9.0.9
2025-09-13 15:26:04 +01:00
renovate[bot]
ad0ade5dd4 chore(deps): update dependency system.security.permissions to 9.0.9 2025-09-10 12:52:32 +00:00
renovate[bot]
6e3cf4630e chore(deps): update dependency system.security.cryptography.protecteddata to 9.0.9 2025-09-10 12:52:28 +00:00
renovate[bot]
6f3768db4b chore(deps): update dependency system.reflection.metadata to 9.0.9 2025-09-10 09:01:05 +00:00
renovate[bot]
98220ccf93 chore(deps): update dependency system.management to 9.0.9 2025-09-10 09:01:01 +00:00
renovate[bot]
52688d7145 chore(deps): update dependency system.io.pipelines to 9.0.9 2025-09-10 06:31:41 +00:00
renovate[bot]
b24edae66b chore(deps): update dependency system.formats.asn1 to 9.0.9 2025-09-10 06:31:37 +00:00
renovate[bot]
ac39ce26ee chore(deps): update dependency system.drawing.common to 9.0.9 2025-09-10 03:14:02 +00:00
renovate[bot]
cf041c661b chore(deps): update dependency system.directoryservices to 9.0.9 2025-09-10 03:13:59 +00:00
Dimitrij
9d173d0bdc Refactor GitHub Actions workflow for changelog updates 2025-09-10 00:07:43 +01:00
Dimitrij
aafb608149 Update GitHub Actions workflow for Renovate PRs 2025-09-09 23:27:09 +01:00
renovate[bot]
04dd125ee6 chore(deps): update dependency system.diagnostics.eventlog to 9.0.9 2025-09-09 22:25:19 +00:00
renovate[bot]
e501235b2e chore(deps): update dependency system.diagnostics.diagnosticsource to 9.0.9 2025-09-09 22:25:15 +00:00
Dimitrij
7c6a322787 Refactor GitHub Actions workflow for changelog updates 2025-09-09 23:16:32 +01:00
Dimitrij
eed729181b Update workflow for changelog generation 2025-09-09 22:48:02 +01:00
Dimitrij
ce9d42d304 Merge pull request #2773 from mRemoteNG/renovate/system.configuration.configurationmanager-9.x
chore(deps): update dependency system.configuration.configurationmanager to 9.0.9
2025-09-09 22:30:13 +01:00
Dimitrij
733e31e54b Merge branch 'v1.78.2-dev' into renovate/system.configuration.configurationmanager-9.x 2025-09-09 22:30:02 +01:00
Dimitrij
f1714f3228 Update workflow to modify changelog after PR merge 2025-09-09 21:17:05 +01:00
Dimitrij
44c90f0de4 Fix variable interpolation in changelog update
upd
2025-09-09 21:01:48 +01:00
Dimitrij
8268310685 added new section 2025-09-09 20:58:56 +01:00
Dimitrij
d64eaf6234 Refactor PR title extraction and changelog update
upd
2025-09-09 20:57:26 +01:00
Dimitrij
4d0b848676 Update workflow to handle Renovate PRs on push
upd
2025-09-09 20:53:23 +01:00
Dimitrij
d508ca33c8 Merge pull request #2772 from mRemoteNG/renovate/system.collections.immutable-9.x
chore(deps): update dependency system.collections.immutable to 9.0.9
2025-09-09 20:29:34 +01:00
renovate[bot]
86fdea129b chore(deps): update dependency system.configuration.configurationmanager to 9.0.9 2025-09-09 18:30:04 +00:00
renovate[bot]
79af98845a chore(deps): update dependency system.collections.immutable to 9.0.9 2025-09-09 18:29:59 +00:00
Dimitrij
2c15c6b0ed Merge pull request #2771 from mRemoteNG/renovate/microsoft.extensions.dependencymodel-9.x
chore(deps): update dependency microsoft.extensions.dependencymodel to 9.0.9
2025-09-09 18:18:31 +01:00
Dimitrij
52ee10efda Merge branch 'v1.78.2-dev' into renovate/microsoft.extensions.dependencymodel-9.x 2025-09-09 18:18:19 +01:00
Dimitrij
2da7d02a2c Update GitHub Actions workflow for PRs
update
2025-09-09 17:50:38 +01:00
Dimitrij
7af4ba27f3 Merge pull request #2770 from mRemoteNG/renovate/microsoft.extensions.configuration.usersecrets-9.x
chore(deps): update dependency microsoft.extensions.configuration.usersecrets to 9.0.9
2025-09-09 17:42:16 +01:00
Dimitrij
46cd974a58 Update changelog update workflow
Fix sed command to append entries in CHANGELOG.md
2025-09-09 17:35:32 +01:00
renovate[bot]
3d791b7f78 chore(deps): update dependency microsoft.extensions.dependencymodel to 9.0.9 2025-09-09 14:33:54 +00:00
renovate[bot]
29e27c1283 chore(deps): update dependency microsoft.extensions.configuration.usersecrets to 9.0.9 2025-09-09 14:33:49 +00:00
Dimitrij
8434ab460c Merge pull request #2768 from mRemoteNG/renovate/cucumber.messages-29.x
chore(deps): update dependency cucumber.messages to v29
2025-09-08 18:02:21 +01:00
Dimitrij
561d073bba Merge pull request #2767 from mRemoteNG/renovate/microsoft.web.webview2-1.x
chore(deps): update dependency microsoft.web.webview2 to 1.0.3485.44
2025-09-08 18:02:09 +01:00
renovate[bot]
d46d37acb9 chore(deps): update dependency cucumber.messages to v29 2025-09-08 14:50:37 +00:00
renovate[bot]
4f4d841137 chore(deps): update dependency microsoft.web.webview2 to 1.0.3485.44 2025-09-08 14:50:33 +00:00
Dimitrij
b7e80ceee9 Merge pull request #2766 from Greenie0701/fix_win_ci
ci: fix T4 template transform by adding EnvDTE & Interop references
2025-09-08 09:20:00 +01:00
Wise Man
8a3112c71f ci: fix T4 template transform by adding EnvDTE & Interop references 2025-09-07 09:28:45 +05:30
Dimitrij
4276b1439b Merge pull request #2765 from mRemoteNG/renovate/awssdk.ec2-4.x
Update dependency AWSSDK.EC2 to 4.0.37
2025-09-05 10:38:42 +01:00
renovate[bot]
3f626149f4 Update dependency AWSSDK.EC2 to 4.0.37 2025-09-04 20:57:09 +00:00
Dimitrij
30b674f8f5 .net check 2025-08-31 01:12:03 +01:00
Dimitrij
fe3fb46e94 undo 2025-08-28 18:15:07 +01:00
Dimitrij
99c524c7b6 fix? 2025-08-28 18:08:26 +01:00
Dimitrij
5da7a582bf Fix indentation and formatting in Build_mR-NB.yml
try to fix
2025-08-28 18:07:27 +01:00
Dimitrij
229c46a0c1 temporary disable Platform variable 2025-08-28 17:17:15 +01:00
Dimitrij
78d347889b Merge pull request #2762 from mRemoteNG/renovate/awssdk.ec2-4.x
Update dependency AWSSDK.EC2 to 4.0.33
2025-08-27 10:01:59 +01:00
renovate[bot]
244741ba15 Update dependency AWSSDK.EC2 to 4.0.33 2025-08-26 22:45:48 +00:00
Dimitrij
015fa601a3 Merge pull request #2761 from mRemoteNG/renovate/awssdk.ec2-4.x
Update dependency AWSSDK.EC2 to 4.0.32
2025-08-26 08:48:27 +01:00
renovate[bot]
1bd9e44ecb Update dependency AWSSDK.EC2 to 4.0.32 2025-08-25 22:33:55 +00:00
Dimitrij
dc7cfdb762 Merge pull request #2759 from mRemoteNG/renovate/chromiumembeddedframework.runtime.win-arm64-139.x
Update dependency chromiumembeddedframework.runtime.win-arm64 to v139
2025-08-25 00:09:27 +01:00
Dimitrij
36afbae5af Merge pull request #2760 from mRemoteNG/renovate/chromiumembeddedframework.runtime.win-x64-139.x
Update dependency chromiumembeddedframework.runtime.win-x64 to v139
2025-08-25 00:09:05 +01:00
Dimitrij
89d56a10f0 Merge pull request #2758 from mRemoteNG/renovate/log4net-3.x
Update dependency log4net to 3.2.0
2025-08-25 00:08:51 +01:00
Dimitrij
8dcc6031e6 Merge pull request #2757 from mRemoteNG/renovate/awssdk.core-4.x
Update dependency AWSSDK.Core to 4.0.0.25
2025-08-25 00:08:37 +01:00
renovate[bot]
b524a5424a Update dependency chromiumembeddedframework.runtime.win-x64 to v139 2025-08-23 08:58:53 +00:00
renovate[bot]
f06ad99658 Update dependency chromiumembeddedframework.runtime.win-arm64 to v139 2025-08-23 08:58:48 +00:00
renovate[bot]
f4ad61a41b Update dependency log4net to 3.2.0 2025-08-22 23:46:53 +00:00
renovate[bot]
3c948aca96 Update dependency AWSSDK.Core to 4.0.0.25 2025-08-22 23:46:49 +00:00
Dimitrij
57ee792198 Merge pull request #2755 from mRemoteNG/renovate/awssdk.ec2-4.x
Update dependency AWSSDK.EC2 to 4.0.31.1
2025-08-22 00:51:18 +01:00
renovate[bot]
6d84364cec Update dependency AWSSDK.EC2 to 4.0.31.1 2025-08-21 23:51:10 +00:00
Dimitrij
d74fc16cfd Merge pull request #2754 from mRemoteNG/renovate/awssdk.core-4.x
Update dependency AWSSDK.Core to 4.0.0.24
2025-08-22 00:50:48 +01:00
renovate[bot]
56d72cb30b Update dependency AWSSDK.Core to 4.0.0.24 2025-08-21 23:40:29 +00:00
Dimitrij
2f6f03ce8f Merge pull request #2740 from mRemoteNG/renovate/nunit.console-3.x
Update dependency NUnit.Console to 3.20.1
2025-08-21 13:10:46 +01:00
renovate[bot]
b437ff3dcc Update dependency NUnit.Console to 3.20.1 2025-08-21 12:09:49 +00:00
Dimitrij
93d17c2c35 Merge pull request #2741 from mRemoteNG/renovate/nunit.consolerunner-3.x
Update dependency NUnit.ConsoleRunner to 3.20.1
2025-08-21 13:09:37 +01:00
Dimitrij
b70ea04f02 Merge pull request #2749 from mRemoteNG/renovate/chromiumembeddedframework.runtime.win-x64-138.x
Update dependency chromiumembeddedframework.runtime.win-x64 to v138
2025-08-21 13:09:23 +01:00
Dimitrij
89cde8b518 Merge pull request #2748 from mRemoteNG/renovate/chromiumembeddedframework.runtime.win-arm64-138.x
Update dependency chromiumembeddedframework.runtime.win-arm64 to v138
2025-08-21 13:09:07 +01:00
Dimitrij
8d78623730 Merge pull request #2747 from mRemoteNG/renovate/actions-checkout-5.x
Update actions/checkout action to v5
2025-08-21 13:08:54 +01:00
Dimitrij
4a5b05f669 Merge pull request #2751 from mRemoteNG/renovate/softprops-action-gh-release-2.x
Update softprops/action-gh-release action to v2
2025-08-21 13:08:40 +01:00
Dimitrij
c4f2b7ed2e Merge pull request #2745 from mRemoteNG/renovate/nunit3testadapter-5.x
Update dependency NUnit3TestAdapter to 5.1.0
2025-08-21 13:08:21 +01:00
renovate[bot]
e296c77b14 Update dependency chromiumembeddedframework.runtime.win-x64 to v138 2025-08-21 12:06:56 +00:00
Dimitrij
56611c6e41 Merge pull request #2752 from mRemoteNG/renovate/microsoft.extensions.dependencymodel-9.x
Update dependency Microsoft.Extensions.DependencyModel to 9.0.8
2025-08-21 13:06:56 +01:00
renovate[bot]
1e216b8cb3 Update dependency chromiumembeddedframework.runtime.win-arm64 to v138 2025-08-21 12:06:52 +00:00
renovate[bot]
302700ac80 Update dependency Microsoft.Extensions.DependencyModel to 9.0.8 2025-08-21 12:06:45 +00:00
Dimitrij
cc5d943a86 Merge pull request #2753 from mRemoteNG/renovate/microsoft.net.test.sdk-17.x
Update dependency Microsoft.NET.Test.Sdk to 17.14.1
2025-08-21 13:06:32 +01:00
Dimitrij
7942f93649 Merge pull request #2744 from mRemoteNG/renovate/nunit-4.x
Update dependency NUnit to 4.4.0
2025-08-21 13:05:51 +01:00
Dimitrij
270c6dd64b Merge pull request #2750 from mRemoteNG/renovate/gherkin-34.x
Update dependency Gherkin to v34
2025-08-21 13:05:33 +01:00
Dimitrij
9f1633bc63 Merge pull request #2746 from mRemoteNG/renovate/actions-cache-4.x
Update actions/cache action to v4
2025-08-21 13:05:20 +01:00
Dimitrij
8953ec0e1f Merge pull request #2738 from mRemoteNG/renovate/chromiumembeddedframework.runtime.win-x64-92.x
Update dependency chromiumembeddedframework.runtime.win-x64 to 92.0.26
2025-08-21 13:05:03 +01:00
Dimitrij
8f5dd3bc93 Merge pull request #2737 from mRemoteNG/renovate/chromiumembeddedframework.runtime.win-arm64-92.x
Update dependency chromiumembeddedframework.runtime.win-arm64 to 92.0.26
2025-08-21 13:04:45 +01:00
renovate[bot]
97ee6e6ee0 Update dependency Microsoft.NET.Test.Sdk to 17.14.1 2025-08-21 12:04:12 +00:00
renovate[bot]
ae626294f3 Update softprops/action-gh-release action to v2 2025-08-21 11:39:30 +00:00
renovate[bot]
842a45aa17 Update dependency Gherkin to v34 2025-08-21 11:39:26 +00:00
renovate[bot]
686936e7b2 Update actions/checkout action to v5 2025-08-21 11:39:15 +00:00
renovate[bot]
8ac8ad6ed9 Update actions/cache action to v4 2025-08-21 11:39:12 +00:00
renovate[bot]
e7eda49676 Update dependency NUnit3TestAdapter to 5.1.0 2025-08-21 11:39:09 +00:00
renovate[bot]
faae8c7a77 Update dependency NUnit to 4.4.0 2025-08-21 11:39:05 +00:00
renovate[bot]
5cb67a6e09 Update dependency NUnit.ConsoleRunner to 3.20.1 2025-08-21 11:38:52 +00:00
renovate[bot]
06747dc38d Update dependency chromiumembeddedframework.runtime.win-x64 to 92.0.26 2025-08-21 11:38:41 +00:00
renovate[bot]
c3813fd87b Update dependency chromiumembeddedframework.runtime.win-arm64 to 92.0.26 2025-08-21 11:38:34 +00:00
Dimitrij
901584a2e4 upd 2025-08-21 01:21:42 +01:00
Dimitrij
f9e33c7fca fix 2025-08-21 00:49:09 +01:00
Dimitrij
ea36ca37f0 Merge pull request #2736 from asherbiny/v1.78.2-dev
Change Default RDP Version to v10
2025-08-21 00:18:39 +01:00
Dimitrij
2ea06f3096 add include 2025-08-21 00:16:13 +01:00
Dimitrij
dc208973c8 Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev 2025-08-21 00:03:44 +01:00
Dimitrij
18ddb1001b one more fix 2025-08-21 00:03:37 +01:00
Dimitrij
567bc4a112 Update Build_mR-NB.yml
fix step 1
2025-08-20 23:29:46 +01:00
Dimitrij
c5dbf5a412 Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev 2025-08-20 23:21:00 +01:00
Dimitrij
c084e66db5 fix path attribute (finger cross) 2025-08-20 23:20:53 +01:00
Ahmed ElSherbiny
bb6d35f91f Change Default RDP Version to v10
Problem I'm solving:
When importing RDP files, mRemoteNG defaults to RDPv6 (which is extremely old, and particularly does not support Gateway access tokens). Thus I would have to manually change the config to a newer RDP protocol, so that I could initiate  the connection.

It is safe to assume that the majority of Windows PCs on the internet today are running Windows 10 or above, and by keeping the default at RDP6 we are causing more unnecessary friction to those majority users.

I've changed the default to Rdp10 (released in Windows 10).
https://en.wikipedia.org/wiki/Remote_Desktop_Protocol#Version_10.0
2025-08-20 15:05:09 -07:00
Dimitrij
ac59bc2999 Update Build_mR-NB.yml
upd
2025-08-20 23:03:30 +01:00
Dimitrij
7dfb1d395d Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev 2025-08-20 22:59:49 +01:00
Dimitrij
46672ca976 adjustment 2025-08-20 22:59:42 +01:00
Dimitrij
94e7e23cda Update Build_mR-NB.yml
extend t4 step
2025-08-20 22:58:08 +01:00
Dimitrij
2fa0ca41d6 adjustment 2025-08-20 22:51:50 +01:00
Dimitrij
2ebe35c6d6 amendments 2025-08-20 22:42:42 +01:00
Dimitrij
354066de0b corrections 2025-08-20 22:39:22 +01:00
Dimitrij
3d3c1f8f23 fix to local 2025-08-20 22:31:09 +01:00
Dimitrij
24448d604d fix path 2025-08-20 22:25:16 +01:00
Dimitrij
7d8e530ebf add EnvDTE 2025-08-20 22:22:29 +01:00
Dimitrij
5db88040cc lib update 2025-08-20 22:06:21 +01:00
Dimitrij
d9de6e78d7 Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev 2025-08-20 21:54:55 +01:00
Dimitrij
da136bfebd update .tt template to include platform type 2025-08-20 21:54:47 +01:00
Dimitrij
6657063441 Update Build_mR-NB.yml
upd
2025-08-19 22:38:42 +01:00
Dimitrij
47e6d4d711 test 2025-08-19 22:32:07 +01:00
Dimitrij
32c7bd2627 Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev 2025-08-19 22:12:06 +01:00
Dimitrij
3422d064ac change to set platform type 2025-08-19 22:11:58 +01:00
Dimitrij
0b01d40a85 Update Build_mR-NB.yml
override platform depending on build platform
2025-08-19 22:11:20 +01:00
Dimitrij
7f79dc6098 upd 2025-08-19 19:22:46 +01:00
Dimitrij
ac7590f23c Merge pull request #2734 from Mugundanmcw/fix_x64
fix native build for Windows-x64
2025-08-19 19:20:07 +01:00
Mugu~~
998bd3a1a9 fix native build for windows-x64 2025-08-19 23:43:53 +05:30
Dimitrij
0f62f1d72c update to signed version 2025-08-19 00:28:14 +01:00
Dimitrij
cb1b1a5cac lib updates 2025-08-18 08:29:03 +01:00
Dimitrij
63ccdd0a23 Merge pull request #2728 from Mugundanmcw/win_arm64
Add support for building mRemoteNG on Windows ARM64
2025-08-18 08:25:40 +01:00
Dimitrij
ead809cc90 Update mRemoteNG/mRemoteNG.csproj
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-18 08:23:54 +01:00
Dimitrij
dfd4851bf1 Update ObjectListView/ObjectListView.NetCore.csproj
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-18 08:23:44 +01:00
Mugu~~
e50f11697f Add support for building mRemoteNG on Windows ARM64 2025-08-17 20:23:35 +05:30
Dimitrij
4800ad53dc Create post_2_Reddit.yml
bump
2025-08-15 13:08:32 +01:00
Dimitrij
9349c3f055 Lib update 2025-08-15 12:19:35 +01:00
Dimitrij
d7a82ae911 Update Build_mR-NB.yml
fix
2025-08-15 11:06:28 +01:00
Dimitrij
b3354fb033 Update Build_mR-NB.yml
fix
2025-08-15 10:51:18 +01:00
Dimitrij
9b56b11c9b Update Build_mR-NB.yml
upd
2025-08-15 10:41:58 +01:00
Dimitrij
15247c3637 Update Build_mR-NB.yml
update actions
2025-08-14 22:37:37 +01:00
Dimitrij
4311d3b057 Update Build_mR-NB.yml
update action
2025-08-14 22:26:37 +01:00
Dimitrij
076db0637f Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev 2025-08-14 22:04:38 +01:00
Dimitrij
8a7108910d Update Change log 2025-08-14 22:04:30 +01:00
Dimitrij
be12600094 Update Build_mR-NB.yml
upd
2025-08-14 21:48:17 +01:00
Dimitrij
988305100d Update Build_mR-NB.yml
Update Action
2025-08-14 21:38:17 +01:00
Dimitrij
16ae28ee62 Update Build_mR-NB.yml
upd
2025-08-14 21:16:09 +01:00
Dimitrij
30aab57bc9 Update Build_mR-NB.yml
fix
2025-08-14 21:04:34 +01:00
Dimitrij
a573914668 Update Build_mR-NB.yml
test
2025-08-14 20:50:12 +01:00
Dimitrij
83771cc2e6 Update Build_mR-NB.yml
text
2025-08-14 20:28:09 +01:00
Dimitrij
d8eaba75d2 Update Build_mR-NB.yml
test
2025-08-14 13:06:34 +01:00
Dimitrij
96f917a1ab Update Build_mR-NB.yml
fix
2025-08-14 12:47:43 +01:00
Dimitrij
44d81a6bdf Update Build_mR-NB.yml
test
2025-08-14 12:33:12 +01:00
Dimitrij
1b0e8440ae Update Build_mR-NB.yml
test
2025-08-14 12:22:48 +01:00
Dimitrij
e236dbe661 Update Build_mR-NB.yml
test
2025-08-14 12:14:45 +01:00
Dimitrij
2ba7d09c21 Update Build_mR-NB.yml
test
2025-08-14 12:09:44 +01:00
Dimitrij
2b937689bd Update Build_mR-NB.yml
fix
2025-08-14 12:01:51 +01:00
Dimitrij
eeb7eb7ad2 Update Build_mR-NB.yml
test
2025-08-14 11:43:13 +01:00
Dimitrij
c8e29211eb Update Build_mR-NB.yml
test
2025-08-14 11:35:03 +01:00
Dimitrij
57cce7ec22 Update Build_mR-NB.yml
test
2025-08-14 11:23:18 +01:00
Dimitrij
78af721250 Update Build_mR-NB.yml
tst
2025-08-14 11:09:40 +01:00
Dimitrij
cba6c2dacc Update Build_mR-NB.yml
test
2025-08-14 11:08:56 +01:00
Dimitrij
b4b12cdbdc Update Build_mR-NB.yml
fix
2025-08-14 03:20:48 +01:00
Dimitrij
e7c86c95fe Update Build_mR-NB.yml
fix
2025-08-14 03:17:03 +01:00
Dimitrij
f28d96e91a Update Build_mR-NB.yml
fix
2025-08-14 03:09:46 +01:00
Dimitrij
b25217dd38 Update Build_mR-NB.yml
fix token
2025-08-14 02:53:55 +01:00
Dimitrij
f60da8665b Update Build_mR-NB.yml
add token
2025-08-14 02:47:39 +01:00
Dimitrij
3d8daff050 Update Build_mR-NB.yml
fix
2025-08-14 02:43:14 +01:00
Dimitrij
c41f3c21c1 Update Build_mR-NB.yml
fix
2025-08-14 02:29:19 +01:00
Dimitrij
8e7897bf76 Update Build_mR-NB.yml
fix
2025-08-14 02:17:48 +01:00
Dimitrij
050bf75fdd Update Build_mR-NB.yml
fix
2025-08-14 02:13:49 +01:00
Dimitrij
1bbfcd0a7c Update Build_mR-NB.yml
fix
2025-08-14 01:51:43 +01:00
Dimitrij
30106d0006 Update Build_mR-NB.yml
fix
2025-08-14 01:46:26 +01:00
Dimitrij
16974068d7 Update Build_mR-NB.yml
fix
2025-08-14 01:41:53 +01:00
Dimitrij
08d0f67062 Update Build_mR-NB.yml
fix
2025-08-14 01:23:26 +01:00
Dimitrij
2edcb5863c Update Build_mR-NB.yml
fix
2025-08-14 01:19:57 +01:00
Dimitrij
44b9e49a54 Update Build_mR-NB.yml
fix
2025-08-14 01:13:33 +01:00
Dimitrij
0175903f51 Update Build_mR-NB.yml
fix date
2025-08-14 00:52:28 +01:00
Dimitrij
0ef282f89c Update Build_mR-NB.yml
fix
2025-08-14 00:51:48 +01:00
Dimitrij
b4eec7d44c Update Build_mR-NB.yml
fix and upd
2025-08-14 00:39:41 +01:00
Dimitrij
2e040a39db Update Build_mR-NB.yml
upd
2025-08-14 00:30:37 +01:00
Dimitrij
7e0ee705ff Update Build_mR-NB.yml
upd
2025-08-14 00:28:37 +01:00
Dimitrij
361005e242 Update Build_mR-NB.yml
fix
2025-08-14 00:17:37 +01:00
Dimitrij
2ea26f7319 Update Build_mR-NB.yml
fix
2025-08-14 00:04:56 +01:00
Dimitrij
44c4ca232f Update Build_mR-NB.yml
fix 11
2025-08-13 23:57:16 +01:00
Dimitrij
f7bb45288e Update Build_mR-NB.yml
fix10
2025-08-13 23:50:46 +01:00
Dimitrij
c99aa41744 Update Build_mR-NB.yml
fix 9
2025-08-13 23:43:09 +01:00
Dimitrij
4eb1cc939c Update Build_mR-NB.yml
remove raw
2025-08-13 23:32:33 +01:00
Dimitrij
d4c51f18f5 Update Build_mR-NB.yml
fix8
2025-08-13 22:14:35 +01:00
Dimitrij
af89c8bba7 Update Build_mR-NB.yml
fix 6
2025-08-13 22:12:02 +01:00
Dimitrij
2e951ef0db Update Build_mR-NB.yml
fix5
2025-08-13 22:06:33 +01:00
Dimitrij
80b82366c4 Update Build_mR-NB.yml
fix4
2025-08-13 22:00:23 +01:00
Dimitrij
3d75eae9c0 Update Build_mR-NB.yml
fix3
2025-08-13 21:52:19 +01:00
Dimitrij
0bd8d9c4ca Update Build_mR-NB.yml
fix2
2025-08-13 19:55:06 +01:00
Dimitrij
b3c1132a25 Update Build_mR-NB.yml
fix
2025-08-13 19:07:42 +01:00
Dimitrij
bddcb16e8a Update Build_mR-NB.yml
fix folder path
2025-08-13 18:57:49 +01:00
Dimitrij
3cb2ba6fa3 Update Build_mR-NB.yml
correct path
2025-08-13 18:47:50 +01:00
Dimitrij
205c8914ed Update Build_mR-NB.yml
upd
2025-08-13 18:38:50 +01:00
Dimitrij
76e0b67599 Update Build_mR-NB.yml
removed !!!
2025-08-13 18:26:51 +01:00
Dimitrij
2c617b230a Update and rename Build mR-NB.yml to Build_mR-NB.yml
update a name
2025-08-13 18:23:26 +01:00
Dimitrij
72d3268d39 lib update 2025-08-13 18:19:40 +01:00
Dimitrij
a093a77610 Merge pull request #2723 from asherbiny/v1.78.2-dev
Read keyboardhook, gatewayaccesstoken and gatewaycredentialssource from RDP File
2025-08-12 10:03:24 +01:00
asherbiny
a9c6443c18 Merge branch 'v1.78.2-dev' into v1.78.2-dev 2025-08-08 11:45:29 -05:00
Ahmed ElSherbiny
757932ed4a Add comment for duplicate cases
Cases 3 & 4 both require the user to manually enter Gateway credentials. Comment added for code readability.
2025-08-08 09:43:03 -07:00
Dimitrij
55aad85bdd Update Build mR-NB.yml
debug
2025-08-08 03:24:14 +01:00
Dimitrij
952d87f858 Update Build mR-NB.yml
add debug
2025-08-08 03:11:46 +01:00
Dimitrij
81fbe68e3b Update Build mR-NB.yml
fix
2025-08-08 02:58:11 +01:00
Dimitrij
c82cd15f24 Update Build mR-NB.yml
fix
2025-08-08 02:56:06 +01:00
Dimitrij
9991f7015f Update Build mR-NB.yml
fix
2025-08-08 02:53:39 +01:00
Dimitrij
e94fac0459 Update Build mR-NB.yml
fix permission
2025-08-08 02:52:00 +01:00
Dimitrij
816833c9f3 Update Build mR-NB.yml
fix 403
2025-08-08 02:51:17 +01:00
Dimitrij
a202d72cd6 Update Build mR-NB.yml
release fix
2025-08-08 02:44:53 +01:00
Dimitrij
0558b0621a Update Build mR-NB.yml
fix
2025-08-08 02:31:05 +01:00
Dimitrij
6e12b398d4 Update Build mR-NB.yml
fix
2025-08-08 02:24:21 +01:00
Dimitrij
cce9aa5e97 Update Build mR-NB.yml
fix
2025-08-08 02:19:47 +01:00
Dimitrij
6020122297 Update Build mR-NB.yml
another fix
2025-08-08 02:13:00 +01:00
Dimitrij
bed11116fe Update Build mR-NB.yml
zip name
2025-08-08 02:01:23 +01:00
Dimitrij
66cf93ffdd Update Build mR-NB.yml
upd
2025-08-08 01:45:09 +01:00
Dimitrij
8f1ca7dee2 Update Build mR-NB.yml
upd
2025-08-08 01:40:01 +01:00
Dimitrij
b24eac52ae Update Build mR-NB.yml
upd
2025-08-08 01:31:12 +01:00
Dimitrij
5aaa3a00b4 upd 2025-08-08 01:22:03 +01:00
Dimitrij
0d737228b0 upd tt 2025-08-08 01:18:15 +01:00
Dimitrij
f57c862c3c Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev 2025-08-08 01:15:40 +01:00
Dimitrij
ba476533ed upd template 2025-08-08 01:15:33 +01:00
Dimitrij
28b39688ec Update Build mR-NB.yml
fix
2025-08-08 01:10:31 +01:00
Dimitrij
fd8b1a8807 Update Build mR-NB.yml
fix prj name
2025-08-08 01:07:37 +01:00
Dimitrij
bff44b5d74 Update Build mR-NB.yml
upd
2025-08-08 01:06:02 +01:00
Dimitrij
a9ee32d020 Update and rename mR-NB.yml to Build mR-NB.yml
upd env
2025-08-08 01:03:26 +01:00
Dimitrij
4fd1864eef Update mR-NB.yml
replace texttransform wiht dotnet-t4
2025-08-08 01:01:14 +01:00
Dimitrij
3775279659 upd prj 2025-08-08 00:55:21 +01:00
Dimitrij
5bba633ab1 Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev 2025-08-08 00:54:27 +01:00
Dimitrij
900cc0afe3 lib update 2025-08-08 00:54:19 +01:00
Dimitrij
29fa64825e Update mR-NB.yml
add TextTransform
2025-08-08 00:13:21 +01:00
Dimitrij
1c59404299 Update mR-NB.yml
fix
2025-08-07 23:59:25 +01:00
Dimitrij
563613be6b Update mR-NB.yml
upd to new build structure
2025-08-07 23:52:46 +01:00
Dimitrij
5ae2296400 Update mR-NB.yml
upd to latest VS
2025-08-07 23:31:44 +01:00
Dimitrij
9534ceccd9 Update and rename build-x86_64.yml to mR-NB.yml
update to latest and switch to new branch
2025-08-07 23:30:51 +01:00
asherbiny
878845941d Merge branch 'v1.78.2-dev' into v1.78.2-dev 2025-08-06 16:29:34 -05:00
Ahmed ElSherbiny
a5be2bb5b5 Read keyboardhook, gatewayaccesstoken and gatewaycredentialssource from RDP File
With this commit mRemoteNG, upon importing an RDP file, will read and apply those fields to the connection being imported.

Tested by verifying that the properties are reflected correctly in the UI (Config panel) and influence the behavior of the connection as expected.
2025-08-06 14:11:10 -07:00
Dimitrij
19c954f972 lib update 2025-08-05 15:33:16 +01:00
Dimitrij
576f9db387 lib upgrade 2025-07-29 22:33:30 +01:00
Dimitrij
28ff02f8cb Merge pull request #2715 from jcefoli/FixObjectListViewSerializerWarnings
Disable WinForms analyzers and suppress WFO1000 build errors for ObjectListView
2025-07-02 09:51:52 +01:00
Joe Cefoli
d4a590d292 Disable WinForms analyzers and suppress WFO1000 build errors 2025-07-01 15:01:44 -04:00
Dimitrij
25f928fc8a Merge pull request #2712 from caspChristian/imp2321
VNCEvent_Disconnected send the ProtocolBase based object reference
2025-06-26 13:31:53 +01:00
Christian Iwata Nilsson
5dd3d927d6 VNCEvent_Disconnected send the ProtocolBase based object reference
Fixes the exception mostly mentioned in #2321, but not the actual connection issues
2025-06-26 09:08:07 +02:00
Dimitrij
77950632f8 lib update 2025-06-23 14:08:48 +01:00
Dimitrij
e85cee752d few more 2025-06-11 23:38:36 +01:00
Dimitrij
5c48f13a27 fix warnings about null-able 2025-06-11 23:34:35 +01:00
Dimitrij
18b283db0a Lib updates 2025-06-11 20:14:52 +01:00
Dimitrij
ca7888a537 Lib update 2025-06-04 23:55:33 +01:00
Dimitrij
246e90acd1 lib update 2025-05-29 18:49:41 +01:00
Dimitrij
e58e33974a cosmetic changes 2025-05-29 18:49:20 +01:00
Dimitrij
5cb07d003c UI changes for DB Setup 2025-05-29 18:48:10 +01:00
Dimitrij
3ecedc8fcf Lib updates 2025-05-13 19:34:57 +01:00
Dimitrij
a88ed5d3a9 UI Changes related SQL server 2022 configuration
(partially done)
2025-04-10 01:03:56 +01:00
Dimitrij
37fead2076 Lib updates
(temporary removed test projects from solution)
2025-04-10 01:01:53 +01:00
Dimitrij
2770c761b2 fix for current version and released version comparison
small fixes in Language
amazon lib update
2025-04-02 13:19:10 +01:00
Dimitrij
4b28eb6758 patch to remove secure-string implementation (will need to be implemented differently)
what will temporary allow to type-in and edit passwords
2025-03-30 13:56:38 +01:00
Dimitrij
ba45b44f45 lib update 2025-03-30 13:04:24 +01:00
Dimitrij
f5186cbadd lib updates 2025-03-19 12:09:39 +00:00
Dimitrij
807924c51e fix for NETSDK1137 (https://learn.microsoft.com/en-us/dotnet/core/tools/sdk-errors/netsdk1137) 2025-03-10 10:59:11 +00:00
Dimitrij
155d849201 Lib update 2025-03-10 09:55:11 +00:00
Dimitrij
e9efd2705c Lib update 2025-03-01 15:24:00 +00:00
Dimitrij
204e0d041c fix to make _mRIdentifier class-level 2025-02-18 02:45:07 +00:00
Dimitrij
227d75d956 Add new method to generate a unique identifier for the machine 2025-02-18 02:20:37 +00:00
Dimitrij
d2706c8748 Switch to use central package version management 2025-02-18 01:58:43 +00:00
Dimitrij
2518b8600f System.Data.SqlClient package is now deprecated, migrated to Microsoft.Data.SqlClient. 2025-02-17 15:23:38 +00:00
Dimitrij
9300839d59 switch to .net9 2025-02-17 14:43:51 +00:00
Dimitrij
a069d1f057 lib update 2025-02-17 14:21:00 +00:00
Dimitrij
7557733b81 Update to PuTTYNG 83.0.1 #2670 2025-02-11 11:17:20 +00:00
Dimitrij
59ac3020fe lib updates 2025-02-05 14:10:32 +00:00
tecxx
f0e4cee613 AssemblyInfo Conflict 2025-02-04 16:11:37 +01:00
tecxx
0f395dad9e Rebase on 1.77.3-dev 2025-02-04 16:10:53 +01:00
tecxx
7e59362b5c fix ssh quickconnect exception 2025-02-04 16:06:15 +01:00
Dimitrij
e69492e94f version naming correction 2025-02-04 16:06:15 +01:00
Dimitrij
2b31b173c0 lib updates 2025-02-04 16:05:35 +01:00
Robert Rostek
8d3a1ab3a7 .net 8.0.12 update 2025-02-04 16:05:35 +01:00
Robert Rostek
0349c5b402 fix null exception in GetDiskIdentifier 2025-02-04 16:05:27 +01:00
Robert Rostek
0b7be8b0c6 .net 8 upgrade (with unsafebinaryformatter) 2025-02-04 16:05:27 +01:00
Dimitrij
3a8ca5fb4a Merge pull request #2668 from tecxx/v1.78.1-dev
fix ssh quickconnect exception
2025-02-04 14:36:08 +00:00
tecxx
c88b50ad89 fix ssh quickconnect exception 2025-02-04 15:31:39 +01:00
Dimitrij
3746ba1819 fix year 2025-02-01 17:21:19 +00:00
Dimitrij
945bff3c17 Merge branch 'v1.77.3-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.77.3-dev 2025-02-01 17:01:13 +00:00
Dimitrij
d7e318b1f8 lib update 2025-02-01 16:59:53 +00:00
Dimitrij
eb3db198a1 Update build-x86_64.yml
update vs to 17.12.4
2025-02-01 16:34:00 +00:00
Dimitrij
b380d32ce1 version naming correction 2025-01-27 10:57:08 +00:00
Dimitrij
7a6056c95f lib updates 2025-01-27 10:56:47 +00:00
Dimitrij
a908a783be lib update 2025-01-24 13:25:58 +00:00
Dimitrij
1ae02885cf Merge pull request #2660 from tecxx/develop-orig
.net 8 upgrade code changes
2025-01-16 22:19:38 +00:00
Robert Rostek
8ea210e14f .net 8.0.12 update 2025-01-15 19:50:39 +01:00
Dimitrij
1ae9960e82 lib update 2025-01-14 14:54:54 +00:00
Robert Rostek
bed946988a fix null exception in GetDiskIdentifier 2025-01-14 13:17:08 +01:00
Robert Rostek
e42632b82d .net 8 upgrade (with unsafebinaryformatter) 2025-01-12 21:51:43 +01:00
Dimitrij
5aa8425209 Lib updates 2024-12-23 14:27:58 +00:00
Dimitrij
b794aa8089 update puttyng to ver. 0.82 2024-12-23 14:09:07 +00:00
Dimitrij
e8bb0bffaa lib upgrade + update
Some version should be not upgraded due .net limitation, what will be done once .net will be upgraded
2024-11-23 15:51:32 +00:00
Dimitrij
e1e0661f25 lib upgrade 2024-11-23 13:12:36 +00:00
Dimitrij
7848c83c72 lib updates 2024-11-23 12:50:16 +00:00
Dimitrij
2de37ed9c7 Merge branch 'v1.77.3-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.77.3-dev 2024-11-21 11:17:24 +00:00
Dimitrij
425b433cd2 lib updates 2024-11-21 11:17:15 +00:00
Dimitrij
44e07e3cdb Merge pull request #2652 from kursataktas/mremoteng-guru
Introducing mRemoteNG Guru on Gurubase.io
2024-11-12 21:46:23 +00:00
Kursat Aktas
689f882104 Introducing mRemoteNG Guru on Gurubase.io
Signed-off-by: Kursat Aktas <kursat.ce@gmail.com>
2024-11-13 00:17:19 +03:00
Dimitrij
207beaee12 Lib updates + small changes 2024-11-12 11:47:47 +00:00
Dimitrij
bd21b85de7 Lib update 2024-11-01 11:17:54 +00:00
Dimitrij
2e7579cac5 Moved all language files into Language folder. To keep root folder clean
On language change new language will not apply, need more work on that later
2024-10-18 15:40:50 +01:00
Dimitrij
ec63812af0 lib update 2024-10-18 11:02:27 +01:00
Dimitrij
8dfe1a22c9 Moved packages dll's into Assembly folder to keep root folder cleaner 2024-10-18 10:58:49 +01:00
Dimitrij
c0e3f547ec Introducing Local settings manager class and example of default settings json schema
Idea to create json file with defaults settings, what will be automatically loaded into new local settings db at creating process.
Group 'default' means its comes from json file and if user change it later - we will have user id there, that allow us to have different settings for different users but at same time keeping "original" ones in case user decide to reset.

What one of the basic steps of transition all settings into local db. Once application is running - will be possible to import already existing settings into local db, as well as switch where to have save them: to windows registery or to local db.

Settings file also could be encrypted, and file will be locked on current machine, that allow all users to use it, but not allow to transfer it to another machine. (If they do it will not work)
mR don't use any hard-coded passwords for such, only hdd id + machine name (what is hashed by SHA256) so couldn't be traceable from memory dump. Later will be possible to lock by user with own password, but that will required to enter on every start as mR will not save it.
2024-10-16 11:37:31 +01:00
Dimitrij
dafb1b364c lib updates 2024-10-15 13:41:41 +01:00
Dimitrij
7d1b9d8635 Merge pull request #2643 from xRushG/asyncRegSetting
Registry Settings: enhancements and new settings implementation
2024-10-01 22:51:52 +01:00
xRushG
8041f58ffd feat: Add registry settings for Security Page and encryption key generator
- Added new registry settings for managing security-related configurations on the Security Page.
- Implemented an encryption key generator to securely generate a password for store in the registry.
- Passwords are placed encrypted on the clipboard for 30 seconds.
- Update documents and finalize comments
2024-10-01 16:02:59 +02:00
xRushG
d018fdbace feat: Add registry settings for Connections and SQL Server settings
- Added registry settings for the Connection Page.
- Added registry settings for the SQL Serverl Page.
- All settings implemented with async registry logic
- Comment unused settings for Connection Page
- Update documents
2024-10-01 12:35:34 +02:00
xRushG
489602ffb3 feat: Add registry settings forTabs and Panel settings
- Added registry settings for the Tabs and Panel Page.
- All settings implemented with async registry logic
- Comment unused settings
- Update documents
2024-10-01 12:32:16 +02:00
xRushG
600ba73f5f Removed the deprecated SaveConnectionsOnExit option from the registry settings related to startup and exit. 2024-10-01 12:29:01 +02:00
xRushG
88600ae6df feat: Add registry settings for Notification Page, Appearance Page, and Startup and Exit Page with async logic
- Added registry settings for the Notification Page.
- Added registry settings for the Appearance Page.
- Added registry settings for the Startup and Exit Page.
- All settings implemented with async registry logic
2024-10-01 12:27:33 +02:00
xRushG
5e3bce9a92 refactor: Apply the async logic for Credentials and Updates registry settings, remove obsolete code
- Moved async registry loading logic to OptRegistryCredentialsPage and OptRegistryUpdatesPage to improve structure and maintainability.
- Removed obsolete logic and redundant code that is no longer needed due to the new asynchronous architecture.
- Enhanced settings management to take advantage of the just-in-time loading mechanism.
- Improved performance by ensuring registry settings are loaded asynchronously right before they are needed by the UI or application logic, minimizing unnecessary loads.
- Update documents
2024-10-01 12:19:41 +02:00
xRushG
9b28b7057d feat: Introduce RegistryLoader class with Lazy<T> and async registry settings loading
- Added the RegistryLoader class to handle loading of registry settings.
- Implemented Lazy<T> for singleton pattern to ensure only one instance of RegistryLoader is created.
- Added support for asynchronous loading of registry settings to improve performance in async workflows.
- Designed the class with thread safety in mind, leveraging ConcurrentDictionary for storage.
- Included logging of debug messages to track the load process and timing.
2024-10-01 12:08:52 +02:00
Dimitrij
0a17220446 lib updates 2024-09-29 17:05:51 +01:00
Dimitrij
7684b19d13 lib updates 2024-09-18 10:32:34 +01:00
Dimitrij
47baf3b662 fix case 2024-08-28 11:35:56 +01:00
Dimitrij
bc5e2b9437 add project line name 2024-08-28 11:29:40 +01:00
Dimitrij
cf0ef6d9d0 remove older schemes from distribution 2024-08-28 09:54:49 +01:00
Dimitrij
b6f3047f82 lib updates 2024-08-28 09:39:02 +01:00
Dimitrij
0a418561d7 Merge pull request #2629 from ianselmi/v1.77.3-dev
Made the specflow compatible with net6
2024-08-27 11:29:10 +01:00
ianselmi
6da79a41df made the specflow compatible with net6 2024-08-18 10:12:16 +02:00
Dimitrij
3ddeac6c74 lib update 2024-08-05 13:35:58 +01:00
Dimitrij
98f97c442d fix build number calculation 2024-08-05 13:33:50 +01:00
Dimitrij
472d33ba12 Merge pull request #2550 from Risae/v1.77.3-dev
CI: Add GitHub Actions workflow
2024-08-01 16:08:44 +01:00
Dimitrij
d906cfcc1e lib updates 2024-07-31 10:24:44 +01:00
Dimitrij
0f0d8bdcdb Merge pull request #2623 from magriggs/issue-2622
issue-2622: mRemoteNGInstaller - remove explicit PuTTyNG fragment, as…
2024-07-30 13:09:31 +01:00
23439176+magriggs@users.noreply.github.com
005b2485f6 issue-2622: mRemoteNGInstaller - remove explicit PuTTyNG fragment, as PuttyNG is copied implicitly by FilesFragment. Fixes #2622. 2024-07-30 09:07:55 +08:00
Dimitrij
ffd90bed70 Merge pull request #2621 from magriggs/issue-726
Issue 726 - SecureString for ConnectionInfo.Password
2024-07-29 09:05:05 +01:00
23439176+magriggs@users.noreply.github.com
70264a24ab Fix null password handling 2024-07-28 21:30:10 +08:00
23439176+magriggs@users.noreply.github.com
ad3a37fde3 Move ConnectionInfo.Password to use SecureString. Only decrypt when required for connecting. Update tests to skip tests of password values where necessary. 2024-07-28 19:56:36 +08:00
Dimitrij
9dac0eeaac Updating due https://github.com/mRemoteNG/mRemoteNG/issues/243 2024-07-16 20:55:18 +01:00
Dimitrij
8dac393bb4 libs update 2024-07-10 10:26:50 +01:00
Dimitrij
287d1a5575 Merge pull request #2611 from Wolvverine/v1.77.3-dev
correct registry path
2024-07-05 22:31:42 +01:00
Michał Panasiewicz
20278e0e53 correct registry path 2024-07-05 20:50:41 +02:00
Dimitrij
a3c3ec8c5c lib updates 2024-06-20 10:22:13 +01:00
Dimitrij
3039b25a66 versioning fix 2024-06-11 14:11:52 +01:00
Dimitrij
0b7ce92af8 libs update 2024-06-11 11:36:17 +01:00
Dimitrij
e3ec521a1d Merge pull request #2601 from xRushG/RegSettings
Enhancement of Registry Settings capabilities
2024-06-11 11:21:39 +01:00
xRushG
88999263a7 Finalize Update Options Page
This commit finalizes the Update Options Page with the following changes:
- Removed unnecessary methods due to new features of the registry handler class.
- Updated and added comments to improve code readability and understanding.
- Implemented UI optimizations such as disabling the frequency control and showing "Never" in the combobox.
- Enabled the password storing ability, making it functional but not yet usable.
- Applied some code optimizations for improved efficiency.
2024-06-11 10:07:33 +02:00
xRushG
9687ccd01d Finalize Credentials Options Page
This commit finalizes the Credentials Options Page with the following changes:
- Updated and added comments to improve code readability and understanding.
- Enabled the password storing ability, making it functional but not yet usable.
- Applied some code optimizations for improved efficiency.
2024-06-11 09:44:20 +02:00
xRushG
ca62502499 Update dependent registry options settings to use new WindowsRegistryEntry class. 2024-06-11 09:08:37 +02:00
xRushG
8b3a68f58e Removing all unnecessary old files 2024-06-11 09:07:04 +02:00
xRushG
08eb35a360 Refactor registry settings interaction classes.
This commit refactors the registry settings interaction classes by consolidating the multiple entry classes for int, string, bool, etc., into a single class that supports multiple data types. This change simplifies the code and enhances readability.

- Consolidate multiple entry classes (int, string, bool, etc.) into a single class
- Introduce WindowsRegistryEntry<T> to support multiple data types
2024-06-11 09:03:34 +02:00
Dimitrij
dec82b41f2 lib update 2024-06-10 21:14:52 +01:00
Dimitrij
b9ab5c76f7 Merge pull request #2597 from CodeCasterNL/v1.77.3-dev
Remember the opened connection file on relaunch
2024-06-07 13:33:53 +01:00
Dimitrij
6de0c536e8 Merge pull request #2591 from tecxx/develop-orig
add Clickstudios Passwordstate API connector
2024-06-07 13:32:56 +01:00
tecxx
3f0aca97bf Merge branch 'mRemoteNG:v1.77.3-dev' into develop-orig 2024-06-06 09:17:37 +02:00
Dimitrij
ccb51a2603 Merge pull request #2599 from xRushG/fix2592
Fix for #2592 compiler issue
2024-05-30 10:03:35 +01:00
xRushG
99d9e70921 Fix #2592: Prevent GeneralAppInfo.ApplicationVersion from being set in Unit Tests. Not needed for tests. 2024-05-29 15:20:56 +02:00
CodeCasterNL
b8b54271dd Save ConnectionFilePath, fix typo in identifier 2024-05-25 17:13:11 +02:00
Dimitrij
49a1549e5a Merge pull request #2596 from xRushG/OpenLogBug
Log File Opening Fails on Notification Settings Page
2024-05-24 09:12:38 +01:00
xRushG
b3f936c194 - Added my missed comment for outlined LocalSettingsManager
- Fixed a bug that occurred when trying to open the log file on the Notification settings page.
- Removed unnecessary file association when user tries to open the log file on the Notification page. The "choose file" dialog handles the extension as it is fixed on .log
- Implemented fallback to open file explorer to the specified file location if mRem fails to open the log file.
2024-05-23 21:29:35 +02:00
Robert Rostek
f3b10d4c20 update credential vault documentation 2024-05-04 18:05:28 +02:00
Robert Rostek
94b17037d0 add Clickstudios Passwordstate API connector 2024-05-04 13:49:17 +02:00
Dimitrij
83f3846ce6 lib updates
correct build number calculations - now its days from last release + hour + minute of build
some changes to migrate to json schema + preparation of using db to save settings
2024-05-03 14:40:52 +01:00
Dimitrij
711a751117 Merge pull request #2589 from xRushG/Fix-2540
LocalSettingsManager class implementation is missing (#2540)
2024-04-29 13:56:53 +01:00
xRushG
e013e32792 Fix #2540
LocalSettingsManager class implementation is missing
2024-04-29 14:34:36 +02:00
Dimitrij
6e64b09256 Merge pull request #2571 from mRemoteNG/dependabot/nuget/mRemoteNG/System.Data.SqlClient-4.8.6
Bump System.Data.SqlClient from 4.8.5 to 4.8.6 in /mRemoteNG
2024-03-11 14:31:41 +00:00
dependabot[bot]
e32d268587 Bump System.Data.SqlClient from 4.8.5 to 4.8.6 in /mRemoteNG
Bumps [System.Data.SqlClient](https://github.com/dotnet/corefx) from 4.8.5 to 4.8.6.
- [Release notes](https://github.com/dotnet/corefx/releases)
- [Commits](https://github.com/dotnet/corefx/commits)

---
updated-dependencies:
- dependency-name: System.Data.SqlClient
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 14:30:32 +00:00
Dimitrij
05635dec93 Merge pull request #2559 from xRushG/v1.77.3-dev
Configuration via windows registry (updates and credentials)
2024-02-19 18:20:44 +00:00
Schmitti91
e694923d33 Merge branch 'v1.77.3-dev' of https://github.com/xRushG/mRemoteNG into v1.77.3-dev 2024-01-28 10:16:30 +01:00
Schmitti91
7322683465 Fix AppVeyor CA2200 and optimize CreateOrSetRegistryValue method 2024-01-28 10:15:34 +01:00
Schmitti91
64688f3512 Add documentation page for registry settings (credentials) 2024-01-27 21:01:38 +01:00
Schmitti91
762760af2f Adding the configuration capability for the Credentials page through the Windows Registry. 2024-01-27 20:55:12 +01:00
Schmitti91
48e6881401 Merge branch 'v1.77.3-dev' of https://github.com/xRushG/mRemoteNG into v1.77.3-dev 2024-01-26 21:13:22 +01:00
Schmitti91
95c958136f Add Dokupage for registry settings (update) 2024-01-26 21:12:24 +01:00
Schmitti91
53a6be0f82 remove placeholder code by commenting it out in its current state. (needed for later commit) 2024-01-26 21:07:04 +01:00
Schmitti91
0b4d95be1c Renameing the common setting name "CheckForUpdate Asked" to "AllowPromptForUpdates Preference" for consistency in naming conventions. 2024-01-23 21:43:29 +01:00
Schmitti91
aad3948475 make ProxyAuthPass not usable, placeholder code 2024-01-23 16:24:28 +01:00
Schmitti91
093370081e Add capability to set Update Settings via Registry
This commit enhances the functionality of the WindowsRegistryAdvanced class to provide the capability to set up Update Settings through the Windows Registry. The changes include the addition of methods or modifications that allow users to configure update-related settings using the Registry.
2024-01-23 16:14:36 +01:00
Schmitti91
33426ceee9 Add GetDwordValue method to WindowsRegistryAdvanced
This commit introduces the GetDwordValue method in the WindowsRegistryAdvanced class, allowing the retrieval of DWORD values from the Windows Registry. The implementation uses int.TryParse for efficient parsing and handles default values gracefully.
2024-01-23 14:29:07 +01:00
Schmitti91
5f5700b948 extend Registry handling capabilities in preparation for Registry settings
Enhanced the functionality of Registry handling in preparation for managing Registry settings
2024-01-23 12:54:43 +01:00
Risae
802e9abfef CI: Create build-x86_64.yml 2024-01-11 17:46:08 +01:00
Dimitrij
45149d6547 Merge pull request #2549 from magriggs/issue-2487
Issue 2487
2024-01-05 12:07:05 +00:00
23439176+magriggs@users.noreply.github.com
595681a982 Remove boilerplate code 2024-01-05 14:28:33 +08:00
23439176+magriggs@users.noreply.github.com
e1cac723d6 First commit of SecureCRT import functionality. 2024-01-05 13:59:36 +08:00
Dimitrij
683f84f053 Merge pull request #2545 from magriggs/issue-1308
Some fixes for issue mRemoteNG#1308. Inspired by dockpanelsuite/dockp…
2024-01-03 13:15:15 +00:00
23439176+magriggs@users.noreply.github.com
b6d882a06f Some fixes for issue mRemoteNG#1308. Inspired by dockpanelsuite/dockpanelsuite#526 (comment). Main change is to enable minimize and close buttons 2024-01-03 20:39:36 +08:00
Dimitrij
9d4bfbcb1c lib updates 2023-12-13 21:56:25 +00:00
Dimitrij
b563747046 Merge pull request #2534 from jcefoli/v1.77.3-dev
Handle case where COM object becomes separated from RCW
2023-12-13 21:47:47 +00:00
Joe Cefoli
1ebd0a430f Remove unnecessary line break 2023-12-13 14:42:13 -05:00
Joe Cefoli
ef4366be4d Handle case where COM object becomes sepatated from Runtime Callable Wrapper (resizing main window after disconnecting from fit to panel session) 2023-12-13 14:34:33 -05:00
Dimitrij
484283fef8 Merge pull request #2533 from tommwq/v1.77.3-dev
support local PowerShell connection
2023-12-13 11:02:10 +00:00
tommwq
b546a48a5f support local PowerShell connection 2023-12-12 23:26:58 +08:00
Dimitrij
51bd64d005 Merge pull request #2530 from BestiaPL/v1.77.3-dev
Update Language.pl.resx
2023-12-11 10:32:02 +00:00
Andrzej Rudnik
2c65d12b92 Update Language.pl.resx 2023-12-05 21:17:59 +01:00
Dimitrij
c43a3b5115 Merge pull request #2529 from xRushG/v1.77.3-dev
Pre-build event powershell code update #2524
2023-12-04 17:47:04 +00:00
Schmitti91
515778b13d Pre-build event powershell code update/fix 2023-12-02 21:59:56 +01:00
Dimitrij
bb4b9ce9e7 Merge branch 'v1.77.3-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.77.3-dev 2023-11-29 09:42:06 +00:00
Dimitrij
1d6707fecd #2528
fix typo
2023-11-29 09:41:56 +00:00
Dimitrij
5b2a46ed91 Merge pull request #2523 from zeze0556/v1.77.3-dev
fixed mysql error
2023-11-20 09:27:46 +00:00
unknown
24fa97da4c fixed mysql error 2023-11-20 10:21:59 +08:00
Dimitrij
b0109c2581 lib update 2023-11-10 11:29:18 +00:00
Dimitrij
20f237ec4c Merge pull request #2519 from BestiaPL/v1.77.3-dev
Update Language.pl.resx
2023-11-04 23:38:09 +00:00
Andrzej Rudnik
6ea535ae29 Update Language.pl.resx 2023-11-04 16:59:46 +01:00
Dimitrij
57ca8a3eee Merge pull request #2514 from Schmitti91/v1.77.3-dev
Update Registry interaction capabilities.
2023-10-24 13:33:12 +01:00
Schmitti91
307ea42a0f Cleanup of references and correction of comments. 2023-10-23 07:58:13 +02:00
Schmitti91
ac41c04f67 mRemoteNG tools WindowsRegistry has been revised and the Windows Registry reading capabilities have been expanded in preparation for working with Registry Keys options. The "RegistryHive" enum was deleted because it is contained in "Microsoft.Win32". 2023-10-22 12:18:42 +02:00
Dimitrij
b3496e79de lib update 2023-10-17 16:05:09 +01:00
Dimitrij
4f7aa755e3 Merge pull request #2508 from RFC1920/v1.77.3-dev-putty
bugfix for Putty import
2023-10-17 09:30:56 +01:00
Dimitrij
d3af50f009 lib updates 2023-10-16 13:59:04 +01:00
RFC1920
ba77a7b7c0 Update Putty registry import to fix connection name, username, and port setting 2023-10-16 05:32:34 -05:00
Dimitrij
666a0e1537 Merge pull request #2507 from RFC1920/v1.77.3-dev-putty
Add Putty registry import
2023-10-16 11:32:21 +01:00
RFC1920
96f4307910 Add Putty registry import 2023-10-16 04:50:35 -05:00
Dimitrij
81e997eb60 Merge pull request #2506 from Schmitti91/v1.77.3-dev
Enhancing the PowerShell Connection.
2023-10-16 09:56:50 +01:00
Schmitti91
7132c6e7c2 Removing the "-NoProfile" option from PowerShell.exe parameters. 2023-10-15 21:48:01 +02:00
Schmitti91
cb01e92fe2 Enhancing the PowerShell Connection. The PowerShell script is designed to simplify multiple login attempts to a remote host using user-provided credentials, or not. In addition, granular error handling has been added to prevent errors, such as the conversion of empty or null strings to SecureString. Furthermore, hostname, username, and password are now passed as parameters in the PowerShell script block (#2195). 2023-10-15 21:13:36 +02:00
Dimitrij
2c9c512d74 Merge pull request #2503 from WojciechNagorski/new-sshnet
Update SSH.NET to 2023.0.0
2023-10-11 13:55:07 +01:00
Wojciech Nagórski
8d05c9040b Update SSH.NET to 2023.0.0 2023-10-11 14:45:01 +02:00
Dimitrij
4e994fb202 Merge pull request #2502 from BestiaPL/v1.77.3-dev
Updated Polish translation
2023-10-09 16:45:55 +01:00
Dimitrij
ab2a778694 Merge pull request #2496 from tecxx/develop-orig
use pwfile instead of cleartext password for putty connections
2023-10-09 09:27:00 +01:00
Andrzej Rudnik
115375820d Update Language.pl.resx 2023-10-08 20:51:22 +02:00
Andrzej Rudnik
8cb753916b Update Language.pl.resx 2023-10-05 23:36:45 +02:00
Robert Rostek
ec3a0cee9b remove unnecessary usings 2023-10-03 10:55:27 +02:00
Robert Rostek
13f8f82537 update changelog 2023-10-03 10:53:34 +02:00
Robert Rostek
54544dd2aa use pwfile instead of pw for puttyng 2023-10-03 10:52:41 +02:00
Dimitrij
ac99115424 Merge pull request #2482 from simonai1254/fix-2195
Fix 2195
2023-08-31 16:37:41 +01:00
Simon Monai
d6c6038193 Update CHANGELOG.md 2023-08-30 23:55:40 +02:00
Simon Monai
c97b84e4a2 Quote variable for #2195
In order to interpret variable as string and not arbitrary code
2023-08-30 23:46:42 +02:00
Dimitrij
6698ec0b06 Update README.md 2023-08-02 13:14:28 +01:00
Dimitrij
290b1c4de5 Update README.md 2023-08-02 13:14:02 +01:00
Dimitrij
d497a50727 Update README.md 2023-08-02 13:13:23 +01:00
Dimitrij
2837c91f5e Update README.md 2023-08-02 13:12:34 +01:00
Dimitrij
9a717f28c6 upd 2023-08-02 13:03:14 +01:00
Dimitrij
e416c2c9c9 upd 2023-08-02 13:01:21 +01:00
Dimitrij
331cdaa6ce upd 2023-08-02 12:54:27 +01:00
Dimitrij
aa329b1a9a upd 2023-08-02 12:51:30 +01:00
Dimitrij
16c080942d upd table 2023-08-02 12:47:15 +01:00
Dimitrij
4acc111b3d upd 2023-08-02 12:39:29 +01:00
Dimitrij
afe453d371 upd 2023-08-02 12:37:25 +01:00
Dimitrij
e93c120dac update table 2023-08-02 12:34:16 +01:00
Dimitrij
2f701458f4 mistype fix 2023-08-02 12:27:30 +01:00
Dimitrij
6a35961dfb Merge branch 'v1.77.3-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.77.3-dev 2023-08-02 12:24:16 +01:00
Dimitrij
1a90095ba3 add add-ons list 2023-08-02 12:24:05 +01:00
Dimitrij
5dc87213b5 Merge pull request #2462 from jurepurgar/rdgw-access-token
Support for gatewayaccesstoken
2023-07-18 16:24:49 +01:00
Jure Purgar
ff8044b09b Delete resx 2023-07-18 16:23:48 +02:00
Jure Purgar
f1fa40f6fd Initial implementation of gatewayaccesstoken support 2023-07-18 16:07:06 +02:00
Dimitrij
9bc5b62828 enable signing 2023-07-15 22:44:26 +01:00
Dimitrij
5aae586812 lib update 2023-07-15 22:11:03 +01:00
Dimitrij
bc5e5be42e update cert 2023-07-15 22:06:47 +01:00
Dimitrij
be68573378 lib updates 2023-07-05 14:33:39 +01:00
Dimitrij
cf0bc3d270 fix links 2023-07-05 12:00:14 +01:00
Dimitrij
d5773eb2e9 doc update
adding image, fix link
2023-07-05 09:29:19 +01:00
Dimitrij
50523e5b67 update docs 2023-07-04 15:59:48 +01:00
Dimitrij
feeca86c6a small fix 2023-06-21 21:03:57 +01:00
Dimitrij
a2e2671483 update links 2023-06-21 20:55:35 +01:00
Dimitrij
0b4d6ef8ef update index 2023-06-21 20:48:23 +01:00
Dimitrij
bdf0e532a5 #2447
add screenshots for themes
2023-06-21 20:46:37 +01:00
Dimitrij
d8cf72d09b Merge branch 'v1.77.3-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.77.3-dev 2023-06-21 12:08:25 +01:00
Dimitrij
6c3ec8f058 update puttyng to actual version 2023-06-21 12:08:07 +01:00
Dimitrij
fbd9b76a09 Merge pull request #2449 from simonai1254/update_documentation
Improve PuttyNG Issue Description
2023-06-21 09:41:55 +01:00
Simon Monai
bee216220a Improve PuttyNG Issue Description
Update PuttyNG Documentation
Do not recommend insecure changes
2023-06-21 09:29:05 +02:00
Dimitrij
4ca7f34989 code corrections 2023-06-16 19:24:33 +01:00
Dimitrij
51015e6b51 lib update 2023-06-16 19:24:00 +01:00
Dimitrij
46100d6d94 lib update 2023-06-08 10:58:05 +01:00
Dimitrij
c94eac3863 Merge pull request #2416 from BlueBlock/streamline_build_scripts_for_appveyor
modify build scripts for appveyor
2023-04-07 15:19:44 +01:00
BlueBlock
965b0e5b6d Update update_and_upload_assemblyinfocs.ps1
enable assemblyinfo.cs update when not (CI)
2023-03-31 11:27:49 -04:00
BlueBlock
000cea1dda test repo name 2023-03-31 11:16:40 -04:00
BlueBlock
6d4d60e3b1 Update postbuild.ps1
fix if matching
2023-03-31 11:02:49 -04:00
BlueBlock
9957282bfa Update postbuild_installer.ps1
disable assemblyinfo.cs update temporarily
2023-03-31 11:01:48 -04:00
BlueBlock
9b603dd956 Update postbuild.ps1
check variables
2023-03-31 10:10:10 -04:00
BlueBlock
759ee55780 modify build scripts for appveyor 2023-03-31 09:14:35 -04:00
Dimitrij
93b2edb5a5 Merge pull request #2415 from BlueBlock/fix_window_restore_resize
Update RdpProtocol8.cs
2023-03-28 14:59:36 +01:00
BlueBlock
4d49c14bb9 Update RdpProtocol8.cs
resize when restored from minimize
2023-03-28 08:49:52 -04:00
Dimitrij
6398808a0a corrected slashes 2023-03-25 22:29:53 +00:00
Dimitrij
64e20f3665 update 2023-03-25 21:45:22 +00:00
Dimitrij
3b542d8868 excluding AssemblyInfo from accidental update in repo 2023-03-25 21:40:42 +00:00
Dimitrij
d5af0e43a5 add condition 2023-03-25 18:44:40 +00:00
Dimitrij
7a8ef87dc7 run string as a command 2023-03-25 18:33:50 +00:00
Dimitrij
7a5293cde3 add wrapping ' 2023-03-25 18:24:08 +00:00
Dimitrij
26400631a0 replace " by ' to avoid path brake 2023-03-25 18:20:46 +00:00
Dimitrij
97a5c584be increase build number only then release in appveyor 2023-03-25 18:16:03 +00:00
Dimitrij
0700b6573c Merge pull request #2413 from BlueBlock/fix_running_of_unit_tests
Fix running of unit tests
2023-03-25 17:17:32 +00:00
Dimitrij
d8f66a4188 Merge pull request #2412 from BlueBlock/update_build_script
Update find_vstool.ps1
2023-03-25 16:39:49 +00:00
BlueBlock
c22971cf54 add unit test references
unit tests were not able to run since the refs were missing
2023-03-25 12:08:52 -04:00
BlueBlock
9d2dd5dd93 remove variable not used 2023-03-25 12:08:02 -04:00
BlueBlock
c19d512631 Update Logger.cs
code formatting and using adjustment
2023-03-25 12:07:28 -04:00
BlueBlock
9efda5fef3 lib update 2023-03-25 12:06:53 -04:00
BlueBlock
a27cdcfdb5 Update find_vstool.ps1
Do not attempt to verify the fingerprint since it changes often. It seems to be overkill to check it.
2023-03-25 12:03:37 -04:00
Dimitrij
5667a07b25 Merge pull request #2411 from BlueBlock/fix_log4net_logging
fix log4net logging
2023-03-25 07:04:21 +00:00
BlueBlock
7d0bd85466 fix log4net logging 2023-03-24 14:49:27 -04:00
Dimitrij
4f269442a6 Merge pull request #2410 from BlueBlock/fix_notificiation_bug_introduced
Fix notification bug introduced
2023-03-24 18:09:14 +00:00
BlueBlock
16d182fca2 Update NotificationPanelMessageWriter.cs
fix notification bug, text not displaying
2023-03-24 11:53:05 -04:00
BlueBlock
6d1a206cc9 code formatting 2023-03-24 11:51:33 -04:00
Dimitrij
d8a7fdbf4f lib update 2023-03-24 15:22:10 +00:00
Dimitrij
516b365ebc Merge pull request #2401 from BlueBlock/fix_options_ui_sizing
options pages size and position fixes
2023-03-24 07:16:15 +00:00
BlueBlock
6da6a5ede9 options pages size and position fixes
- options page was too small
- some tabs needed adjustment for spacing
2023-03-24 03:02:27 -04:00
Dimitrij
67244f1957 Merge pull request #2400 from BlueBlock/fix_database_columns_and_some_tests
Fix database columns and some tests
2023-03-24 06:08:48 +00:00
BlueBlock
0a304e9a03 add missing objects needed in tests 2023-03-24 01:35:37 -04:00
BlueBlock
05154af606 code formatting 2023-03-24 01:34:51 -04:00
BlueBlock
7454b15ec9 adjust app to use 2.8 xsd schema 2023-03-24 01:33:30 -04:00
BlueBlock
38f435f639 Update RemoteDesktopConnectionDeserializer.cs
add missing serializer
2023-03-24 01:32:21 -04:00
BlueBlock
5f38ec7210 Update SqlVersion29To30Upgrader.cs
add missing DB column UserViaAPI
2023-03-24 01:31:41 -04:00
BlueBlock
ca143bf969 Update mremoteng_confcons_v2_8.xsd
inherit should be boolean
2023-03-24 01:29:39 -04:00
BlueBlock
402e5c3665 Create mRemoteNG.lutconfig
live test runner config file
2023-03-24 01:27:52 -04:00
BlueBlock
290dbcc3ed Update SqlDatabaseMetaDataRetriever.cs
- add missing db column UserViaAPI
- formatting and remove commented code
2023-03-24 01:26:44 -04:00
Dimitrij
a7749c70e7 Merge pull request #2399 from BlueBlock/fix_database_use
Fix database use
2023-03-23 22:01:40 +00:00
BlueBlock
9f0cfd5f22 Update CsvConnectionsDeserializerMremotengFormatTests.cs
fix test
2023-03-23 17:47:27 -04:00
BlueBlock
f47434c09e Merge remote-tracking branch 'upstream/v1.77.3-dev' into fix_database_use 2023-03-23 17:44:39 -04:00
BlueBlock
795a4b571e Update AbstractConnectionRecord.cs
defaults set not needed
2023-03-23 17:26:11 -04:00
BlueBlock
f71727e05e Update SqlConnectionsUpdateChecker.cs
code formatting
2023-03-23 16:34:37 -04:00
BlueBlock
d2fa8e86b1 Update DatabaseConnectorFactory.cs
add TODO
2023-03-23 16:34:15 -04:00
BlueBlock
23dcd9d30f Update MySqlDatabaseConnector.cs
add TODO
2023-03-23 16:33:55 -04:00
BlueBlock
d6890c6ecd refactor to newer c# code statements 2023-03-23 16:32:14 -04:00
BlueBlock
b1a6ba78d4 fix database upgrades
- fix tested db upgrading for each version
- fix version check for 3.0
2023-03-23 16:31:28 -04:00
BlueBlock
e6abe3f3a1 simple code reformatting 2023-03-23 16:30:04 -04:00
BlueBlock
6c32540ecb refactor class mssql to sql
refactor class mssql to sql
code formatting
refactor null check
use db TRUNCATE instead of DELETE (mysql warns about mass DELETE)
2023-03-23 16:27:17 -04:00
BlueBlock
951ad5cd0a Update ConnectionsService.cs
use universal time for db time
2023-03-23 16:23:45 -04:00
BlueBlock
5f56477123 Update MiscTools.cs
- add GetBooleanValue method for getting bool from databases
- use universal time for db time
2023-03-23 16:22:38 -04:00
BlueBlock
3180f38a1d Update AbstractConnectionRecord.cs
fix to add redirectdiskdrives default value
2023-03-23 16:21:34 -04:00
BlueBlock
07f15993e5 Update SettingsLoader.cs
refactor null check
2023-03-23 16:20:55 -04:00
BlueBlock
fc8e9c7689 rename class from mssql to sql
This class is not dedicated to mssql but is used by all sql databases. Rename to reflect this.
2023-03-23 16:20:01 -04:00
BlueBlock
7cb20b9e28 Update Runtime.cs
use universal time for stored DB time since users can span time zones
2023-03-23 15:16:28 -04:00
BlueBlock
ffc2351d64 Update ConnectionsFileInfo.cs
latest db version should be 3.0
2023-03-23 15:15:36 -04:00
Dimitrij
f10fec61ac Merge pull request #2398 from BlueBlock/fix_deserializer_test
Update CsvConnectionsDeserializerMremotengFormatTests.cs
2023-03-23 16:39:01 +00:00
BlueBlock
e41200067c Update CsvConnectionsDeserializerMremotengFormatTests.cs
fix test, missing changes needed for RedirectDrives PR
2023-03-23 12:18:28 -04:00
Dimitrij
30f6d3dde7 Merge pull request #2397 from BlueBlock/fix_remove_exception_test
Update ProgramRoot.cs
2023-03-21 20:59:32 +00:00
BlueBlock
7f9dcb11f0 Update ProgramRoot.cs
remove exception test
2023-03-21 16:49:59 -04:00
Dimitrij
d5fa2f62c0 Merge pull request #2396 from BlueBlock/fix_load_misssing_localconnectionproperties_xml_file
Fix load misssing localconnectionproperties xml file
2023-03-21 20:44:59 +00:00
Dimitrij
fa7967190d Merge pull request #2395 from integratorbit/v1.77.3-dev
Improve Disk Drive Redirection
2023-03-21 20:33:33 +00:00
BlueBlock
20e04c0d75 resolve conflicts 2023-03-21 16:04:32 -04:00
BlueBlock
d5eea9db20 Merge remote-tracking branch 'upstream/v1.77.3-dev' into pr/2395 2023-03-21 16:00:50 -04:00
BlueBlock
8cb598ce25 Update FileDataProvider.cs
fix creation of file LocalConnectionProperties.xml if it does not exist
2023-03-21 15:26:01 -04:00
BlueBlock
f3a57d7f23 move filename value LocalConnectionProperties.xml into settings file 2023-03-21 15:25:32 -04:00
BlueBlock
14e47def9d Update SqlDatabaseMetaDataRetriever.cs
fix exception casting int64 to int32, unbox and cast to int
2023-03-21 15:24:49 -04:00
Dimitrij
7573081d7a Merge pull request #2394 from BlueBlock/fix_rdp_operation
Fix RDP operation
2023-03-21 18:48:56 +00:00
Dimitrij
aaca581324 Merge pull request #2393 from BlueBlock/do_not_display_splash_on_taskbar
Do not show splash on taskbar
2023-03-21 18:06:52 +00:00
BlueBlock
43c2797a51 Update ProgramRoot.cs
do not show splash on taskbar
2023-03-21 13:56:55 -04:00
integratorbit
da6bbf65f0 Improve Disk Drive Redirection 2023-03-21 14:31:15 -03:00
BlueBlock
7d0cbf423e add resize fallback
Add resize fallback if the target OS fails to resize using the most current method. (for example, this occurs with Server2008R2)
2023-03-21 13:03:21 -04:00
BlueBlock
d94eab71da Update RdpProtocol8.cs
remove test code
2023-03-21 12:58:58 -04:00
BlueBlock
b0a8ccc5b6 multiple RDP issues fixed
- fix scale of RDP session when moving between 4k and 1080 monitors etc.
- fix inefficient continuous RDP screen resizing while the window size is changed
- add a RDP version check before attempting to use each RDP version and properties
2023-03-21 12:23:59 -04:00
BlueBlock
5cbf1f1568 adjust method access level of common protocol methods 2023-03-21 12:22:20 -04:00
BlueBlock
22cde74db2 Delete RdpProtocol6.cs
class renamed to RdpProtocol
2023-03-21 12:07:18 -04:00
BlueBlock
cdc3759c04 Update Optional.cs
- use more efficient empty assignments
- use string interpolation
- adjust method access to minimal needed
2023-03-21 12:06:38 -04:00
BlueBlock
38abc8a167 Update NotificationPanelMessageWriter.cs
check for object _messageWindow before using it to avoid an exception
2023-03-21 12:04:52 -04:00
BlueBlock
7c7c7086ce Update RdpProtocolFactory.cs
class RdpProtocol6 renamed to RdpProtocol
2023-03-21 12:04:03 -04:00
BlueBlock
50daf64025 Update ProtocolBase.cs
- add check for object _interfaceControl before using it
- adjust methods to protected where appropriate
- remove unused using
2023-03-21 12:03:28 -04:00
BlueBlock
dc38df5389 Update ConnectionsService.cs
use object initializer and remove unused using
2023-03-21 12:00:54 -04:00
BlueBlock
a46b2c9d98 Update ProtocolFactory.cs
remove unused usings
2023-03-21 11:57:34 -04:00
BlueBlock
200756361c rename base class of RDP
So it is consistent with other protocols, rename the RDP base class to not reflect a version.
2023-03-21 11:55:17 -04:00
Dimitrij
877a1e4cd0 Merge pull request #2391 from BlueBlock/add_set_devenvdir_to_prebuild
Update mRemoteNG.csproj
2023-03-20 21:16:18 +00:00
BlueBlock
a382206f5a Update mRemoteNG.csproj
add setting of DevEnvDir in prebuild
2023-03-20 16:31:22 -04:00
Dimitrij
1af7a622a6 Merge pull request #2388 from BlueBlock/fix_remove_unsused_class
Delete RDPVersions.cs
2023-03-17 17:54:50 +00:00
Dimitrij
3f95b2aa7e Merge pull request #2387 from BlueBlock/fix_exception_in_disposeinterface
Update ProtocolBase.cs
2023-03-17 17:48:43 +00:00
BlueBlock
b7871e2e04 Delete RDPVersions.cs
Class is unused and not needed.
2023-03-17 13:18:14 -04:00
BlueBlock
6b6ceda497 Update ProtocolBase.cs
check if object is disposed before working with it
2023-03-17 13:07:58 -04:00
Dimitrij
b01b8e4bc8 adjust installer script 2023-03-17 00:42:46 +00:00
Dimitrij
20f7cb1cd0 update move installer 2023-03-17 00:17:52 +00:00
Dimitrij
3e1ee0056d small amendments 2023-03-16 23:01:06 +00:00
Dimitrij
a3a851e57f Merge pull request #2383 from BlueBlock/fix_null_check_not_needed
Update PuttySessionsRegistryProvider.cs
2023-03-16 20:10:57 +00:00
BlueBlock
cf650d4318 use more efficient array 2023-03-16 16:10:01 -04:00
Dimitrij
58e7420f04 Merge pull request #2386 from BlueBlock/fix_suppress_com_warnings_on_release_build
Update mRemoteNG.csproj
2023-03-16 19:24:18 +00:00
Dimitrij
aedaf084d9 Merge pull request #2382 from BlueBlock/fix_more_CA1416_warnings
fix more CA1416 warnings
2023-03-16 19:04:47 +00:00
BlueBlock
c18f638989 Update mRemoteNG.csproj
suppress com warnings on release build, but display warnings on debug builds
2023-03-16 15:00:46 -04:00
BlueBlock
fb50cdc058 Update PuttySessionsRegistryProvider.cs
remove unreachable code
- no need to test for null, PuttySessionsKey is a const with a value set on instantiation
2023-03-16 14:35:39 -04:00
BlueBlock
68e702c344 fix more CA1416 warnings 2023-03-16 14:15:44 -04:00
Dimitrij
651b18a8b3 Merge pull request #2381 from BlueBlock/fix_obsolete_SHA1CryptoServiceProvider
Update PuttyKeyFileGenerator.cs
2023-03-16 17:11:53 +00:00
BlueBlock
2174e56407 Update PuttyKeyFileGenerator.cs
Replace use of obsolete SHA1CryptoServiceProvider with replacement call.
2023-03-16 12:59:28 -04:00
Dimitrij
ea27a4da02 Merge pull request #2380 from BlueBlock/fix_appveyor_bulk_of_changes
appveyor build automation
2023-03-16 16:50:53 +00:00
BlueBlock
321fd1162b appveyor build automation
bulk of the changes for appveyor automation
2023-03-16 11:08:27 -04:00
Dimitrij
c51f38ddc7 Merge pull request #2379 from BlueBlock/fix_appveyor
additional appveyor change
2023-03-15 19:05:36 +00:00
BlueBlock
b34cd1acc5 additional appveyor change 2023-03-15 14:51:04 -04:00
Dimitrij
43f20bb83a Merge pull request #2378 from BlueBlock/fix_adj_appveyor_build
appveyor build adjustment
2023-03-15 16:57:16 +00:00
Dimitrij
79b8ddef15 Merge pull request #2377 from BlueBlock/fix_putty_registry_watcher
Fix putty registry watcher
2023-03-15 16:49:29 +00:00
BlueBlock
2642ea0601 appveyor build adjustment
Further work to test appveyor builds
2023-03-15 12:46:37 -04:00
Dimitrij
7a2f934f6a Merge pull request #2376 from BlueBlock/add_rdp_protocol_rdc11
add rdp protocol rdc11
2023-03-15 12:49:03 +00:00
BlueBlock
f299fefcdc Update PuttySessionsRegistryProvider.cs
Remove previous fixes return statement
2023-03-15 08:22:29 -04:00
BlueBlock
209319f460 Update PuttySessionsRegistryProvider.cs
Update the latest fix for the putty registry watcher, to create the registry path if it does not exist, so the watcher is able to always run.
2023-03-15 08:14:52 -04:00
BlueBlock
5ec4f4dcea add rdp rdc11 2023-03-15 08:04:01 -04:00
Dimitrij
16f67d58d1 Merge pull request #2372 from BlueBlock/fix_exception_when_no_putty_sessions_exist
fix exception when no putty sessions exist
2023-03-14 19:32:10 +00:00
Dimitrij
542d794ab4 Merge pull request #2373 from BlueBlock/fix_exception_with_notification_on_app_closing
Update MessageFocusDecorator.cs
2023-03-14 17:03:33 +00:00
Dimitrij
ede1573b56 Merge pull request #2375 from BlueBlock/fix_exception_for_protocolbase_when_closing_app
Update ProtocolBase.cs
2023-03-14 16:24:43 +00:00
Dimitrij
bc8eec8887 Merge pull request #2374 from BlueBlock/add_rdp_protocol_rdc10
add rdc10 protocol
2023-03-14 15:35:38 +00:00
Dimitrij
59b0654b95 Merge pull request #2371 from BlueBlock/fix_installer_build_order
Update mRemoteNG.sln
2023-03-14 15:24:05 +00:00
Dimitrij
cae3477591 Merge pull request #2370 from BlueBlock/fix_file_fragments
Update FilesFragment.wxs
2023-03-14 15:22:30 +00:00
BlueBlock
6a3115b80a Update ProtocolBase.cs
Do not attempt to dispose the control if the control is already closed, closing or disposed.
2023-03-14 04:44:42 -04:00
BlueBlock
1232d7c288 add rdc10 protocol 2023-03-13 22:38:13 -04:00
BlueBlock
8e1b4c9271 Update MessageFocusDecorator.cs
do not attempt to focus the notification panel if the application is closing, otherwise exceptions will occur since _frmMain is closing
2023-03-13 20:08:48 -04:00
BlueBlock
0f8810e22a fix exception when no putty sessions exist
When putty is not installed or no sessions exist in the registry, an exception is thrown when starting the eventwatcher. Only start the eventwatcher if sessions registry path exists.
2023-03-13 18:53:00 -04:00
BlueBlock
7bbff38b6b Update mRemoteNG.sln
fix install build order by also including building mremoteng
2023-03-13 18:47:14 -04:00
BlueBlock
4a7b8fa250 Update FilesFragment.wxs
fix some dll paths and remove unused dll
2023-03-13 18:44:26 -04:00
Dimitrij
53acc94976 Merge pull request #2369 from BlueBlock/fix_find_vstool.ps1
Update find_vstool.ps1
2023-03-13 14:11:00 +00:00
BlueBlock
ab8d83b12d Update find_vstool.ps1
add vs2022 cert thunbprint
fix function which tests if a file is executable
2023-03-13 09:46:27 -04:00
Dimitrij
713a0a6174 more fixes for CA1416 2023-03-11 13:13:19 +00:00
Dimitrij
a3b9695b81 lib replace 2023-03-11 12:38:53 +00:00
Dimitrij
85dd381bd9 lib update 2023-03-11 12:21:17 +00:00
Dimitrij
05b418e9b6 more fix for Warning CA1416 2023-03-11 12:20:19 +00:00
Dimitrij
40af584afe fix for Warning CA1416 2023-03-11 01:46:11 +00:00
Dimitrij
93fe32491b couple of fixes 2023-03-10 23:00:05 +00:00
Dimitrij
4b67fcb0d4 fix splash screen version logo 2023-03-09 09:12:40 +00:00
Dimitrij
0b0c92717d fix format to avoid error on file name 2023-03-08 22:15:25 +00:00
Dimitrij
4564cafa85 more fixes for CA1416 2023-03-08 21:50:23 +00:00
Dimitrij
2e21f8d03a add form 2023-03-08 20:51:58 +00:00
Dimitrij
4336254d56 fix naming after rename 2023-03-08 20:36:00 +00:00
Dimitrij
bc5e9279a5 fix naming to be inline with Others 2023-03-08 20:28:31 +00:00
Dimitrij
1bfbb956c2 fix CA1416: Validate platform compatibility
https://learn.microsoft.com/en-gb/dotnet/fundamentals/code-analysis/quality-rules/ca1416
2023-03-08 20:23:38 +00:00
Dimitrij
b336db773c Merge pull request #2367 from BlueBlock/update_build_scripts
Update build scripts
2023-03-08 19:32:06 +00:00
BlueBlock
30e7d2424c Merge remote-tracking branch 'upstream/v1.77.3-dev' into update_build_scripts 2023-03-08 14:08:46 -05:00
kmscode
cbb8c0234d fixed a few warnings 2023-03-05 20:10:32 -05:00
kmscode
95469107fd don't need to manually editbin for large address aware on x64 targets 2023-03-05 15:37:49 -05:00
Dimitrij
ba0058c0b9 fix installer paths 2023-03-04 22:49:23 +00:00
Dimitrij
8f35afe353 fix for portable version 2023-03-04 21:02:19 +00:00
Dimitrij
aaf219eb90 revert check due set variable explicitly in appveyor 2023-03-04 20:32:22 +00:00
Dimitrij
1167794d58 fix condition to work in appveyor env. 2023-03-04 18:38:12 +00:00
Dimitrij
929babd69a Update change log 2023-03-04 00:35:35 +00:00
Dimitrij
870b7c1ffd update links 2023-03-04 00:07:19 +00:00
Dimitrij
9ed9d835b9 file harvester update 2023-03-04 00:06:55 +00:00
Dimitrij
985feb4b91 name space sync 2023-03-03 23:25:18 +00:00
Dimitrij
c43c85bb21 lib Update
correction for update window
2023-03-03 22:19:38 +00:00
Dimitrij
038074131c add template for build versioning 2023-03-03 20:45:40 +00:00
Dimitrij
ab9156a7d3 Merge branch 'v1.77.3-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.77.3-dev 2023-03-03 15:44:34 +00:00
Dimitrij
f4b76818e8 lib update 2023-03-03 15:42:33 +00:00
Dimitrij
12a399a354 Merge pull request #2364 from BlueBlock/update_sql_usage_instructions
update sql usage doc
2023-03-02 13:55:24 +00:00
BlueBlock
dfdbbea85c sql usage doc updated 2023-03-02 08:32:07 -05:00
BlueBlock
5fad5ec4c7 Merge remote-tracking branch 'upstream/v1.77.3-dev' into update_build_scripts 2023-03-02 08:28:10 -05:00
Dimitrij
fe94da4727 lib update 2023-03-02 09:42:43 +00:00
Dimitrij
361ae3af50 Merge pull request #2362 from BlueBlock/fix_use_of_sql_database
Fix use of sql database
2023-03-02 09:34:06 +00:00
BlueBlock
a2e302ebfa add check for appveyor only actions in scripts 2023-03-01 15:31:54 -05:00
BlueBlock
186ab9c00e Merge remote-tracking branch 'upstream/v1.77.3-dev' into update_build_scripts 2023-03-01 14:50:49 -05:00
BlueBlock
9a2453524a update db to 2.9 2023-03-01 14:19:41 -05:00
BlueBlock
3a2e128e98 add missing columns to serializer 2023-03-01 11:22:18 -05:00
BlueBlock
1bd2dbf9f0 modify schema to be consistent 2023-03-01 11:21:55 -05:00
BlueBlock
b7f880e7f8 modify sql bit columns to be consistent 2023-03-01 11:21:26 -05:00
BlueBlock
13cd926b4f add missing columns 2023-03-01 09:37:10 -05:00
BlueBlock
6fb7b1c6e4 update docs to reflect auto-schema generation 2023-03-01 07:29:31 -05:00
BlueBlock
8f35015a6c fix db upgrade
fix db upgrade 2.8->2.9
2023-03-01 07:11:45 -05:00
BlueBlock
a926bccb97 fix db upgrade
fix db upgrade 2.7->2.8
2023-03-01 07:11:30 -05:00
BlueBlock
b9f2abf5eb add db schema initialization on a new empty database 2023-03-01 07:10:29 -05:00
BlueBlock
8dc5bda171 Update SqlDatabaseVersionVerifier.cs
fix CRLF of file
2023-03-01 07:09:33 -05:00
Dimitrij
d24bdb543b lib update 2023-02-26 21:40:14 +00:00
BlueBlock
dc921161e4 Merge remote-tracking branch 'upstream/v1.77.3-dev' into update_build_scripts 2023-02-25 17:37:58 -05:00
Dimitrij
8e248cb545 lib update 2023-02-21 21:48:20 +00:00
BlueBlock
ae93be4cfb make website update optional 2023-02-20 12:39:29 -05:00
BlueBlock
42949fbe91 update build scripts to create update files 2023-02-20 05:21:11 -05:00
Dimitrij
21dd175456 lib update 2023-02-17 19:49:47 +00:00
BlueBlock
a3df0da26b widen the version label further 2023-02-16 16:55:40 -05:00
BlueBlock
6b9d4fd107 increase label width for version on splashscreen
Widen to support longer version numbers such as 1.77.3.1234
2023-02-16 16:27:17 -05:00
BlueBlock
fac2ae6399 Merge remote-tracking branch 'upstream/v1.77.3-dev' into update_build_scripts 2023-02-16 15:47:57 -05:00
Dimitrij
46732803c3 Merge pull request #2356 from BlueBlock/improve_options_page_speed
improve speed for the display of the options page
2023-02-16 20:16:47 +00:00
BlueBlock
39d205bd4d improve speed for the display of the options page
rather than create the options page on demand, instantiate the options page immediately upon app start though in the background using idle time.
2023-02-16 14:57:11 -05:00
Dimitrij
8e2bd7997e update "Known Issues" regarding how-to update PuttyNG to latest 2023-02-16 11:12:44 +00:00
Dimitrij
76db9a6c62 Merge pull request #2352 from pakass/SSH.NET-update
SSH.NET Update
2023-02-16 09:45:30 +00:00
pakass
c03d5e891d update Changelog 2023-02-15 15:15:55 +01:00
pakass
b21f6e8ce7 update SSH.NET 2023-02-15 15:11:22 +01:00
BlueBlock
4b916f1888 update build scripts 2023-02-14 19:38:08 -05:00
Dimitrij
1f80e5339b Merge pull request #2348 from BlueBlock/remove_mat_kit
Remove mat kit included unintentionally
2023-02-13 09:30:48 +00:00
BlueBlock
dc616f8ea1 remove mat kit included unintentionally
It appears the mat kit config was included along with some .xlf definitions on the csproj

Remove th mat kit and references
2023-02-12 16:39:30 -05:00
Dimitrij
dc6588243f libs update 2023-02-11 21:31:12 +00:00
Dimitrij
690d3b0d4e Merge pull request #2347 from savornicesei/simo/gh-2344-docs-log4net-patching
Documented manual patching of  log4net CVE-2018-1285 vulnerability #2344
2023-02-11 12:41:09 +00:00
Dimitrij
4bc5dffabb Merge pull request #2346 from BlueBlock/add_feature_to_auto_reconnect_without_clicking_check
Modify "auto reconnect" to have the ability to really auto-reconnect
2023-02-11 12:35:02 +00:00
Simona Avornicesei
55a1e4a544 Documented manual patching of log4net CVE-2018-1285 vulnerability #2344 2023-02-10 21:17:26 +02:00
BlueBlock
5cb32dd75a change reconnect timer from 2s to 5s
Change and increase the reconnect timer to avoid too frequent connection attempts.
2023-02-10 04:40:55 -05:00
BlueBlock
f858c9fe48 fix checkbox state 2023-02-10 04:39:13 -05:00
BlueBlock
0ac39af404 modify "auto reconnect" to really auto-reconnect
The auto-reconnect does not automatically reconnect but instead displays a reconnect dialog. A second Options checkbox is added to allow true auto-reconnect.
2023-02-09 13:41:31 -05:00
Dimitrij
fc757b236f lib update 2023-02-04 16:42:12 +00:00
Dimitrij
a921e6e3d4 Merge pull request #2340 from BlueBlock/set_default_theme_on_first_run
set the default theme setting
2023-02-04 16:28:34 +00:00
Dimitrij
8cf0f50565 Merge pull request #2341 from BlueBlock/fix_broken_tests
Fix broken tests
2023-02-04 16:26:34 +00:00
Dimitrij
575ad7664f Merge pull request #2343 from savornicesei/simo/gh-2342-installer-in-debug-mode
Build installer in Debug mode #2342
2023-02-04 16:23:51 +00:00
Dimitrij
be17488070 Merge pull request #2339 from BlueBlock/add_missing_settings
Add 2 missing settings
2023-02-04 16:23:07 +00:00
Simona Avornicesei
f504107928 Build installer in Debug mode #2342 2023-02-04 10:10:51 +02:00
BlueBlock
0113f549c5 fix remaining unit tests 2023-02-03 17:35:09 -05:00
BlueBlock
9ff831faab fix additional tests 2023-02-03 17:05:36 -05:00
BlueBlock
a9ca243c0b fix protocol config unit tests 2023-02-03 16:53:29 -05:00
BlueBlock
553bbef45f set the default theme setting
On the initial start of a fresh install, if the options are opened the theme is then set from an empty string to the default. This causes the user to see the "must restart app" dialog when closing the options.

This only occurs on the initial run of the app. If the app is closed and run again, the theme will have been set.
2023-02-03 14:34:38 -05:00
BlueBlock
8f713f861b Add 2 missing settings
Two settings are missing in the settings but do exist in the Designer class.  These settings are used in code so they do need to exist.
ConDefaultRDGatewayUserViaAPI
ConDefaultRDGatewayExternalCredentialProvider
2023-02-03 14:12:02 -05:00
Dimitrij
324054b9d7 Merge pull request #2337 from BlueBlock/set_language_resx_to_autogen_language_class
set language.resx to auto generate the designer class
2023-02-03 10:10:20 +00:00
BlueBlock
d3fc9404ee set language.resx to auto generate the designer class
This avoids the need to manually regenerate the Language.Designer.cs file.

Note that in VS the file Language.Designer.cs will appear under Language.resx but the actual file locations have not changed.
2023-02-02 10:25:27 -05:00
Dimitrij
5bb780439b add autodate as was suggested by @savornicese 2023-02-01 12:56:43 +00:00
Dimitrij
f0e6008441 lib update 2023-02-01 12:44:37 +00:00
Dimitrij
39dd69f15e Merge pull request #2334 from BlueBlock/fix_build_order
Fix the dependencies for the Installer project build
2023-02-01 12:37:47 +00:00
Dimitrij
7b2e89df26 Merge pull request #2335 from BlueBlock/update_packages
Update project packages
2023-02-01 12:36:14 +00:00
BlueBlock
aa853f8481 Update project packages 2023-01-31 14:44:37 -05:00
BlueBlock
83bba75af6 Fix the dependencies for the Installer project build
The Installer project fails to build without also specifying the ExternalConnectors and mRemoteNG projects as dependencies.
2023-01-31 10:46:51 -05:00
Dimitrij
23889aa5b1 lib update 2023-01-08 11:57:03 +00:00
Dimitrij
5e6094fc42 lib update 2022-11-15 15:16:03 +00:00
Dimitrij
a4181cb6d6 Merge pull request #2301 from bartuszekj/splash_screen_fix
Fix for splash screen to appear on the primary screen.
2022-11-14 09:29:06 +00:00
Jerzy Bartuszek
88c49f0722 Fix for splash screen to appear on the primary screen. 2022-11-11 17:06:50 -06:00
Dimitrij
513d9e199c lib update 2022-11-09 14:34:02 +00:00
Dimitrij
e80975c56e lib update 2022-10-20 11:53:32 +01:00
Dimitrij
9051ac102c Merge pull request #2298 from SOlangsam/patch-2
Update mysql_db_setup.sql
2022-10-19 19:13:44 +01:00
SOlangsam
39a9b2e619 Update mysql_db_setup.sql
Added missing fields
Fix Issue for mysql #2292
2022-10-11 16:40:08 +02:00
Dimitrij
dbc55d248f add version color overwrite 2022-10-06 18:58:52 +01:00
Dimitrij
2b46180bfb clear inherited max-width 2022-10-06 18:41:31 +01:00
Dimitrij
5abe6c7e27 empty css 2022-10-06 18:30:31 +01:00
Dimitrij
4595ebeb9a next try to fix theme 2022-10-06 17:36:45 +01:00
Dimitrij
815c08e6d4 fix colors of sidebar 2022-10-05 21:31:30 +01:00
Dimitrij
a72ad218a0 fix for menu links color 2022-10-05 21:14:46 +01:00
Dimitrij
944ad1f769 Merge pull request #2295 from CrunchyBlue/v1.77.3-dev
*Updates hyperlink style to make links more visible to end users
2022-10-05 19:40:07 +01:00
PRINTABLE\dgagliardi
e17a68f61c *Updates hyperlink style to make links more visible to end users 2022-10-05 11:40:30 -05:00
Dimitrij
0b8196be68 Merge pull request #2285 from tecxx/develop-orig
support extraction of SSH private keys from external cred prov
2022-09-07 13:59:21 +01:00
tecxx
d9c01148b7 support extraction of SSH private keys from external credential provider (DSS)
supported formats: rsa, rsa with passphrase, putty private key
2022-09-07 14:08:29 +02:00
Dimitrij
2b3cfd992f Merge pull request #2275 from tecxx/develop-orig
external connectors improvement
2022-08-25 21:36:27 +01:00
tecxx
7e4bd7a6f3 add UI property selectors for external connectors
add support for external credential provider in remote desktop gateway
rename TSS to DSS
fix tests and property naming issues
2022-08-25 21:58:16 +02:00
Dimitrij
161e0ed637 fix for #2208 2022-08-22 10:31:54 +01:00
Dimitrij
1ee03e863c Merge branch 'v1.77.3-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.77.3-dev 2022-07-29 23:26:59 +01:00
Dimitrij
2411481d8b lib upd 2022-07-29 23:26:14 +01:00
Dimitrij
0314a627ed Merge pull request #2259 from luciusagarthy/patch-1
Update Language.cs-CZ.resx
2022-07-27 23:39:09 +01:00
Dimitrij
4d339a0b09 Merge pull request #2261 from maxshlain/v1.77.3-hide-filemenu
Implement Show/Hide file menu in view menu
2022-07-27 23:38:37 +01:00
Dimitrij
c171e7f94b Merge branch 'v1.77.3-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.77.3-dev 2022-07-17 23:14:21 +01:00
Dimitrij
eac4d966d9 remove not needed reference to old ObjectListView 2.7 2022-07-17 23:11:36 +01:00
Dimitrij
c20868c20c lib update 2022-07-17 22:42:17 +01:00
maxim-shlain
bb74d46f1f Implement Show/Hide file menu in view menu 2022-07-08 19:34:42 +03:00
luciusagarthy
152d48c583 Update Language.cs-CZ.resx
Thank you for a great app. I will contribute more. Here is some translation.
2022-07-07 16:19:48 +02:00
Dimitrij
b7a0155ba4 lib update 2022-06-25 06:39:19 +01:00
Dimitrij
0414724b21 update 2022-06-13 13:34:21 +01:00
Dimitrij
469f21db8d lib update 2022-06-13 10:35:41 +01:00
761 changed files with 101341 additions and 23778 deletions

242
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,242 @@
# GitHub Copilot Instructions for mRemoteNG
## Project Overview
mRemoteNG is an open-source, multi-protocol, tabbed remote connections manager for Windows. It's a fork of mRemote that allows users to view and manage all their remote connections (RDP, VNC, SSH, Telnet, HTTP/HTTPS, rlogin, Raw Socket, PowerShell remoting, AnyDesk) in a simple yet powerful interface.
## Technology Stack
- **Language**: C# with latest language version
- **Framework**: .NET 10.0 for Windows (net10.0-windows10.0.26100.0)
- **UI Framework**: Windows Forms with WPF support
- **Target Platforms**: x64 and ARM64
- **Testing Framework**: NUnit with NSubstitute for mocking
- **Build System**: MSBuild with .NET SDK-style projects
## Building the Project
### Prerequisites
- Visual Studio 2022 (version 17.14.12 or later)
- .NET 10.0 Desktop Runtime
- Windows 10/11 or Windows Server 2016+
### Build Commands
```powershell
# Restore NuGet packages
dotnet restore
# Build the solution
msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=x64
# Or for ARM64
msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=arm64
```
### Running Tests
The project uses NUnit for testing. Test projects are in `mRemoteNGTests/` directory.
```powershell
dotnet test mRemoteNGTests/mRemoteNGTests.csproj
```
## Code Organization
### Main Projects
- **mRemoteNG**: Main application project
- **mRemoteNGTests**: Unit and integration tests
- **mRemoteNGSpecs**: Specification tests
- **ObjectListView.NetCore**: Custom list view control
- **ExternalConnectors**: External protocol connector implementations
- **mRemoteNGDocumentation**: reStructuredText documentation
### Key Directories in mRemoteNG Project
- `App/`: Application startup and initialization
- `Connection/`: Connection models, protocols, and management
- `Config/`: Configuration and serialization (XML, CSV)
- `Container/`: Container/folder node implementations
- `Credential/`: Credential storage and repositories
- `Language/`: Localization resource files (.resx)
- `Security/`: Authentication, encryption, and password management
- `Tree/`: Connection tree UI and management
- `UI/`: User interface components and forms
- `Tools/`: Utility classes and helpers
## Code Style and Conventions
### EditorConfig Settings
The project uses EditorConfig (`.editorconfig` in `mRemoteNG/` directory) with these key rules:
- **Indentation**: 4 spaces (no tabs)
- **Line Endings**: CRLF (Windows-style)
- **Charset**: UTF-8 with BOM for C# files
- **New Lines**: Opening braces on new lines (Allman style)
- **Using Directives**: Sort System directives first
- **this. Qualifier**: Do not use `this.` prefix unless necessary
### Naming Conventions
- **Classes/Interfaces**: PascalCase (e.g., `ConnectionInfo`, `IInheritable`)
- **Methods**: PascalCase (e.g., `GetConnection`, `SaveToXml`)
- **Properties**: PascalCase (e.g., `ConnectionName`, `Port`)
- **Fields**: Use camelCase for private fields, consider `_` prefix for backing fields
- **Constants**: PascalCase
### Code Patterns
#### Inheritance System
mRemoteNG has a sophisticated property inheritance system for connection settings:
1. Properties can be inherited from parent containers/folders
2. Each inheritable property has a corresponding `Inherit<PropertyName>` boolean in `ConnectionInfoInheritance` class
3. Use `IInheritable` interface for objects that support inheritance
4. Example: `ConnectionFrameColor` property has `InheritConnectionFrameColor` boolean
#### Serialization
The project supports multiple serialization formats:
**XML Serialization** (`Config/Serializers/ConnectionSerializers/Xml/`):
- Node-based serializers (e.g., `XmlConnectionNodeSerializer28.cs`)
- Deserializers handle backward compatibility
- Attributes for properties and inheritance flags
- Always maintain backward compatibility - old files must still load
**CSV Serialization** (`Config/Serializers/ConnectionSerializers/Csv/`):
- Export connections to CSV format
- Include both value and inheritance columns
- Maintain column order consistency
#### Localization
All user-facing strings must be localized:
1. Add entries to `Language/Language.resx` (English base)
2. Use `Language.ResourceName` to access strings in code
3. Resource naming:
- Properties: `PropertyName` (e.g., `ConnectionFrameColor`)
- Descriptions: `PropertyDescription<PropertyName>`
- Enum values: `<EnumName><Value>` (e.g., `FrameColorRed`)
4. Provide clear, concise descriptions for PropertyGrid tooltips
### Common Patterns
#### Adding a New Connection Property
1. **Add enum** (if needed) in `Connection/` directory
2. **Add property** to `AbstractConnectionRecord.cs` or `ConnectionInfo.cs`
- Add `[Category("CategoryName")]` attribute for PropertyGrid grouping
- Add `[Description("Language.PropertyDescription<Name>")]` for tooltip
- Use appropriate TypeConverter if needed
3. **Add inheritance support** in `ConnectionInfoInheritance.cs`
4. **Update serializers**:
- XML: Update latest `XmlConnectionNodeSerializer*.cs` and `XmlConnectionsDeserializer.cs`
- CSV: Update `CsvConnectionsSerializerMremotengFormat.cs`
5. **Add localization** in `Language/Language.resx`
6. **Write tests** in `mRemoteNGTests/Connection/`
7. **Update documentation** in `mRemoteNGDocumentation/` if user-facing
#### UI Controls
- Prefer existing mRemoteNG patterns for UI controls
- Connection panels use `InterfaceControl` base class
- Custom painting: Override `OnPaint` or handle `Paint` event
- Follow Windows Forms best practices
## Testing Guidelines
### Test Structure
- Use NUnit's `[Test]` attribute for test methods
- Use `[SetUp]` and `[TearDown]` for test initialization/cleanup
- Use `[TestCase]` for parameterized tests
- Use NSubstitute for mocking: `Substitute.For<IInterface>()`
### Test Naming
- Use descriptive names: `MethodName_Scenario_ExpectedBehavior`
- Example: `ConnectionInfo_SetPassword_EncryptsValue`
### Test Organization
Mirror the main project structure in `mRemoteNGTests/`:
- `Connection/` for connection tests
- `Security/` for security tests
- `Config/` for configuration/serialization tests
## Important Notes and Pitfalls
### Backward Compatibility
- **Critical**: Never break loading of old connection files
- Always provide default values for new properties
- Test that files without new attributes still load correctly
### Security
- Sensitive data (passwords, credentials) must be encrypted
- Use existing security providers in `Security/` namespace
- Never log or expose credentials in plain text
### Performance
- Connection tree can contain thousands of nodes
- Optimize for large connection files
- Avoid unnecessary UI refreshes
- Use async/await for I/O operations
### Platform-Specific Code
- The project targets both x64 and ARM64
- Avoid platform-specific code unless absolutely necessary
- Test on both platforms when possible
### External Dependencies
- PuTTY for SSH/Telnet (bundled)
- Terminal Service Client for RDP
- Various protocol-specific libraries (see CREDITS.md)
## Documentation
### User Documentation
- Located in `mRemoteNGDocumentation/`
- Written in reStructuredText (.rst)
- Follows ReadTheDocs format
- Include screenshots and examples for new features
### Code Documentation
- Use XML documentation comments for public APIs
- Document complex algorithms and business logic
- Keep implementation notes in `IMPLEMENTATION_NOTES.md` for significant features
## Common Tasks
### Adding a Protocol
1. Create protocol implementation in `Connection/Protocol/`
2. Add protocol enum value to protocol types
3. Implement connection logic
4. Add UI controls if needed
5. Update documentation
### Adding a Theme
1. Add theme files to `Themes/` directory
2. Update theme manager
3. Add documentation in `mRemoteNGDocumentation/themes/`
### Updating Dependencies
- Dependencies are centrally managed in `Directory.Packages.props`
- Use `dotnet restore` after updating
- Test thoroughly after dependency updates
## Version and Release
- Current development version: 1.78.2-dev
- Version is set in `mRemoteNG.csproj`
- Builds are automated via GitHub Actions (`.github/workflows/`)
- Changelog maintained in `CHANGELOG.md`
## Getting Help
- Project website: https://mremoteng.org
- Documentation: https://mremoteng.readthedocs.io
- GitHub Issues: Report bugs and feature requests
- Community: Reddit r/mRemoteNG, Matrix chat
## Summary
When contributing to mRemoteNG:
1. Follow the existing code style (EditorConfig)
2. Maintain backward compatibility with old connection files
3. Localize all user-facing strings
4. Write tests for new functionality
5. Update documentation for user-facing changes
6. Test on both x64 and ARM64 if possible
7. Keep security and performance in mind

284
.github/workflows/Build_mR-NB.yml vendored Normal file
View File

@@ -0,0 +1,284 @@
name: Build_and_Release_mR-NB_MultiDeploy
on:
push:
branches:
- v1.78.2-dev
workflow_dispatch:
inputs:
release_flag:
description: 'Run NB release'
required: false
default: 'true'
permissions:
contents: write
jobs:
NB-Build-and-Release:
strategy:
matrix:
include:
# Framework-Dependent builds (requires .NET runtime on user machine)
- runner: windows-latest
platform: x64
arch: x64
deployment: framework-dependent
deploy_suffix: FD
- runner: windows-11-arm
platform: ARM64
arch: arm64
deployment: framework-dependent
deploy_suffix: FD
# Self-Contained builds (includes .NET runtime)
- runner: windows-latest
platform: x64
arch: x64
deployment: self-contained
deploy_suffix: SC
- runner: windows-11-arm
platform: ARM64
arch: arm64
deployment: self-contained
deploy_suffix: SC
runs-on: ${{ matrix.runner }}
# Only run if:
# - manual dispatch, OR
# - push event AND commit message contains "NB release"
if: >
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'push' && contains(github.event.head_commit.message, 'NB release'))
steps:
- name: (01) Checkout Repository
uses: actions/checkout@v6
- name: (02) Install .NET 10 SDK
uses: actions/setup-dotnet@v5
with:
dotnet-version: '10.0.x'
- name: (03) Setup MSBuild
uses: microsoft/setup-msbuild@v2
with:
vs-version: '18.0'
- name: (04) Install and run dotnet-t4 to transform T4 templates
shell: pwsh
run: |
dotnet tool install --global dotnet-t4
# Refresh PATH to include global tools
$env:PATH += ";$env:USERPROFILE\.dotnet\tools"
$ttFile = "$env:GITHUB_WORKSPACE\mRemoteNG\Properties\AssemblyInfo.tt"
# VS Enterprise 2022 assemblies
$vsPath = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\PublicAssemblies"
Write-Host "Transforming T4 template"
t4 $ttFile -P platformType=${{ matrix.platform }} -r:"$vsPath\EnvDTE.dll" -r:"$vsPath\Microsoft.VisualStudio.Interop.dll"
env:
PLATFORM: '${{ matrix.platform }}'
- name: (05) Cache NuGet Packages
uses: actions/cache@v5
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-${{ matrix.arch }}-${{ matrix.deployment }}-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: |
${{ runner.os }}-${{ matrix.arch }}-${{ matrix.deployment }}-nuget-
${{ runner.os }}-${{ matrix.arch }}-nuget-
- name: (06) Restore NuGet Packages
shell: pwsh
run: dotnet restore
- name: (07) Build Framework-Dependent Release
if: matrix.deployment == 'framework-dependent'
shell: pwsh
run: |
msbuild "$Env:GITHUB_WORKSPACE\mRemoteNG.sln" -p:Configuration="Release" -p:Platform=${{ matrix.platform }} /verbosity:minimal
- name: (08) Build Self-Contained Release
if: matrix.deployment == 'self-contained'
shell: pwsh
run: |
msbuild "$Env:GITHUB_WORKSPACE\mRemoteNG.sln" /t:Publish /p:Configuration="Release Self-Contained" -p:Platform=${{ matrix.platform }} /verbosity:minimal /p:SelfContained=true /p:PublishDir="bin\${{ matrix.platform }}\Release"
- name: (09) Release Information
id: version
shell: pwsh
run: |
$assemblyInfoPath = "${{ github.workspace }}\mRemoteNG\Properties\AssemblyInfo.cs"
$line = Get-Content $assemblyInfoPath | Where-Object { $_ -match 'AssemblyVersion\("(.+?)"\)' }
if ($line -match 'AssemblyVersion\("(?<ver>\d+\.\d+\.\d+)\.(?<build>\d+)"\)') {
$version = $matches['ver']
$build = $matches['build']
} else {
throw "Could not extract version and build number"
}
$date = Get-Date -Format "yyyyMMdd"
$zipName = "mRemoteNG-$date-v$version-NB-$build-${{ matrix.arch }}-${{ matrix.deploy_suffix }}.zip"
$tag = "$date-v$version-NB-($build)"
$message = git log -1 --pretty=%B
echo "message=$message" >> $env:GITHUB_OUTPUT
echo "zipname=$zipName" >> $env:GITHUB_OUTPUT
echo "version=$version" >> $env:GITHUB_OUTPUT
echo "build=$build" >> $env:GITHUB_OUTPUT
echo "tag=$tag" >> $env:GITHUB_OUTPUT
echo "deployment=${{ matrix.deployment }}" >> $env:GITHUB_OUTPUT
- name: (10) Extract Changelog Section
id: changelog
shell: pwsh
run: |
$changelogPath = "$env:GITHUB_WORKSPACE\CHANGELOG.md"
$lines = Get-Content $changelogPath
$startIndex = -1
for ($i = 0; $i -lt $lines.Count; $i++) {
if ($lines[$i] -match '^## \[') {
$startIndex = $i
break
}
}
if ($startIndex -eq -1) {
throw "No version header found in CHANGELOG.md"
}
$section = @()
for ($i = $startIndex + 1; $i -lt $lines.Count; $i++) {
if ($lines[$i] -match '^## ') {
break
}
$section += $lines[$i]
}
$joined = $section -join "`n"
echo "log<<EOF" >> $env:GITHUB_OUTPUT
echo $joined >> $env:GITHUB_OUTPUT
echo "EOF" >> $env:GITHUB_OUTPUT
- name: (11) Create Zip File
shell: pwsh
run: |
$sourceDir = "$Env:GITHUB_WORKSPACE\mRemoteNG\bin\${{ matrix.platform }}\Release"
Compress-Archive -Path "$sourceDir\*" -DestinationPath ${{ steps.version.outputs.zipname }}
echo "File: ${{ steps.version.outputs.zipname }}"
# Show file size
$fileSize = (Get-Item ${{ steps.version.outputs.zipname }}).Length / 1MB
echo "Size: $([math]::Round($fileSize, 2)) MB"
- name: (10) Upload artifacts for combination
uses: actions/upload-artifact@v6
with:
name: release-${{ matrix.arch }}-${{ matrix.deploy_suffix }}
path: ${{ steps.version.outputs.zipname }}
retention-days: 1
Create-Combined-Release:
needs: NB-Build-and-Release
runs-on: windows-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Download all artifacts
uses: actions/download-artifact@v7
with:
path: artifacts
- name: Extract version info
id: version
shell: pwsh
run: |
$assemblyInfoPath = "${{ github.workspace }}\mRemoteNG\Properties\AssemblyInfo.cs"
$line = Get-Content $assemblyInfoPath | Where-Object { $_ -match 'AssemblyVersion\("(.+?)"\)' }
if ($line -match 'AssemblyVersion\("(?<ver>\d+\.\d+\.\d+)\.(?<build>\d+)"\)') {
$version = $matches['ver']
$build = $matches['build']
} else {
throw "Could not extract version and build number"
}
$date = Get-Date -Format "yyyyMMdd"
$tag = "$date-v$version-NB-($build)"
$message = git log -1 --pretty=%B
echo "version=$version" >> $env:GITHUB_OUTPUT
echo "build=$build" >> $env:GITHUB_OUTPUT
echo "tag=$tag" >> $env:GITHUB_OUTPUT
echo "message=$message" >> $env:GITHUB_OUTPUT
- name: Extract Changelog
id: changelog
shell: pwsh
run: |
$changelogPath = "$env:GITHUB_WORKSPACE\CHANGELOG.md"
$lines = Get-Content $changelogPath
$startIndex = -1
for ($i = 0; $i -lt $lines.Count; $i++) {
if ($lines[$i] -match '^## \[') {
$startIndex = $i
break
}
}
if ($startIndex -eq -1) {
throw "No version header found in CHANGELOG.md"
}
$section = @()
for ($i = $startIndex + 1; $i -lt $lines.Count; $i++) {
if ($lines[$i] -match '^## ') {
break
}
$section += $lines[$i]
}
$joined = $section -join "`n"
echo "log<<EOF" >> $env:GITHUB_OUTPUT
echo $joined >> $env:GITHUB_OUTPUT
echo "EOF" >> $env:GITHUB_OUTPUT
- name: Collect all release files
shell: pwsh
run: |
Get-ChildItem -Path artifacts -Recurse -Filter "*.zip" | ForEach-Object {
Copy-Item $_.FullName -Destination .
echo "Found: $($_.Name)"
}
- name: Create combined release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.version.outputs.tag }}
name: "mRemoteNG ${{ steps.version.outputs.version }} NB ${{ steps.version.outputs.build }}"
files: '*.zip'
body: |
## mRemoteNG ${{ steps.version.outputs.version }} NB Build ${{ steps.version.outputs.build }}
### 📦 Available Downloads
**Framework-Dependent (FD)** - Requires .NET 10 Runtime installed:
- Smaller download size (~15-25 MB)
- Requires .NET 10 Desktop Runtime on user machine
- Files: `*-FD.zip`
**Self-Contained (SC)** - Portable, no installation required:
- Larger download size (~80-150 MB)
- Includes .NET 10 runtime - no installation needed
- Files: `*-SC.zip`
Both versions are available for **x64** and **ARM64** architectures.
---
### 📝 Changes in this Release
${{ steps.changelog.outputs.log }}
### 💬 Last Commit Message
${{ steps.version.outputs.message }}
draft: false
prerelease: true

126
.github/workflows/add_PR_2_chlog.yml vendored Normal file
View File

@@ -0,0 +1,126 @@
name: Update Changelog After Renovate PR Merge
on:
# 1) Auto on pushes to your repos default branch (merge commits included)
push:
branches:
- v1.78.2-dev
# 2) Manual trigger
workflow_dispatch:
inputs:
dryRun:
description: 'Run without committing changes'
required: false
default: 'true'
jobs:
update-changelog:
runs-on: ubuntu-latest
# Only proceed if…
# - manual dispatch
# - OR a push to default branch
if: |
github.event_name == 'workflow_dispatch' ||
github.event_name == 'push'
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Check for Renovate dependency update
id: check-renovate
shell: bash
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "isRenovate=true" >> $GITHUB_OUTPUT
exit 0
fi
msg="$(git log -1 --pretty=%B)"
if echo "$msg" | grep -q 'chore(deps): update dependency'; then
echo "isRenovate=true" >> $GITHUB_OUTPUT
else
echo "isRenovate=false" >> $GITHUB_OUTPUT
fi
- name: Abort if not a Renovate PR
if: steps.check-renovate.outputs.isRenovate == 'false'
run: |
echo " Last commit is not a Renovate dependency update—skipping."
- name: Parse Renovate PR info
if: steps.check-renovate.outputs.isRenovate == 'true'
id: extract
shell: pwsh
run: |
# 1) Determine dryRun
$dryRun = '${{ github.event.inputs.dryRun }}'
if (-not $dryRun) { $dryRun = 'false' }
Write-Host "🔍 dryRun = $dryRun"
# 2) Read full commit message
$fullMsg = git log -1 --pretty=%B
Write-Host "📝 Commit message:"; Write-Host $fullMsg
# 3) Extract PR number
if ($fullMsg -match 'Merge pull request #(\d+)') {
$prNumber = $matches[1]
} else {
throw "❌ Could not locate PR number in merge commit"
}
# 4) Extract dependency name & version
if ($fullMsg -match 'chore\(deps\): update dependency ([\w\.\-]+) to ([\d\.]+)') {
$depName = $matches[1]
$depVersion = $matches[2]
} else {
throw "❌ Could not parse dependency name/version"
}
# 5) Export outputs
echo "pr=$prNumber" >> $env:GITHUB_OUTPUT
echo "depName=$depName" >> $env:GITHUB_OUTPUT
echo "depVersion=$depVersion" >> $env:GITHUB_OUTPUT
echo "dryRun=$dryRun" >> $env:GITHUB_OUTPUT
- name: Update CHANGELOG.md
if: steps.check-renovate.outputs.isRenovate == 'true'
shell: pwsh
run: |
$path = "$env:GITHUB_WORKSPACE/CHANGELOG.md"
if (-not (Test-Path $path)) { throw "❌ CHANGELOG.md not found" }
$lines = Get-Content $path
$pr = '${{ steps.extract.outputs.pr }}'
$dep = '${{ steps.extract.outputs.depName }}'
$ver = '${{ steps.extract.outputs.depVersion }}'
$entry = "- #$pr: update dependency $dep to $ver"
# Find latest version header: ## [x.y.z]
$vIndex = $lines.FindIndex({ $_ -match '^## \[\d+\.\d+\.\d+\]' })
if ($vIndex -lt 0) { throw "❌ No version header found" }
# Locate or create "### Dependency update"
$depIndex = -1
for ($i = $vIndex + 1; $i -lt $lines.Count; $i++) {
if ($lines[$i] -match '^### Dependency update') { $depIndex = $i; break }
if ($lines[$i] -match '^## ') { break }
}
if ($depIndex -eq -1) {
$lines.Insert($vIndex + 1, '### Dependency update')
$depIndex = $vIndex + 1
}
# Insert the changelog entry
$lines.Insert($depIndex + 1, $entry)
Set-Content -Path $path -Value $lines
Write-Host "✅ Inserted in CHANGELOG.md:"; Write-Host " $entry"
- name: Commit & push
if: steps.check-renovate.outputs.isRenovate == 'true' && steps.extract.outputs.dryRun != 'true'
run: |
git config user.name "github-actions"
git config user.email "github-actions@github.com"
git add CHANGELOG.md
git commit -m "docs: update changelog for #${{ steps.extract.outputs.pr }}"
git push

49
.github/workflows/post_2_Reddit.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: Post to Reddit via PowerShell
on:
workflow_dispatch:
jobs:
post:
runs-on: windows-latest
steps:
- name: Authenticate and post to Reddit
shell: pwsh
env:
CLIENT_ID: ${{ secrets.REDDIT_CLIENT_ID }}
CLIENT_SECRET: ${{ secrets.REDDIT_CLIENT_SECRET }}
USERNAME: ${{ secrets.REDDIT_USERNAME }}
PASSWORD: ${{ secrets.REDDIT_PASSWORD }}
SUBREDDIT: ${{ secrets.REDDIT_SUBREDDIT }}
run: |
# Step 1: Get access token
$authHeaders = @{
Authorization = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$env:CLIENT_ID:$env:CLIENT_SECRET"))
"User-Agent" = "github-to-reddit-pwsh/0.1"
}
$authBody = @{
grant_type = "password"
username = $env:USERNAME
password = $env:PASSWORD
}
$authResponse = Invoke-RestMethod -Uri "https://www.reddit.com/api/v1/access_token" -Method Post -Headers $authHeaders -Body $authBody
$token = $authResponse.access_token
# Step 2: Post to subreddit
$postHeaders = @{
Authorization = "bearer $token"
"User-Agent" = "github-to-reddit-pwsh/0.1"
}
$postBody = @{
sr = $env:SUBREDDIT
title = "Hello from GitHub Actions (PowerShell)"
kind = "self"
text = "This post was made using PowerShell in GitHub Actions."
}
$postResponse = Invoke-RestMethod -Uri "https://oauth.reddit.com/api/submit" -Method Post -Headers $postHeaders -Body $postBody
Write-Host "Posted: $($postResponse.json.url)"

6
.gitignore vendored
View File

@@ -283,6 +283,6 @@ Installer Projects/Installer/Fragments/HelpFilesFragment.wxs
InstallerProjects/Installer/Fragments/FilesFragment.wxs
InstallerProjects/Installer/Resources/License.rtf
# gh-pages info
runlocal.bat
/_site
# mRemoteNG
**/mRemoteNG/Properties/AssemblyInfo.tt
**/mRemoteNG/Properties/AssemblyInfo.cs

11
Add-ons/README.md Normal file
View File

@@ -0,0 +1,11 @@
## Add-ons library by 3rd party
This is a list of add-ons, plugins and extentions what could be used with mRemoteNG, if you wish to add yours to this list - just drop me a line: <a href="mailto:support@mremoteng.org">support@mremoteng.org</a>
<br>
| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b>Date&nbsp;added</b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b>Author</b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | <b>Type</b> | <b>Name</b> | <b>Description</b> | <b>Repository</b> |
| :---------------------------------------------------------:|:-------------------------------------------------:| :---------: |-------------|--------------------|:-----------------:|
| 02-08-2022 | <a href="https://github.com/JustBeta"><img align="left" src="https://avatars.githubusercontent.com/u/25150896?v=4" alt="JustBeta" width="30px"/>JustBeta</a> | script | Export-MobaXterm2mRemoteNG | Conversion of MobaXterm's ini file to mRemoteNG format. | [GITHUB Repository](https://github.com/JustBeta/Export-MobaXtern2mRemoteNG/tree/main) |
<br>
For a detailed usage examples and documentation please reach out authors.

View File

@@ -2,9 +2,69 @@
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.78.2]
### Fixed
- #2939: fixed SQL injection vulnerabilities via parameterized queries
- #2940: fixed for Possible command injection via Process.Start
- #2932: fixed HTTP/HTTPS protocol to support multiple concurrent connections
- #2855: fixed missing Username field for HTTP and HTTPS protocols
- #2852: fixed XML External Entity (XXE) vulnerability in XML deserialization
- #2851: fixed path traversal vulnerability in file operations
- #2850: fixed password dialog appearing behind splash screen on startup
- #2842: fixed element placement in options window
- #2734: fixed native build for Windows-x64
- #2715: fixed Disable WinForms analyzers and suppress WFO1000 build errors for ObjectListView
- #2712: fixed VNCEvent_Disconnected send the ProtocolBase based object reference
- #2668: fixed ssh quickconnect exception
- #2611: fixed correct registry path
- #2496: fixed use pwfile instead of cleartext password for putty connections
### Added
- #2931: added vault openbao connector
- #2900: added "Report a Bug" menu item to Help menu and update bug report URL to GitHub issues
- #2865: added configurable connection tab colors to distinguish between different environments
- #2864: added color property to connections and folders with inheritance support
- #2863: added ARD (Apple Remote Desktop) protocol support for macOS connections
- #2728: added support for building mRemoteNG on Windows ARM64
- #2723: added read keyboardhook, gatewayaccesstoken and gatewaycredentialssource from RDP File
- #2690: added தமிழ் (ta) Translation update
- #2643: added registry Settings: enhancements and new settings implementation
- #2591: add Clickstudios Passwordstate API connector
- #2212: added 1Password integration
### Updated
- #2883: Improve 1Password logging
- #2854: Refactor settings dialog to be opened in dockable panel (for consistency)
- #2597: Remember the opened connection file on relaunch
- #2502: Updated Polish translation
### Dependency update
- #3bd2fe8: puttyng updated to x64 version (and signed)
## [1.77.3.1784]
### Fixed
- #2362: Fix use of sql database
- #2356: Improve speed for the display of the options page
- #2352: SSH.NET Update
- #2346: Modify "auto reconnect" to have the ability to really auto-reconnect
- #2340: Set the default theme setting
- #2339: Add 2 missing settings
- #2261: Implement Show/Hide file menu in view menu
- #2244: Save RCG and RestrictedAdmin fields correctly in connections file
- #2195: Fix crafted XML File Code Execution vulnerability
- #304: use pwfile instead of cleartext password for puttyng
### Added
- #2285: Support extraction of SSH private keys from external cred prov
- #2268: Postregsql database support
### Updated
- #2295: Updates hyperlink style to make links more visible to end users
- #2337: Set language.resx to auto generate the designer class
## [1.77.3]
### Added
- #1736: Update of SSH.NET to 2020.0.2 to allow File Transfer again
- #2138: Improve compatibility with Remote Desktop Connection Manager v2.83
- #2123: Thycotic Secret Server - Added 2FA OTP support
### Changed

View File

@@ -1,7 +1,9 @@
# Contributors
## Current mRemoteNG dev team
[Dimitrij Gorodeckij](https://github.com/Kvarkas)
## Past Contributors
[David Sparer](http://github.com/sparerd)
[Sean Kaim](http://github.com/kmscode)
[Faryan Rezagholi](http://github.com/farosch)
@@ -28,8 +30,6 @@ Tony Lambert
[MitchellBot](http://github.com/MitchellBot)
[Filippo Ferrazini](http://github.com/Filippo125)
## Past Contributors
Felix Deimel - mRemote original developer
Riley McArdle - mRemoteNG original developer

167
DEPLOYMENT_OPTIONS.md Normal file
View File

@@ -0,0 +1,167 @@
# mRemoteNG Deployment Options
This document explains the two deployment options for mRemoteNG and how to build each version.
## Deployment Types
### 1. Framework-Dependent (FD)
**File suffix: `-FD.zip`**
- **Size**: ~15-25 MB
- **Requirements**: User must have .NET 10 Desktop Runtime installed
- **Startup**: Application checks for .NET runtime and prompts user to download if missing
- **Use case**: Standard release for users comfortable installing prerequisites
### 2. Self-Contained (SC)
**File suffix: `-SC.zip`**
- **Size**: ~80-150 MB
- **Requirements**: None - includes .NET 10 runtime
- **Startup**: No runtime checks performed (runtime is bundled)
- **Use case**: Portable version for users who want zero installation/configuration
## Building Locally
### Framework-Dependent Build
```powershell
# x64
msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=x64
# ARM64
msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=ARM64
```
### Self-Contained Build
```powershell
# x64
dotnet publish mRemoteNG\mRemoteNG.csproj `
--configuration Release `
--runtime win-x64 `
--self-contained true `
-p:Platform=x64 `
-p:PublishSingleFile=false `
-p:PublishReadyToRun=true `
-p:DefineConstants="SELF_CONTAINED"
# ARM64
dotnet publish mRemoteNG\mRemoteNG.csproj `
--configuration Release `
--runtime win-arm64 `
--self-contained true `
-p:Platform=ARM64 `
-p:PublishSingleFile=false `
-p:PublishReadyToRun=true `
-p:DefineConstants="SELF_CONTAINED"
```
## GitHub Actions Workflow
The new workflow file `Build_and_Release_mR-NB-MultiDeploy.yml` automatically builds all four variants:
1. **x64 Framework-Dependent** - `mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-x64-FD.zip`
2. **x64 Self-Contained** - `mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-x64-SC.zip`
3. **ARM64 Framework-Dependent** - `mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-arm64-FD.zip`
4. **ARM64 Self-Contained** - `mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-arm64-SC.zip`
### Workflow Triggers
The workflow runs when:
- You push to `v1.78.2-dev` branch with commit message containing "NB release"
- You manually trigger via workflow_dispatch
### Release Output
All four zip files are uploaded to a single GitHub Release with clear descriptions:
- Framework-Dependent versions are marked as requiring .NET 10 Runtime
- Self-Contained versions are marked as portable/no installation needed
## Code Changes
### ProgramRoot.cs
The `MainAsync` method now uses conditional compilation:
```csharp
#if !SELF_CONTAINED
// Runtime check code only included in Framework-Dependent builds
// Checks for .NET Runtime and Visual C++ Redistributable
#endif
```
When building with `-p:DefineConstants="SELF_CONTAINED"`, the runtime checks are completely excluded from the compiled binary.
## Recommendations
### For Users
**Choose Framework-Dependent (FD) if:**
- You don't mind installing .NET 10 Desktop Runtime once
- You want smaller download size
- You're using multiple .NET applications (runtime is shared)
**Choose Self-Contained (SC) if:**
- You want zero installation/setup
- You need portable deployment (USB drive, restricted environments)
- You don't want to deal with prerequisites
### For Distribution
Consider offering both options:
- Make Framework-Dependent the **default/recommended** option (smaller, faster updates)
- Offer Self-Contained as **portable alternative** for special use cases
## Technical Details
### Compilation Symbols
- Framework-Dependent builds: No special symbols
- Self-Contained builds: `SELF_CONTAINED` symbol defined
### Runtime Identifiers
- x64: `win-x64`
- ARM64: `win-arm64`
### Publish Options
Self-contained builds use these optimizations:
- `PublishReadyToRun=true` - AOT compilation for faster startup
- `IncludeNativeLibrariesForSelfExtract=true` - Bundle native dependencies
- `PublishSingleFile=false` - Keep files separate for better compatibility with mRemoteNG's plugin system
## File Size Comparison
Typical build sizes:
| Version | Framework-Dependent | Self-Contained |
|---------|---------------------|----------------|
| x64 | ~18 MB | ~95 MB |
| ARM64 | ~18 MB | ~95 MB |
*Note: Self-contained includes entire .NET 10 runtime (~80 MB overhead)*
## Testing
### Framework-Dependent Build
1. Uninstall .NET 10 Runtime (if installed)
2. Run mRemoteNG.exe
3. Should prompt to download .NET 10 Runtime
4. Install runtime and verify app launches
### Self-Contained Build
1. Uninstall .NET 10 Runtime (if installed)
2. Run mRemoteNG.exe
3. Should launch immediately without runtime check
4. Verify full functionality
## Migration from Old Workflow
The original workflow `Build_and_Release_mR-NB.yml` is preserved. To migrate:
1. Rename or remove old workflow: `Build_and_Release_mR-NB.yml`
2. Rename new workflow: `Build_and_Release_mR-NB-MultiDeploy.yml``Build_and_Release_mR-NB.yml`
3. Commit and push with "NB release" in message

105
Directory.Packages.props Normal file
View File

@@ -0,0 +1,105 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
<NoWarn>$(NoWarn);NU1507;NU1701</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="AWSSDK.Core" Version="4.0.3.14" />
<PackageVersion Include="AWSSDK.EC2" Version="4.0.75" />
<PackageVersion Include="BouncyCastle.Cryptography" Version="2.6.2" />
<PackageVersion Include="Castle.Core" Version="5.2.1" />
<PackageVersion Include="ConsoleControl" Version="1.3.0" />
<PackageVersion Include="ConsoleControlAPI" Version="1.3.0" />
<PackageVersion Include="coverlet.collector" Version="8.0.0" />
<PackageVersion Include="Cucumber.Messages" Version="32.0.1" />
<PackageVersion Include="DockPanelSuite" Version="3.1.1" />
<PackageVersion Include="DockPanelSuite.ThemeVS2015" Version="3.1.1" />
<PackageVersion Include="envdte" Version="17.14.40260" />
<PackageVersion Include="Gherkin" Version="38.0.0" />
<PackageVersion Include="Google.Protobuf" Version="3.33.5" />
<PackageVersion Include="LiteDB" Version="5.0.21" />
<PackageVersion Include="log4net" Version="3.2.0" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.1.4" />
<PackageVersion Include="Microsoft.Data.SqlClient.SNI" Version="6.0.2" />
<PackageVersion Include="Microsoft.Data.SqlClient.SNI.runtime" Version="6.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.3" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageVersion Include="Microsoft.NETCore.Platforms" Version="7.0.4" />
<PackageVersion Include="Microsoft.NETCore.Targets" Version="5.0.0" />
<PackageVersion Include="Microsoft.VisualStudio.TextTemplating.VSHost" Version="17.14.40265" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3800.47" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
<PackageVersion Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.5" />
<PackageVersion Include="MySql.Data" Version="9.6.0" />
<PackageVersion Include="NETStandard.Library" Version="2.0.3" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="Newtonsoft.Json.Schema" Version="4.0.1" />
<PackageVersion Include="NSubstitute" Version="5.3.0" />
<PackageVersion Include="NUnit" Version="4.4.0" />
<PackageVersion Include="NUnit.Console" Version="3.22.0" />
<PackageVersion Include="NUnit.ConsoleRunner" Version="3.22.0" />
<PackageVersion Include="NUnit.Extension.TeamCityEventListener" Version="1.0.10" />
<PackageVersion Include="NUnit.Runners" Version="3.12.0" />
<PackageVersion Include="NUnit3TestAdapter" Version="6.1.0" />
<PackageVersion Include="OpenCover" Version="4.7.1221" />
<PackageVersion Include="Renci.SshNet.Async" Version="1.4.0" />
<PackageVersion Include="ReportGenerator" Version="5.5.1" />
<PackageVersion Include="runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
<PackageVersion Include="runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
<PackageVersion Include="runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
<PackageVersion Include="runtime.native.System" Version="4.3.1" />
<PackageVersion Include="runtime.native.System.IO.Compression" Version="4.3.2" />
<PackageVersion Include="runtime.native.System.Net.Http" Version="4.3.1" />
<PackageVersion Include="runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
<PackageVersion Include="runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
<PackageVersion Include="runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
<PackageVersion Include="runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
<PackageVersion Include="SSH.NET" Version="2025.1.0" />
<PackageVersion Include="System.Buffers" Version="4.6.1" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="10.0.3" />
<PackageVersion Include="System.Collections.Immutable" Version="10.0.3" />
<PackageVersion Include="System.Console" Version="4.3.1" />
<PackageVersion Include="System.Data.Common" Version="4.3.0" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.3" />
<PackageVersion Include="System.Drawing.Common" Version="10.0.3" />
<PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.3" />
<PackageVersion Include="System.DirectoryServices" Version="10.0.3" />
<PackageVersion Include="System.Dynamic.Runtime" Version="4.3.0" />
<PackageVersion Include="System.IO.Pipelines" Version="10.0.3" />
<PackageVersion Include="System.Formats.Asn1" Version="10.0.3" />
<PackageVersion Include="System.Management" Version="10.0.3" />
<PackageVersion Include="System.Memory" Version="4.6.3" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Net.Primitives" Version="4.3.1" />
<PackageVersion Include="System.Net.Sockets" Version="4.3.0" />
<PackageVersion Include="System.Reflection.Emit" Version="4.7.0" />
<PackageVersion Include="System.Reflection.Emit.ILGeneration" Version="4.7.0" />
<PackageVersion Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
<PackageVersion Include="System.Reflection.Metadata" Version="10.0.3" />
<PackageVersion Include="System.Reflection.TypeExtensions" Version="4.7.0" />
<PackageVersion Include="System.Resources.ResourceManager" Version="4.3.0" />
<PackageVersion Include="System.Runtime" Version="4.3.1" />
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.2" />
<PackageVersion Include="System.Runtime.Extensions" Version="4.3.1" />
<PackageVersion Include="System.Security.AccessControl" Version="6.0.1" />
<PackageVersion Include="System.Security.Cryptography.Algorithms" Version="4.3.1" />
<PackageVersion Include="System.Security.Cryptography.Cng" Version="5.0.0" />
<PackageVersion Include="System.Security.Cryptography.OpenSsl" Version="5.0.0" />
<PackageVersion Include="System.Security.Cryptography.ProtectedData" Version="10.0.3" />
<PackageVersion Include="System.Security.Cryptography.X509Certificates" Version="4.3.2" />
<PackageVersion Include="System.Security.Permissions" Version="10.0.3" />
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="10.0.3" />
<PackageVersion Include="System.Text.Json" Version="10.0.3" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageVersion Include="System.ValueTuple" Version="4.6.1" />
<PackageVersion Include="System.Windows.Extensions" Version="10.0.3" />
<PackageVersion Include="System.Xml.ReaderWriter" Version="4.3.1" />
<PackageVersion Include="VaultSharp" Version="1.17.5.1" />
<PackageVersion Include="VncSharpCore" Version="1.2.1" />
<PackageVersion Include="ZstdSharp.Port" Version="0.8.7" />
</ItemGroup>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,241 @@
namespace ExternalConnectors.CPS
{
partial class CPSConnectionForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(CPSConnectionForm));
tbServerURL = new TextBox();
label3 = new Label();
tbAPIKey = new TextBox();
btnOK = new Button();
btnCancel = new Button();
tableLayoutPanel1 = new TableLayoutPanel();
label1 = new Label();
label6 = new Label();
tbOTP = new TextBox();
cbUseSSO = new CheckBox();
tableLayoutPanel2 = new TableLayoutPanel();
label4 = new Label();
tableLayoutPanel1.SuspendLayout();
tableLayoutPanel2.SuspendLayout();
SuspendLayout();
//
// tbServerURL
//
tbServerURL.Dock = DockStyle.Fill;
tbServerURL.Location = new Point(298, 5);
tbServerURL.Margin = new Padding(5);
tbServerURL.Name = "tbServerURL";
tbServerURL.Size = new Size(611, 27);
tbServerURL.TabIndex = 0;
//
// label3
//
label3.AutoSize = true;
label3.Dock = DockStyle.Fill;
label3.Location = new Point(5, 84);
label3.Margin = new Padding(5, 0, 5, 0);
label3.Name = "label3";
label3.Size = new Size(283, 42);
label3.TabIndex = 5;
label3.Text = "API Key";
label3.TextAlign = ContentAlignment.MiddleLeft;
//
// tbAPIKey
//
tbAPIKey.Dock = DockStyle.Fill;
tbAPIKey.Location = new Point(298, 89);
tbAPIKey.Margin = new Padding(5);
tbAPIKey.Name = "tbAPIKey";
tbAPIKey.Size = new Size(611, 27);
tbAPIKey.TabIndex = 4;
tbAPIKey.UseSystemPasswordChar = true;
//
// btnOK
//
btnOK.Anchor = AnchorStyles.Right;
btnOK.DialogResult = DialogResult.OK;
btnOK.Location = new Point(337, 16);
btnOK.Margin = new Padding(5);
btnOK.Name = "btnOK";
btnOK.Size = new Size(101, 35);
btnOK.TabIndex = 6;
btnOK.Text = "OK";
btnOK.UseVisualStyleBackColor = true;
//
// btnCancel
//
btnCancel.Anchor = AnchorStyles.Left;
btnCancel.DialogResult = DialogResult.Cancel;
btnCancel.Location = new Point(474, 16);
btnCancel.Margin = new Padding(5);
btnCancel.Name = "btnCancel";
btnCancel.Size = new Size(101, 35);
btnCancel.TabIndex = 11;
btnCancel.Text = "Cancel";
btnCancel.UseVisualStyleBackColor = true;
//
// tableLayoutPanel1
//
tableLayoutPanel1.ColumnCount = 2;
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 32.06997F));
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 67.93003F));
tableLayoutPanel1.Controls.Add(label1, 0, 0);
tableLayoutPanel1.Controls.Add(label3, 0, 2);
tableLayoutPanel1.Controls.Add(tbServerURL, 1, 0);
tableLayoutPanel1.Controls.Add(tbAPIKey, 1, 2);
tableLayoutPanel1.Controls.Add(label6, 0, 3);
tableLayoutPanel1.Controls.Add(tbOTP, 1, 3);
tableLayoutPanel1.Controls.Add(cbUseSSO, 0, 1);
tableLayoutPanel1.Dock = DockStyle.Top;
tableLayoutPanel1.Location = new Point(0, 0);
tableLayoutPanel1.Margin = new Padding(5);
tableLayoutPanel1.Name = "tableLayoutPanel1";
tableLayoutPanel1.RowCount = 5;
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
tableLayoutPanel1.Size = new Size(914, 212);
tableLayoutPanel1.TabIndex = 12;
//
// label1
//
label1.AutoSize = true;
label1.Dock = DockStyle.Fill;
label1.Location = new Point(5, 0);
label1.Margin = new Padding(5, 0, 5, 0);
label1.Name = "label1";
label1.Size = new Size(283, 42);
label1.TabIndex = 2;
label1.Text = "Passwordstate URL";
label1.TextAlign = ContentAlignment.MiddleLeft;
//
// label6
//
label6.AutoSize = true;
label6.Dock = DockStyle.Fill;
label6.Location = new Point(3, 126);
label6.Name = "label6";
label6.Size = new Size(287, 42);
label6.TabIndex = 15;
label6.Text = "2FA OTP (Optional)";
//
// tbOTP
//
tbOTP.Dock = DockStyle.Fill;
tbOTP.Location = new Point(298, 131);
tbOTP.Margin = new Padding(5);
tbOTP.Name = "tbOTP";
tbOTP.Size = new Size(611, 27);
tbOTP.TabIndex = 5;
//
// cbUseSSO
//
cbUseSSO.Anchor = AnchorStyles.Left;
cbUseSSO.AutoSize = true;
cbUseSSO.Location = new Point(5, 53);
cbUseSSO.Margin = new Padding(5, 5, 5, 0);
cbUseSSO.Name = "cbUseSSO";
cbUseSSO.Size = new Size(157, 24);
cbUseSSO.TabIndex = 14;
cbUseSSO.Text = "Use SSO / WinAuth";
cbUseSSO.UseVisualStyleBackColor = true;
cbUseSSO.CheckedChanged += cbUseSSO_CheckedChanged;
//
// tableLayoutPanel2
//
tableLayoutPanel2.ColumnCount = 5;
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 106F));
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 26F));
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 107F));
tableLayoutPanel2.Controls.Add(btnOK, 1, 0);
tableLayoutPanel2.Controls.Add(btnCancel, 3, 0);
tableLayoutPanel2.Dock = DockStyle.Bottom;
tableLayoutPanel2.Location = new Point(0, 300);
tableLayoutPanel2.Margin = new Padding(5);
tableLayoutPanel2.Name = "tableLayoutPanel2";
tableLayoutPanel2.RowCount = 1;
tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
tableLayoutPanel2.Size = new Size(914, 67);
tableLayoutPanel2.TabIndex = 13;
//
// label4
//
label4.AutoSize = true;
label4.Dock = DockStyle.Fill;
label4.Location = new Point(0, 212);
label4.Margin = new Padding(5, 0, 5, 0);
label4.Name = "label4";
label4.Size = new Size(345, 20);
label4.TabIndex = 14;
label4.Text = "URL is the base URL, like https://pass.domain.local/";
label4.TextAlign = ContentAlignment.MiddleLeft;
//
// CPSConnectionForm
//
AcceptButton = btnOK;
AutoScaleDimensions = new SizeF(8F, 20F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(914, 367);
Controls.Add(label4);
Controls.Add(tableLayoutPanel2);
Controls.Add(tableLayoutPanel1);
Icon = (Icon)resources.GetObject("$this.Icon");
Margin = new Padding(5);
Name = "CPSConnectionForm";
Text = "Passwordstate API Login Data";
Activated += CPSConnectionForm_Activated;
tableLayoutPanel1.ResumeLayout(false);
tableLayoutPanel1.PerformLayout();
tableLayoutPanel2.ResumeLayout(false);
ResumeLayout(false);
PerformLayout();
}
#endregion
private System.Windows.Forms.Label label3;
public System.Windows.Forms.TextBox tbServerURL;
//public System.Windows.Forms.TextBox tbUsername;
public System.Windows.Forms.TextBox tbAPIKey;
private System.Windows.Forms.Button btnOK;
private System.Windows.Forms.Button btnCancel;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2;
public System.Windows.Forms.CheckBox cbUseSSO;
private System.Windows.Forms.Label label4;
private Label label6;
public TextBox tbOTP;
}
}

View File

@@ -0,0 +1,41 @@
namespace ExternalConnectors.CPS
{
public partial class CPSConnectionForm : Form
{
public CPSConnectionForm()
{
InitializeComponent();
}
private void CPSConnectionForm_Activated(object sender, EventArgs e)
{
SetVisibility();
if (cbUseSSO.Checked)
btnOK.Focus();
else
{
if (tbAPIKey.Text.Length == 0)
tbAPIKey.Focus();
else
tbOTP.Focus();
}
tbAPIKey.Focus();
if (!string.IsNullOrEmpty(tbAPIKey.Text) || cbUseSSO.Checked == true)
tbOTP.Focus();
}
private void cbUseSSO_CheckedChanged(object sender, EventArgs e)
{
SetVisibility();
}
private void SetVisibility()
{
bool ch = cbUseSSO.Checked;
tbAPIKey.Enabled = !ch;
//tbUsername.Enabled = !ch;
}
}
}

View File

@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAEAEBAAAAEACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAA
AAAtLDAA+PDaAP///wD79ugA/PrzAPf39wBGRUkA7NacAM2WAAA3z6kA+Pz/AIKBgwD+//4A4sNtAHXe
xAD8+vIAjuTOANOjHgDV6/4AJZf3APn5+QDw37IAIB8jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAgICAgICCQ4CAgIWAgIUAgICAgICAgkJAgICFhYWAAIIDwICAgICCQwCAhYWFgYCCAgIBwIC
AgkJAgsWFhYWBQICCAgIAwIJCQIWFhYWAgICAgINCAgCAgICFhYCAgICAgICAgIRAgICAgICAgICAgIC
AgICEwICAgICEhMTExMCAgITExMCAgICAgITExMTAhMTExMCAgICAgICAgICAhMTEwICAgIICAIJCQIC
AgIKAgICAgIICAQCAgkJAgICAgICAgICCAgCAgICCQkCAgICAgICFQgBAgICAgwJAgICAgICAggIAgIC
AgICEAkCAgICAgIIAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
</value>
</data>
</root>

View File

@@ -0,0 +1,295 @@
using Microsoft.Win32;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using System.Security.Cryptography;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace ExternalConnectors.CPS;
public class PasswordstateInterface
{
private static class CPSConnectionData
{
public static string ssUsername = "";
public static string ssPassword = "";
public static string ssUrl = "";
public static string ssOTP = "";
public static DateTime ssOTPTimeStampExpiration;
public static bool ssSSO = false;
public static bool initdone = false;
//token
//public static string ssTokenBearer = "";
//public static DateTime ssTokenExpiresOn = DateTime.UtcNow;
//public static string ssTokenRefresh = "";
public static void Init()
{
// 2024-05-04 passwordstate currently does not support auth tokens, so we need to re-enter otp codes frequently
if (!string.IsNullOrEmpty(ssOTP) && DateTime.Now > ssOTPTimeStampExpiration)
{
ssOTP = "";
initdone = false;
}
if (initdone == true)
return;
RegistryKey key = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\mRemoteCPSInterface");
try
{
// display gui and ask for data
CPSConnectionForm f = new CPSConnectionForm();
//string? un = key.GetValue("Username") as string;
//f.tbUsername.Text = un ?? "";
f.tbAPIKey.Text = CPSConnectionData.ssPassword; // in OTP refresh cases, this value might already be filled
string? url = key.GetValue("URL") as string;
if (url == null || !url.Contains("://"))
url = "https://cred.domain.local/SecretServer";
f.tbServerURL.Text = url;
var b = key.GetValue("SSO");
if (b == null || (string)b != "True")
ssSSO = false;
else
ssSSO = true;
f.cbUseSSO.Checked = ssSSO;
// show dialog
while (true)
{
_ = f.ShowDialog();
if (f.DialogResult != DialogResult.OK)
return;
// store values to memory
//ssUsername = f.tbUsername.Text;
ssPassword = f.tbAPIKey.Text;
ssUrl = f.tbServerURL.Text;
ssSSO = f.cbUseSSO.Checked;
ssOTP = f.tbOTP.Text;
ssOTPTimeStampExpiration = DateTime.Now.AddSeconds(30);
// check connection first
try
{
if (TestCredentials() == true)
{
initdone = true;
break;
}
}
catch (Exception)
{
MessageBox.Show("Test Credentials failed - please check your credentials");
}
}
// write values to registry
//key.SetValue("Username", ssUsername);
key.SetValue("URL", ssUrl);
key.SetValue("SSO", ssSSO);
}
catch (Exception)
{
throw;
}
finally
{
key.Close();
}
}
}
private static bool TestCredentials()
{
return ConnectionTest();
}
private static bool ConnectionTest()
{
if (CPSConnectionData.ssSSO)
{
string url = $"{CPSConnectionData.ssUrl}/winapi/passwordlists/";
using HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("User-Agent", "mRemote");
client.DefaultRequestHeaders.Add("OTP", CPSConnectionData.ssOTP);
var json = client.GetStringAsync(url).Result;
JsonNode? data = JsonSerializer.Deserialize<JsonNode>(json);
if (data == null)
return false;
return true;
}
else
{
string url = $"{CPSConnectionData.ssUrl}/api/passwordlists/";
using HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("User-Agent", "mRemote");
client.DefaultRequestHeaders.Add("APIKey", CPSConnectionData.ssPassword);
client.DefaultRequestHeaders.Add("OTP", CPSConnectionData.ssOTP);
var json = client.GetStringAsync(url).Result;
JsonNode? data = JsonSerializer.Deserialize<JsonNode>(json);
if (data == null)
return false;
return true;
}
}
private static JsonNode? FetchDataWinAuth(int secretID)
{
string url = $"{CPSConnectionData.ssUrl}/winapi/passwords/{secretID}";
using HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("User-Agent", "mRemote");
client.DefaultRequestHeaders.Add("OTP", CPSConnectionData.ssOTP);
var json = client.GetStringAsync(url).Result;
JsonNode? data = JsonSerializer.Deserialize<JsonNode>(json);
if (data == null)
return null;
JsonNode? element = data[0];
return element;
}
private static JsonNode? FetchDataAPIKeyAuth(int secretID)
{
string url = $"{CPSConnectionData.ssUrl}/api/passwords/{secretID}";
using HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("User-Agent", "mRemote");
client.DefaultRequestHeaders.Add("APIKey", CPSConnectionData.ssPassword);
client.DefaultRequestHeaders.Add("OTP", CPSConnectionData.ssOTP);
var json = client.GetStringAsync(url).Result;
JsonNode? data = JsonSerializer.Deserialize<JsonNode>(json);
if (data == null)
return null;
JsonNode? element = data[0];
return element;
}
private static void FetchSecret(int secretID, out string secretUsername, out string secretPassword, out string secretDomain, out string privatekey)
{
// clear return variables
secretDomain = "";
secretUsername = "";
secretPassword = "";
privatekey = "";
string privatekeypassphrase = "";
JsonNode? element = null;
if (CPSConnectionData.ssSSO)
element = FetchDataWinAuth(secretID);
else
element = FetchDataAPIKeyAuth(secretID);
if (element == null)
return;
var dom = element["Domain"];
if (dom != null) secretDomain = dom.ToString();
var user = element["UserName"];
if (user != null) secretUsername = user.ToString();
var pw = element["Password"];
if (pw != null) secretPassword = pw.ToString();
var privkey = element["GenericField1"];
if (privkey != null) privatekey = privkey.ToString();
var phrase = element["GenericField3"];
if (phrase != null) privatekeypassphrase = phrase.ToString();
// need to decode the private key?
if (!string.IsNullOrEmpty(privatekeypassphrase))
{
try
{
var key = DecodePrivateKey(privatekey, privatekeypassphrase);
privatekey = key;
}
catch(Exception)
{
}
}
// conversion to putty format necessary?
if (!string.IsNullOrEmpty(privatekey) && !privatekey.StartsWith("PuTTY-User-Key-File-2"))
{
try
{
RSACryptoServiceProvider key = ImportPrivateKey(privatekey);
privatekey = PuttyKeyFileGenerator.ToPuttyPrivateKey(key);
}
catch (Exception)
{
}
}
}
#region PUTTY KEY HANDLING
// decode rsa private key with encryption password
private static string DecodePrivateKey(string encryptedPrivateKey, string password)
{
TextReader textReader = new StringReader(encryptedPrivateKey);
PemReader pemReader = new PemReader(textReader, new PasswordFinder(password));
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
TextWriter textWriter = new StringWriter();
var pemWriter = new PemWriter(textWriter);
pemWriter.WriteObject(keyPair.Private);
pemWriter.Writer.Flush();
return ""+textWriter.ToString();
}
private class PasswordFinder(string password) : IPasswordFinder
{
private string password = password;
public char[] GetPassword()
{
return password.ToCharArray();
}
}
// read private key pem string to rsacryptoserviceprovider
public static RSACryptoServiceProvider ImportPrivateKey(string pem)
{
PemReader pr = new PemReader(new StringReader(pem));
AsymmetricCipherKeyPair KeyPair = (AsymmetricCipherKeyPair)pr.ReadObject();
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)KeyPair.Private);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(rsaParams);
return rsa;
}
#endregion
// input: must be the secret id to fetch
public static void FetchSecretFromServer(string secretID, out string username, out string password, out string domain, out string privatekey)
{
// get secret id
int sid = Int32.Parse(secretID);
// init connection credentials, display popup if necessary
CPSConnectionData.Init();
// get the secret
FetchSecret(sid, out username, out password, out domain, out privatekey);
}
}

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -1,4 +1,4 @@
namespace ExternalConnectors.TSS
namespace ExternalConnectors.DSS
{
partial class SSConnectionForm
{
@@ -51,29 +51,29 @@
// tbSSURL
//
this.tbSSURL.Dock = System.Windows.Forms.DockStyle.Fill;
this.tbSSURL.Location = new System.Drawing.Point(260, 4);
this.tbSSURL.Margin = new System.Windows.Forms.Padding(4);
this.tbSSURL.Location = new System.Drawing.Point(298, 5);
this.tbSSURL.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
this.tbSSURL.Name = "tbSSURL";
this.tbSSURL.Size = new System.Drawing.Size(536, 23);
this.tbSSURL.Size = new System.Drawing.Size(611, 27);
this.tbSSURL.TabIndex = 0;
//
// tbUsername
//
this.tbUsername.Dock = System.Windows.Forms.DockStyle.Fill;
this.tbUsername.Location = new System.Drawing.Point(260, 35);
this.tbUsername.Margin = new System.Windows.Forms.Padding(4);
this.tbUsername.Location = new System.Drawing.Point(298, 47);
this.tbUsername.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
this.tbUsername.Name = "tbUsername";
this.tbUsername.Size = new System.Drawing.Size(536, 23);
this.tbUsername.Size = new System.Drawing.Size(611, 27);
this.tbUsername.TabIndex = 2;
//
// label3
//
this.label3.AutoSize = true;
this.label3.Dock = System.Windows.Forms.DockStyle.Fill;
this.label3.Location = new System.Drawing.Point(4, 62);
this.label3.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label3.Location = new System.Drawing.Point(5, 84);
this.label3.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(248, 31);
this.label3.Size = new System.Drawing.Size(283, 42);
this.label3.TabIndex = 5;
this.label3.Text = "Password";
this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
@@ -81,10 +81,10 @@
// tbPassword
//
this.tbPassword.Dock = System.Windows.Forms.DockStyle.Fill;
this.tbPassword.Location = new System.Drawing.Point(260, 66);
this.tbPassword.Margin = new System.Windows.Forms.Padding(4);
this.tbPassword.Location = new System.Drawing.Point(298, 89);
this.tbPassword.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
this.tbPassword.Name = "tbPassword";
this.tbPassword.Size = new System.Drawing.Size(536, 23);
this.tbPassword.Size = new System.Drawing.Size(611, 27);
this.tbPassword.TabIndex = 4;
this.tbPassword.UseSystemPasswordChar = true;
//
@@ -92,10 +92,10 @@
//
this.btnOK.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.btnOK.DialogResult = System.Windows.Forms.DialogResult.OK;
this.btnOK.Location = new System.Drawing.Point(296, 12);
this.btnOK.Margin = new System.Windows.Forms.Padding(4);
this.btnOK.Location = new System.Drawing.Point(337, 16);
this.btnOK.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
this.btnOK.Name = "btnOK";
this.btnOK.Size = new System.Drawing.Size(88, 26);
this.btnOK.Size = new System.Drawing.Size(101, 35);
this.btnOK.TabIndex = 6;
this.btnOK.Text = "OK";
this.btnOK.UseVisualStyleBackColor = true;
@@ -104,10 +104,10 @@
//
this.btnCancel.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.btnCancel.Location = new System.Drawing.Point(415, 12);
this.btnCancel.Margin = new System.Windows.Forms.Padding(4);
this.btnCancel.Location = new System.Drawing.Point(474, 16);
this.btnCancel.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(88, 26);
this.btnCancel.Size = new System.Drawing.Size(101, 35);
this.btnCancel.TabIndex = 11;
this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true;
@@ -129,7 +129,7 @@
this.tableLayoutPanel1.Controls.Add(this.tbOTP, 1, 3);
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Top;
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
this.tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(4);
this.tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 5;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F));
@@ -137,17 +137,17 @@
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(800, 159);
this.tableLayoutPanel1.Size = new System.Drawing.Size(914, 212);
this.tableLayoutPanel1.TabIndex = 12;
//
// label5
//
this.label5.AutoSize = true;
this.label5.Dock = System.Windows.Forms.DockStyle.Fill;
this.label5.Location = new System.Drawing.Point(260, 124);
this.label5.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label5.Location = new System.Drawing.Point(298, 168);
this.label5.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.label5.Name = "label5";
this.label5.Size = new System.Drawing.Size(536, 35);
this.label5.Size = new System.Drawing.Size(611, 44);
this.label5.TabIndex = 15;
this.label5.Text = "For SSO to work, additional IIS configuration is required!";
this.label5.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
@@ -156,10 +156,10 @@
//
this.label1.AutoSize = true;
this.label1.Dock = System.Windows.Forms.DockStyle.Fill;
this.label1.Location = new System.Drawing.Point(4, 0);
this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label1.Location = new System.Drawing.Point(5, 0);
this.label1.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(248, 31);
this.label1.Size = new System.Drawing.Size(283, 42);
this.label1.TabIndex = 2;
this.label1.Text = "Secret Server URL";
this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
@@ -168,10 +168,10 @@
//
this.label2.AutoSize = true;
this.label2.Dock = System.Windows.Forms.DockStyle.Fill;
this.label2.Location = new System.Drawing.Point(4, 31);
this.label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label2.Location = new System.Drawing.Point(5, 42);
this.label2.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(248, 31);
this.label2.Size = new System.Drawing.Size(283, 42);
this.label2.TabIndex = 4;
this.label2.Text = "DOMAIN\\Username";
this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
@@ -179,10 +179,10 @@
// cbUseSSO
//
this.cbUseSSO.AutoSize = true;
this.cbUseSSO.Location = new System.Drawing.Point(4, 128);
this.cbUseSSO.Margin = new System.Windows.Forms.Padding(4, 4, 4, 0);
this.cbUseSSO.Location = new System.Drawing.Point(5, 173);
this.cbUseSSO.Margin = new System.Windows.Forms.Padding(5, 5, 5, 0);
this.cbUseSSO.Name = "cbUseSSO";
this.cbUseSSO.Size = new System.Drawing.Size(69, 19);
this.cbUseSSO.Size = new System.Drawing.Size(86, 24);
this.cbUseSSO.TabIndex = 14;
this.cbUseSSO.Text = "Use SSO";
this.cbUseSSO.UseVisualStyleBackColor = true;
@@ -192,47 +192,48 @@
//
this.label6.AutoSize = true;
this.label6.Dock = System.Windows.Forms.DockStyle.Fill;
this.label6.Location = new System.Drawing.Point(3, 93);
this.label6.Location = new System.Drawing.Point(3, 126);
this.label6.Name = "label6";
this.label6.Size = new System.Drawing.Size(250, 31);
this.label6.Size = new System.Drawing.Size(287, 42);
this.label6.TabIndex = 15;
this.label6.Text = "2FA OTP (Optional)";
//
// tbOTP
//
this.tbOTP.Dock = System.Windows.Forms.DockStyle.Fill;
this.tbOTP.Location = new System.Drawing.Point(259, 96);
this.tbOTP.Location = new System.Drawing.Point(296, 130);
this.tbOTP.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.tbOTP.Name = "tbOTP";
this.tbOTP.Size = new System.Drawing.Size(538, 23);
this.tbOTP.Size = new System.Drawing.Size(615, 27);
this.tbOTP.TabIndex = 5;
//
// tableLayoutPanel2
//
this.tableLayoutPanel2.ColumnCount = 5;
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 93F));
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 106F));
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 23F));
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 26F));
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 93F));
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 107F));
this.tableLayoutPanel2.Controls.Add(this.btnOK, 1, 0);
this.tableLayoutPanel2.Controls.Add(this.btnCancel, 3, 0);
this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Bottom;
this.tableLayoutPanel2.Location = new System.Drawing.Point(0, 225);
this.tableLayoutPanel2.Margin = new System.Windows.Forms.Padding(4);
this.tableLayoutPanel2.Location = new System.Drawing.Point(0, 300);
this.tableLayoutPanel2.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
this.tableLayoutPanel2.Name = "tableLayoutPanel2";
this.tableLayoutPanel2.RowCount = 1;
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel2.Size = new System.Drawing.Size(800, 50);
this.tableLayoutPanel2.Size = new System.Drawing.Size(914, 67);
this.tableLayoutPanel2.TabIndex = 13;
//
// label4
//
this.label4.AutoSize = true;
this.label4.Dock = System.Windows.Forms.DockStyle.Fill;
this.label4.Location = new System.Drawing.Point(0, 159);
this.label4.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label4.Location = new System.Drawing.Point(0, 212);
this.label4.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(341, 15);
this.label4.Size = new System.Drawing.Size(427, 20);
this.label4.TabIndex = 14;
this.label4.Text = "URL is the base URL, like https://cred.domain.local/SecretServer";
this.label4.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
@@ -240,14 +241,14 @@
// SSConnectionForm
//
this.AcceptButton = this.btnOK;
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 20F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 275);
this.ClientSize = new System.Drawing.Size(914, 367);
this.Controls.Add(this.label4);
this.Controls.Add(this.tableLayoutPanel2);
this.Controls.Add(this.tableLayoutPanel1);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Margin = new System.Windows.Forms.Padding(4);
this.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
this.Name = "SSConnectionForm";
this.Text = "Secret Server API Login Data";
this.Activated += new System.EventHandler(this.SSConnectionForm_Activated);

View File

@@ -1,4 +1,4 @@
namespace ExternalConnectors.TSS
namespace ExternalConnectors.DSS
{
public partial class SSConnectionForm : Form
{

View File

@@ -12,7 +12,7 @@
#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'"
#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant"
namespace SecretServerAuthentication.TSS
namespace SecretServerAuthentication.DSS
{
using System = global::System;
@@ -58,10 +58,7 @@ namespace SecretServerAuthentication.TSS
/// <param name="refresh_token">The refresh token. Required when refreshing a token.</param>
/// <returns>Successful retrieval of an access token</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
public System.Threading.Tasks.Task<TokenResponse> AuthorizeAsync(Grant_type grant_type, string username, string password, string refresh_token, string OTP)
{
return AuthorizeAsync(grant_type, username, password, refresh_token, System.Threading.CancellationToken.None, OTP);
}
public System.Threading.Tasks.Task<TokenResponse> AuthorizeAsync(Grant_type grant_type, string username, string password, string refresh_token, string OTP) => AuthorizeAsync(grant_type, username, password, refresh_token, System.Threading.CancellationToken.None, OTP);
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <summary>Retrieve or Refresh Access Token</summary>
@@ -355,10 +352,7 @@ namespace SecretServerAuthentication.TSS
Headers = headers;
}
public override string ToString()
{
return string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString());
}
public override string ToString() => string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString());
}
[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.14.8.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")]

View File

@@ -0,0 +1,335 @@
using Microsoft.Win32;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using SecretServerAuthentication.DSS;
using SecretServerRestClient.DSS;
using System.Security.Cryptography;
namespace ExternalConnectors.DSS;
public class SecretServerInterface
{
private static class SSConnectionData
{
public static string ssUsername = "";
public static string ssPassword = "";
public static string ssUrl = "";
public static string ssOTP = "";
public static bool ssSSO = false;
public static bool initdone = false;
//token
public static string ssTokenBearer = "";
public static DateTime ssTokenExpiresOn = DateTime.UtcNow;
public static string ssTokenRefresh = "";
public static void Init()
{
if (initdone == true)
return;
RegistryKey key = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\mRemoteSSInterface");
try
{
// display gui and ask for data
SSConnectionForm f = new();
string? un = key.GetValue("Username") as string;
f.tbUsername.Text = un ?? "";
f.tbPassword.Text = SSConnectionData.ssPassword; // in OTP refresh cases, this value might already be filled
string? url = key.GetValue("URL") as string;
if (url == null || !url.Contains("://"))
url = "https://cred.domain.local/SecretServer";
f.tbSSURL.Text = url;
var b = key.GetValue("SSO");
if (b == null || (string)b != "True")
ssSSO = false;
else
ssSSO = true;
f.cbUseSSO.Checked = ssSSO;
// show dialog
while (true)
{
_ = f.ShowDialog();
if (f.DialogResult != DialogResult.OK)
return;
// store values to memory
ssUsername = f.tbUsername.Text;
ssPassword = f.tbPassword.Text;
ssUrl = f.tbSSURL.Text;
ssSSO = f.cbUseSSO.Checked;
ssOTP = f.tbOTP.Text;
// check connection first
try
{
if (TestCredentials() == true)
{
initdone = true;
break;
}
}
catch (Exception)
{
MessageBox.Show("Test Credentials failed - please check your credentials");
}
}
// write values to registry
key.SetValue("Username", ssUsername);
key.SetValue("URL", ssUrl);
key.SetValue("SSO", ssSSO);
}
catch (Exception)
{
throw;
}
finally
{
key.Close();
}
}
}
private static bool TestCredentials()
{
if (SSConnectionData.ssSSO)
{
// checking creds doesn't really make sense here, as we can't modify them anyway if something is wrong
return true;
}
else
{
if (!String.IsNullOrEmpty(GetToken()))
{
return true;
}
else
{
return false;
}
}
}
private static SecretsServiceClient ConstructSecretsServiceClient()
{
string baseURL = SSConnectionData.ssUrl;
if (SSConnectionData.ssSSO)
{
// REQUIRES IIS CONFIG! https://docs.thycotic.com/ss/11.0.0/api-scripting/webservice-iwa-powershell
var handler = new HttpClientHandler() { UseDefaultCredentials = true };
var httpClient = new HttpClient(handler);
{
// Call REST API:
return new SecretsServiceClient($"{baseURL}/winauthwebservices/api", httpClient);
}
}
else
{
var httpClient = new HttpClient();
{
var token = GetToken();
// Set credentials (token):
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
// Call REST API:
return new SecretsServiceClient($"{baseURL}/api", httpClient);
}
}
}
private static void FetchSecret(int secretID, out string secretUsername, out string secretPassword, out string secretDomain, out string privatekey)
{
var client = ConstructSecretsServiceClient();
SecretModel secret = client.GetSecretAsync(false, true, secretID, null).Result;
// clear return variables
secretDomain = "";
secretUsername = "";
secretPassword = "";
privatekey = "";
string privatekeypassphrase = "";
// parse data and extract what we need
foreach (var item in secret.Items)
{
if (item.FieldName.ToLower().Equals("domain"))
secretDomain = item.ItemValue;
else if (item.FieldName.ToLower().Equals("username"))
secretUsername = item.ItemValue;
else if (item.FieldName.ToLower().Equals("password"))
secretPassword = item.ItemValue;
else if (item.FieldName.ToLower().Equals("private key"))
{
client.ReadResponseNoJSONConvert = true;
privatekey = client.GetFieldAsync(false, false, secretID, "private-key").Result;
client.ReadResponseNoJSONConvert = false;
}
else if (item.FieldName.ToLower().Equals("private key passphrase"))
privatekeypassphrase = item.ItemValue;
}
// need to decode the private key?
if (!string.IsNullOrEmpty(privatekeypassphrase))
{
try
{
var key = DecodePrivateKey(privatekey, privatekeypassphrase);
privatekey = key;
}
catch(Exception)
{
}
}
// conversion to putty format necessary?
if (!string.IsNullOrEmpty(privatekey) && !privatekey.StartsWith("PuTTY-User-Key-File-2"))
{
try
{
RSACryptoServiceProvider key = ImportPrivateKey(privatekey);
privatekey = PuttyKeyFileGenerator.ToPuttyPrivateKey(key);
}
catch (Exception)
{
}
}
}
#region PUTTY KEY HANDLING
// decode rsa private key with encryption password
private static string DecodePrivateKey(string encryptedPrivateKey, string password)
{
TextReader textReader = new StringReader(encryptedPrivateKey);
PemReader pemReader = new(textReader, new PasswordFinder(password));
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
TextWriter textWriter = new StringWriter();
var pemWriter = new PemWriter(textWriter);
pemWriter.WriteObject(keyPair.Private);
pemWriter.Writer.Flush();
return ""+textWriter.ToString();
}
private class PasswordFinder(string password) : IPasswordFinder
{
private string password = password;
public char[] GetPassword()
{
return password.ToCharArray();
}
}
// read private key pem string to rsacryptoserviceprovider
public static RSACryptoServiceProvider ImportPrivateKey(string pem)
{
PemReader pr = new(new StringReader(pem));
AsymmetricCipherKeyPair KeyPair = (AsymmetricCipherKeyPair)pr.ReadObject();
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)KeyPair.Private);
RSACryptoServiceProvider rsa = new();
rsa.ImportParameters(rsaParams);
return rsa;
}
#endregion
#region TOKEN
private static string GetToken()
{
// if there is no token, fetch a fresh one
if (String.IsNullOrEmpty(SSConnectionData.ssTokenBearer))
{
return GetTokenFresh();
}
// if there is a token, check if it is valid
if (SSConnectionData.ssTokenExpiresOn >= DateTime.UtcNow)
{
return SSConnectionData.ssTokenBearer;
}
else
{
// try using refresh token
using (var httpClient = new HttpClient())
{
var tokenClient = new OAuth2ServiceClient(SSConnectionData.ssUrl, httpClient);
TokenResponse token = new();
try
{
token = tokenClient.AuthorizeAsync(Grant_type.Refresh_token, null, null, SSConnectionData.ssTokenRefresh, null).Result;
var tokenResult = token.Access_token;
SSConnectionData.ssTokenBearer = tokenResult;
SSConnectionData.ssTokenRefresh = token.Refresh_token;
SSConnectionData.ssTokenExpiresOn = token.Expires_on;
return tokenResult;
}
catch (Exception)
{
// refresh token failed. clean memory and start fresh
SSConnectionData.ssTokenBearer = "";
SSConnectionData.ssTokenRefresh = "";
SSConnectionData.ssTokenExpiresOn = DateTime.Now;
// if OTP is required we need to ask user for a new OTP
if (!String.IsNullOrEmpty(SSConnectionData.ssOTP))
{
SSConnectionData.initdone = false;
// the call below executes a connection test, which fetches a valid token
SSConnectionData.Init();
// we now have a fresh token in memory. return it to caller
return SSConnectionData.ssTokenBearer;
}
else
{
// no user interaction required. get a fresh token and return it to caller
return GetTokenFresh();
}
}
}
}
}
static string GetTokenFresh()
{
using (var httpClient = new HttpClient())
{
// Authenticate:
var tokenClient = new OAuth2ServiceClient(SSConnectionData.ssUrl, httpClient);
// call below will throw an exception if the creds are invalid
var token = tokenClient.AuthorizeAsync(Grant_type.Password, SSConnectionData.ssUsername, SSConnectionData.ssPassword, null, SSConnectionData.ssOTP).Result;
// here we can be sure the creds are ok - return success state
var tokenResult = token.Access_token;
SSConnectionData.ssTokenBearer = tokenResult;
SSConnectionData.ssTokenRefresh = token.Refresh_token;
SSConnectionData.ssTokenExpiresOn = token.Expires_on;
return tokenResult;
}
}
#endregion
// input must be the secret id to fetch
public static void FetchSecretFromServer(string input, out string username, out string password, out string domain, out string privatekey)
{
// get secret id
int secretID = Int32.Parse(input);
// init connection credentials, display popup if necessary
SSConnectionData.Init();
// get the secret
FetchSecret(secretID, out username, out password, out domain, out privatekey);
}
}

View File

@@ -1,28 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<UseWindowsForms>True</UseWindowsForms>
<Platforms>x64</Platforms>
<Configurations>Debug;Release;Debug Portable;Release Portable</Configurations>
<Platforms>x64;arm64</Platforms>
<Configurations>Debug;Release;Debug Portable;Release Self-Contained;Deploy to github</Configurations>
<SupportedOSPlatformVersion>10.0.22621.0</SupportedOSPlatformVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Self-Contained|x64'">
<Optimize>True</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Self-Contained|arm64'">
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AWSSDK.Core" Version="3.7.11.2" />
<PackageReference Include="AWSSDK.EC2" Version="3.7.72" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="AWSSDK.Core" />
<PackageReference Include="AWSSDK.EC2" />
<PackageReference Include="BouncyCastle.Cryptography" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="VaultSharp" />
</ItemGroup>
<ItemGroup>
<Compile Update="AWS\AWSConnectionForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="TSS\SSConnectionForm.cs">
<Compile Update="AWS\AWSConnectionForm.cs" />
<Compile Update="CPS\CPSConnectionForm.cs" />
<Compile Update="DSS\SSConnectionForm.cs" />
<Compile Update="VO\VaultOpenbaoConnectionForm.cs">
<SubType>Form</SubType>
</Compile>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,131 @@
using System.Diagnostics;
using System.Net;
using System.Text.Json;
using System.Web;
namespace ExternalConnectors.OP;
public class OnePasswordCliException(string message, string arguments) : Exception(message)
{
public string Arguments { get; set; } = arguments;
}
public class OnePasswordCli
{
private const string OnePasswordCliExecutable = "op.exe";
// Username / password purpose metadata is used on Login category item fields
private const string UserNamePurpose = "USERNAME";
private const string PasswordPurpose = "PASSWORD";
// Server category items (and perhaps others) do have a built-in username/password field but don't have the `purpose` set
// and because it's a built-in field this can't be set afterwards.
// We use the label for as fallback because that can be user-modified to fit this convention in all cases.
private const string UserNameLabel = "username";
private const string PasswordLabel = "password";
private const string StringType = "STRING";
private const string SshKeyType = "SSHKEY";
private const string DomainLabel = "domain";
private record VaultUrl(string Label, string Href);
private record VaultField(string Id, string Label, string Type, string Purpose, string Value);
private record VaultItem(VaultUrl[]? Urls, VaultField[]? Fields);
private static readonly JsonSerializerOptions JsonSerializerOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
public static void ReadPassword(string input, out string username, out string password, out string domain, out string privateKey)
{
var inputUrl = new Uri(input);
var vault = WebUtility.UrlDecode(inputUrl.Host);
var queryParams = HttpUtility.ParseQueryString(inputUrl.Query);
var account = queryParams["account"];
var item = WebUtility.UrlDecode(inputUrl.AbsolutePath.TrimStart('/'));
ItemGet(item, vault, account, out username, out password, out domain, out privateKey);
}
private static void ItemGet(string item, string? vault, string? account, out string username, out string password, out string domain, out string privateKey)
{
var args = new List<string> { "item", "get", item };
if (!string.IsNullOrEmpty(account))
{
args.Add("--account");
args.Add(account);
}
if (!string.IsNullOrEmpty(vault))
{
args.Add("--vault");
args.Add(vault);
}
args.Add("--format");
args.Add("json");
string commandLine = OnePasswordCliExecutable + " " + string.Join(' ', args);
var exitCode = RunCommand(OnePasswordCliExecutable, args, out var output, out var error);
if (exitCode != 0)
{
username = string.Empty;
password = string.Empty;
privateKey = string.Empty;
domain = string.Empty;
throw new OnePasswordCliException($"Error running op item get: {error}",
commandLine);
}
var items = JsonSerializer.Deserialize<VaultItem>(output, JsonSerializerOptions) ??
throw new OnePasswordCliException("1Password returned null",
commandLine);
username = FindField(items, UserNamePurpose, UserNameLabel);
password = FindField(items, PasswordPurpose, PasswordLabel);
privateKey = items.Fields?.FirstOrDefault(x => x.Type == SshKeyType)?.Value ?? string.Empty;
domain = items.Fields?.FirstOrDefault(x => x.Type == StringType && x.Label == DomainLabel)?.Value ?? string.Empty;
if(string.IsNullOrEmpty(password) && string.IsNullOrEmpty(privateKey))
{
throw new OnePasswordCliException("No secret found in 1Password. At least fields with labels username/password or a SshKey are expected.", commandLine);
}
}
private static string FindField(VaultItem items, string purpose, string fallbackLabel)
{
return items.Fields?.FirstOrDefault(x => x.Purpose == purpose)?.Value ??
items.Fields?.FirstOrDefault(x => x.Type == StringType && string.Equals(x.Id, fallbackLabel, StringComparison.InvariantCultureIgnoreCase))?.Value ??
items.Fields?.FirstOrDefault(x => x.Type == StringType && string.Equals(x.Label, fallbackLabel, StringComparison.InvariantCultureIgnoreCase))?.Value ??
string.Empty;
}
private static int RunCommand(string command, IReadOnlyCollection<string> arguments, out string output,
out string error)
{
var processStartInfo = new ProcessStartInfo
{
FileName = command,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
foreach (var argument in arguments)
{
processStartInfo.ArgumentList.Add(argument);
}
using var process = new Process();
process.StartInfo = processStartInfo;
process.Start();
output = process.StandardOutput.ReadToEnd();
error = process.StandardError.ReadToEnd();
process.WaitForExit();
return process.ExitCode;
}
}

View File

@@ -0,0 +1,111 @@
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
namespace ExternalConnectors;
public class PuttyKeyFileGenerator
{
private const int prefixSize = 4;
private const int paddedPrefixSize = prefixSize + 1;
private const int lineLength = 64;
private const string keyType = "ssh-rsa";
private const string encryptionType = "none";
public static string ToPuttyPrivateKey(RSACryptoServiceProvider cryptoServiceProvider, string Comment = "imported-openssh-key")
{
var publicParameters = cryptoServiceProvider.ExportParameters(false);
byte[] publicBuffer = new byte[3 + keyType.Length + GetPrefixSize(publicParameters.Exponent) + publicParameters.Exponent!.Length + GetPrefixSize(publicParameters.Modulus) + publicParameters.Modulus!.Length + 1];
using (var bw = new BinaryWriter(new MemoryStream(publicBuffer)))
{
bw.Write(new byte[] { 0x00, 0x00, 0x00 });
bw.Write(Encoding.ASCII.GetBytes(keyType));
PutPrefixed(bw, publicParameters.Exponent, CheckIsNeddPadding(publicParameters.Exponent));
PutPrefixed(bw, publicParameters.Modulus, CheckIsNeddPadding(publicParameters.Modulus));
}
var publicBlob = System.Convert.ToBase64String(publicBuffer);
var privateParameters = cryptoServiceProvider.ExportParameters(true);
byte[] privateBuffer = new byte[paddedPrefixSize + privateParameters.D!.Length + paddedPrefixSize + privateParameters.P!.Length + paddedPrefixSize + privateParameters.Q!.Length + paddedPrefixSize + privateParameters.InverseQ!.Length];
using (var bw = new BinaryWriter(new MemoryStream(privateBuffer)))
{
PutPrefixed(bw, privateParameters.D, true);
PutPrefixed(bw, privateParameters.P, true);
PutPrefixed(bw, privateParameters.Q, true);
PutPrefixed(bw, privateParameters.InverseQ, true);
}
var privateBlob = System.Convert.ToBase64String(privateBuffer);
HMACSHA1 hmacSha1 = new(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes("putty-private-key-file-mac-key")));
byte[] bytesToHash = new byte[prefixSize + keyType.Length + prefixSize + encryptionType.Length + prefixSize + Comment.Length + prefixSize + publicBuffer.Length + prefixSize + privateBuffer.Length];
using (var bw = new BinaryWriter(new MemoryStream(bytesToHash)))
{
PutPrefixed(bw, Encoding.ASCII.GetBytes(keyType));
PutPrefixed(bw, Encoding.ASCII.GetBytes(encryptionType));
PutPrefixed(bw, Encoding.ASCII.GetBytes(Comment));
PutPrefixed(bw, publicBuffer);
PutPrefixed(bw, privateBuffer);
}
var hash = string.Join("", hmacSha1.ComputeHash(bytesToHash).Select(x => $"{x:x2}"));
var sb = new StringBuilder();
sb.AppendLine("PuTTY-User-Key-File-2: " + keyType);
sb.AppendLine("Encryption: " + encryptionType);
sb.AppendLine("Comment: " + Comment);
var publicLines = SpliceText(publicBlob, lineLength);
sb.AppendLine("Public-Lines: " + publicLines.Length);
foreach (var line in publicLines)
{
sb.AppendLine(line);
}
var privateLines = SpliceText(privateBlob, lineLength);
sb.AppendLine("Private-Lines: " + privateLines.Length);
foreach (var line in privateLines)
{
sb.AppendLine(line);
}
sb.AppendLine("Private-MAC: " + hash);
return sb.ToString();
}
private static void PutPrefixed(BinaryWriter bw, byte[] bytes, bool addLeadingNull = false)
{
bw.Write(BitConverter.GetBytes(bytes.Length + (addLeadingNull ? 1 : 0)).Reverse().ToArray());
if (addLeadingNull)
bw.Write(new byte[] { 0x00 });
bw.Write(bytes);
}
private static string[] SpliceText(string text, int lineLength)
{
return Regex.Matches(text, ".{1," + lineLength + "}").Cast<Match>().Select(m => m.Value).ToArray();
}
private static int GetPrefixSize(byte[]? bytes)
{
if (bytes is null)
return 0;
return CheckIsNeddPadding(bytes) ? paddedPrefixSize : prefixSize;
}
private static bool CheckIsNeddPadding(byte[] bytes)
{
if (bytes is null || bytes.Length == 0)
return false;
// 128 == 10000000
// This means that the number of bits can be divided by 8.
// According to the algorithm in putty, you need to add a padding.
return bytes[0] >= 128;
}
}

View File

@@ -1,254 +0,0 @@
using Microsoft.Win32;
using SecretServerAuthentication.TSS;
using SecretServerRestClient.TSS;
namespace ExternalConnectors.TSS
{
public class SecretServerInterface
{
private static class SSConnectionData
{
public static string ssUsername = "";
public static string ssPassword = "";
public static string ssUrl = "";
public static string ssOTP = "";
public static bool ssSSO = false;
public static bool initdone = false;
//token
public static string ssTokenBearer = "";
public static DateTime ssTokenExpiresOn = DateTime.UtcNow;
public static string ssTokenRefresh = "";
public static void Init()
{
if (initdone == true)
return;
RegistryKey key = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\mRemoteSSInterface");
try
{
// display gui and ask for data
SSConnectionForm f = new SSConnectionForm();
string? un = key.GetValue("Username") as string;
f.tbUsername.Text = un ?? "";
f.tbPassword.Text = SSConnectionData.ssPassword; // in OTP refresh cases, this value might already be filled
string? url = key.GetValue("URL") as string;
if (url == null || !url.Contains("://"))
url = "https://cred.domain.local/SecretServer";
f.tbSSURL.Text = url;
var b = key.GetValue("SSO");
if (b == null || (string)b != "True")
ssSSO = false;
else
ssSSO = true;
f.cbUseSSO.Checked = ssSSO;
// show dialog
while (true)
{
_ = f.ShowDialog();
if (f.DialogResult != DialogResult.OK)
return;
// store values to memory
ssUsername = f.tbUsername.Text;
ssPassword = f.tbPassword.Text;
ssUrl = f.tbSSURL.Text;
ssSSO = f.cbUseSSO.Checked;
ssOTP = f.tbOTP.Text;
// check connection first
try
{
if (TestCredentials() == true)
{
initdone = true;
break;
}
}
catch (Exception)
{
MessageBox.Show("Test Credentials failed - please check your credentials");
}
}
// write values to registry
key.SetValue("Username", ssUsername);
key.SetValue("URL", ssUrl);
key.SetValue("SSO", ssSSO);
}
catch (Exception)
{
throw;
}
finally
{
key.Close();
}
}
}
private static bool TestCredentials()
{
if (SSConnectionData.ssSSO)
{
// checking creds doesn't really make sense here, as we can't modify them anyway if something is wrong
return true;
}
else
{
if (!String.IsNullOrEmpty(GetToken()))
{
return true;
}
else
{
return false;
}
}
}
private static void FetchSecret(int secretID, out string secretUsername, out string secretPassword, out string secretDomain)
{
string baseURL = SSConnectionData.ssUrl;
SecretModel secret;
if (SSConnectionData.ssSSO)
{
// REQUIRES IIS CONFIG! https://docs.thycotic.com/ss/11.0.0/api-scripting/webservice-iwa-powershell
var handler = new HttpClientHandler() { UseDefaultCredentials = true };
using (var httpClient = new HttpClient(handler))
{
// Call REST API:
var client = new SecretsServiceClient($"{baseURL}/winauthwebservices/api", httpClient);
secret = client.GetSecretAsync(false, true, secretID, null).Result;
}
}
else
{
using (var httpClient = new HttpClient())
{
var token = GetToken();
// Set credentials (token):
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
// Call REST API:
var client = new SecretsServiceClient($"{baseURL}/api", httpClient);
secret = client.GetSecretAsync(false, true, secretID, null).Result;
}
}
// clear return variables
secretDomain = "";
secretUsername = "";
secretPassword = "";
// parse data and extract what we need
foreach (var item in secret.Items)
{
if (item.FieldName.ToLower().Equals("domain"))
secretDomain = item.ItemValue;
else if (item.FieldName.ToLower().Equals("username"))
secretUsername = item.ItemValue;
else if (item.FieldName.ToLower().Equals("password"))
secretPassword = item.ItemValue;
}
}
private static string GetToken()
{
// if there is no token, fetch a fresh one
if (String.IsNullOrEmpty(SSConnectionData.ssTokenBearer))
{
return GetTokenFresh();
}
// if there is a token, check if it is valid
if (SSConnectionData.ssTokenExpiresOn >= DateTime.UtcNow)
{
return SSConnectionData.ssTokenBearer;
}
else
{
// try using refresh token
using (var httpClient = new HttpClient())
{
var tokenClient = new OAuth2ServiceClient(SSConnectionData.ssUrl, httpClient);
TokenResponse token = new();
try
{
token = tokenClient.AuthorizeAsync(Grant_type.Refresh_token, null, null, SSConnectionData.ssTokenRefresh, null).Result;
var tokenResult = token.Access_token;
SSConnectionData.ssTokenBearer = tokenResult;
SSConnectionData.ssTokenRefresh = token.Refresh_token;
SSConnectionData.ssTokenExpiresOn = token.Expires_on;
return tokenResult;
}
catch (Exception)
{
// refresh token failed. clean memory and start fresh
SSConnectionData.ssTokenBearer = "";
SSConnectionData.ssTokenRefresh = "";
SSConnectionData.ssTokenExpiresOn = DateTime.Now;
// if OTP is required we need to ask user for a new OTP
if (!String.IsNullOrEmpty(SSConnectionData.ssOTP))
{
SSConnectionData.initdone = false;
// the call below executes a connection test, which fetches a valid token
SSConnectionData.Init();
// we now have a fresh token in memory. return it to caller
return SSConnectionData.ssTokenBearer;
}
else
{
// no user interaction required. get a fresh token and return it to caller
return GetTokenFresh();
}
}
}
}
}
static string GetTokenFresh()
{
using (var httpClient = new HttpClient())
{
// Authenticate:
var tokenClient = new OAuth2ServiceClient(SSConnectionData.ssUrl, httpClient);
// call below will throw an exception if the creds are invalid
var token = tokenClient.AuthorizeAsync(Grant_type.Password, SSConnectionData.ssUsername, SSConnectionData.ssPassword, null, SSConnectionData.ssOTP).Result;
// here we can be sure the creds are ok - return success state
var tokenResult = token.Access_token;
SSConnectionData.ssTokenBearer = tokenResult;
SSConnectionData.ssTokenRefresh = token.Refresh_token;
SSConnectionData.ssTokenExpiresOn = token.Expires_on;
return tokenResult;
}
}
// input must be in form "SSAPI:xxxx" where xxx is the secret id to fetch
public static void FetchSecretFromServer(string input, out string username, out string password, out string domain)
{
// get secret id
if (!input.StartsWith("SSAPI:"))
throw new Exception("calling this function requires SSAPI: input");
int secretID = Int32.Parse(input.Substring(6));
// init connection credentials, display popup if necessary
SSConnectionData.Init();
// get the secret
FetchSecret(secretID, out username, out password, out domain);
}
}
}

View File

@@ -0,0 +1,105 @@
using Microsoft.Win32;
using System.Net;
using System.Net.Sockets;
using VaultSharp;
using VaultSharp.V1.AuthMethods;
using VaultSharp.V1.AuthMethods.Token;
namespace ExternalConnectors.VO {
public class VaultOpenbaoException(string message, string? arguments = null) : Exception(message) {
public string Arguments { get; set; } = arguments ?? string.Empty;
}
public static class VaultOpenbao {
private static readonly RegistryKey baseKey = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\mRemoteVaultOpenbao");
private static string token = "";
private static VaultClient GetClient() {
string url = (string)baseKey.GetValue("URL", "");
using VaultOpenbaoConnectionForm voForm = new();
voForm.tbUrl.Text = url;
voForm.tbToken.Text = token;
_ = voForm.ShowDialog();
if (voForm.DialogResult != DialogResult.OK)
throw new VaultOpenbaoException($"No credential provided");
url = voForm.tbUrl.Text;
if (!string.IsNullOrEmpty(voForm.tbToken.Text)) // override token if provided
token = voForm.tbToken.Text;
IAuthMethodInfo authMethod = new TokenAuthMethodInfo(token);
var vaultClientSettings = new VaultClientSettings(url, authMethod);
VaultClient client = new(vaultClientSettings);
var sysInfo = client.V1.System.GetInitStatusAsync().Result;
if (!sysInfo) {
MessageBox.Show("Test connection failed", "Vault Openbao", MessageBoxButtons.OK, MessageBoxIcon.Error);
throw new VaultOpenbaoException("Url not working");
}
baseKey.SetValue("URL", url);
return client;
}
private static void TestMountType(VaultClient vaultClient, string mount, int VaultOpenbaoSecretEngine) {
switch (vaultClient.V1.System.GetSecretBackendAsync(mount).Result.Data.Type.Type) {
case "kv" when VaultOpenbaoSecretEngine != 0:
throw new VaultOpenbaoException($"Backend of type kv does not match expected type {VaultOpenbaoSecretEngine}");
case "ldap" when VaultOpenbaoSecretEngine != 1 && VaultOpenbaoSecretEngine != 2:
throw new VaultOpenbaoException($"Backend of type ldap does not match expected type {VaultOpenbaoSecretEngine}");
case "ssh" when VaultOpenbaoSecretEngine != 3:
throw new VaultOpenbaoException($"Backend of type ssh does not match expected type {VaultOpenbaoSecretEngine}");
}
}
public static void ReadOtpSSH(string mount, string role, string? username, string address, out string password) {
VaultClient vaultClient = GetClient();
TestMountType(vaultClient, mount, 3);
if (!IPAddress.TryParse(address, out _)) {
try {
var addrs = Dns.GetHostAddressesAsync(address).Result;
if (addrs == null || addrs.Length == 0) {
throw new VaultOpenbaoException($"Could not resolve address '{address}'");
}
// Prefer IPv4, otherwise take first available
var selected = addrs.FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork) ?? addrs[0];
address = selected.ToString();
} catch (Exception ex) {
throw new VaultOpenbaoException($"Failed to resolve address '{address}'", ex.Message);
}
}
var otp = vaultClient.V1.Secrets.SSH.GetCredentialsAsync(role, address, username, mount).Result;
password = otp.Data.Key;
}
public static void ReadPasswordSSH(int secretEngine, string mount, string role, string username, out string password) {
VaultClient vaultClient = GetClient();
TestMountType(vaultClient, mount, secretEngine);
switch (secretEngine) {
case 0:
var kv = vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync(role, mountPoint: mount).Result;
password = kv.Data.Data[username].ToString() ?? string.Empty;
return;
default:
throw new VaultOpenbaoException($"Backend of type {secretEngine} is not supported");
}
}
public static void ReadPasswordRDP(int secretEngine, string mount, string role, ref string username, out string password) {
VaultClient vaultClient = GetClient();
TestMountType(vaultClient, mount, secretEngine);
switch (secretEngine) {
case 0:
var kv = vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync(role, mountPoint: mount).Result;
password = kv.Data.Data[username].ToString() ?? string.Empty;
return;
case 1:
var ldapd = vaultClient.V1.Secrets.OpenLDAP.GetDynamicCredentialsAsync(role, mount).Result;
username = ldapd.Data.Username;
password = ldapd.Data.Password;
return;
case 2:
var ldaps = vaultClient.V1.Secrets.OpenLDAP.GetStaticCredentialsAsync(role, mount).Result;
username = ldaps.Data.Username;
password = ldaps.Data.Password;
return;
default:
throw new VaultOpenbaoException($"Backend of type {secretEngine} is not supported");
}
}
}
}

View File

@@ -0,0 +1,185 @@
namespace ExternalConnectors.VO
{
partial class VaultOpenbaoConnectionForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
tbUrl = new TextBox();
tbToken = new TextBox();
btnOK = new Button();
btnCancel = new Button();
tableLayoutPanel1 = new TableLayoutPanel();
label1 = new Label();
label2 = new Label();
tableLayoutPanel2 = new TableLayoutPanel();
tableLayoutPanel1.SuspendLayout();
tableLayoutPanel2.SuspendLayout();
SuspendLayout();
//
// tbUrl
//
tbUrl.Dock = DockStyle.Fill;
tbUrl.Location = new Point(174, 5);
tbUrl.Margin = new Padding(5);
tbUrl.Name = "tbUrl";
tbUrl.Size = new Size(559, 27);
tbUrl.TabIndex = 0;
//
// tbToken
//
tbToken.Dock = DockStyle.Fill;
tbToken.Location = new Point(174, 57);
tbToken.Margin = new Padding(5);
tbToken.Name = "tbToken";
tbToken.Size = new Size(559, 27);
tbToken.TabIndex = 2;
tbToken.UseSystemPasswordChar = true;
tbToken.Focus();
//
// btnOK
//
btnOK.Anchor = AnchorStyles.Right;
btnOK.DialogResult = DialogResult.OK;
btnOK.Location = new Point(250, 16);
btnOK.Margin = new Padding(5);
btnOK.Name = "btnOK";
btnOK.Size = new Size(101, 35);
btnOK.TabIndex = 10;
btnOK.Text = "OK";
btnOK.UseVisualStyleBackColor = true;
//
// btnCancel
//
btnCancel.Anchor = AnchorStyles.Left;
btnCancel.DialogResult = DialogResult.Cancel;
btnCancel.Location = new Point(387, 16);
btnCancel.Margin = new Padding(5);
btnCancel.Name = "btnCancel";
btnCancel.Size = new Size(101, 35);
btnCancel.TabIndex = 11;
btnCancel.Text = "Cancel";
btnCancel.UseVisualStyleBackColor = true;
//
// tableLayoutPanel1
//
tableLayoutPanel1.ColumnCount = 2;
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 22.92994F));
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 77.07006F));
tableLayoutPanel1.Controls.Add(label1, 0, 0);
tableLayoutPanel1.Controls.Add(label2, 0, 1);
tableLayoutPanel1.Controls.Add(tbUrl, 1, 0);
tableLayoutPanel1.Controls.Add(tbToken, 1, 1);
tableLayoutPanel1.Dock = DockStyle.Top;
tableLayoutPanel1.Location = new Point(0, 0);
tableLayoutPanel1.Margin = new Padding(5);
tableLayoutPanel1.Name = "tableLayoutPanel1";
tableLayoutPanel1.RowCount = 3;
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 31F));
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 31F));
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 31F));
tableLayoutPanel1.Size = new Size(738, 136);
tableLayoutPanel1.TabIndex = 12;
//
// label1
//
label1.AutoSize = true;
label1.Dock = DockStyle.Fill;
label1.Location = new Point(5, 0);
label1.Margin = new Padding(5, 0, 5, 0);
label1.Name = "label1";
label1.Size = new Size(159, 52);
label1.TabIndex = 2;
label1.Text = "Server URL";
label1.TextAlign = ContentAlignment.MiddleLeft;
//
// label2
//
label2.AutoSize = true;
label2.Dock = DockStyle.Fill;
label2.Location = new Point(5, 52);
label2.Margin = new Padding(5, 0, 5, 0);
label2.Name = "label2";
label2.Size = new Size(159, 52);
label2.TabIndex = 4;
label2.Text = "Access Token";
label2.TextAlign = ContentAlignment.MiddleLeft;
//
// tableLayoutPanel2
//
tableLayoutPanel2.ColumnCount = 5;
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 106F));
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 26F));
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 106F));
tableLayoutPanel2.Controls.Add(btnOK, 1, 0);
tableLayoutPanel2.Controls.Add(btnCancel, 3, 0);
tableLayoutPanel2.Dock = DockStyle.Bottom;
tableLayoutPanel2.Location = new Point(0, 149);
tableLayoutPanel2.Margin = new Padding(5);
tableLayoutPanel2.Name = "tableLayoutPanel2";
tableLayoutPanel2.RowCount = 1;
tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
tableLayoutPanel2.Size = new Size(738, 67);
tableLayoutPanel2.TabIndex = 13;
//
// VaultOpenbaoConnectionForm
//
AcceptButton = btnOK;
AutoScaleDimensions = new SizeF(8F, 20F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(738, 216);
Controls.Add(tableLayoutPanel2);
Controls.Add(tableLayoutPanel1);
FormBorderStyle = FormBorderStyle.FixedDialog;
Margin = new Padding(5);
MaximizeBox = false;
MinimizeBox = false;
Name = "VaultOpenbaoConnectionForm";
SizeGripStyle = SizeGripStyle.Hide;
Text = "Vault/Openbao API Login Data";
Activated += VaultOpenbaoConnectionForm_Activated;
tableLayoutPanel1.ResumeLayout(false);
tableLayoutPanel1.PerformLayout();
tableLayoutPanel2.ResumeLayout(false);
ResumeLayout(false);
}
#endregion
public System.Windows.Forms.TextBox tbUrl;
public System.Windows.Forms.TextBox tbToken;
private System.Windows.Forms.Button btnOK;
private System.Windows.Forms.Button btnCancel;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2;
}
}

View File

@@ -0,0 +1,13 @@
namespace ExternalConnectors.VO
{
public partial class VaultOpenbaoConnectionForm : Form {
public VaultOpenbaoConnectionForm() {
InitializeComponent();
}
private void VaultOpenbaoConnectionForm_Activated(object sender, EventArgs e) {
tbUrl.Focus();
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

158
IMPLEMENTATION_NOTES.md Normal file
View File

@@ -0,0 +1,158 @@
# Connection Frame Color Feature - Implementation Summary
## Overview
This implementation adds a "Connection Frame Color" feature to mRemoteNG that allows users to visually distinguish between different connection environments (e.g., production, test, development) by displaying a colored border around connection panels.
## Feature Request Context
The feature was requested in issue to prevent accidental operations on production systems. The user cited DBeaver database management tool as an example, which uses a red frame to indicate production database connections.
## Implementation Details
### 1. Data Model (Connection/ConnectionFrameColor.cs)
Created an enum with the following values:
- **None** (default): No colored border
- **Red**: Intended for production environments
- **Yellow**: Intended for staging/UAT environments
- **Green**: Intended for test environments
- **Blue**: Intended for development environments
- **Purple**: Intended for custom/other environments
### 2. Property Addition (Connection/AbstractConnectionRecord.cs)
- Added `ConnectionFrameColor` property to the base connection record class
- Property is categorized under "Display" section in PropertyGrid
- Uses EnumTypeConverter for proper display in PropertyGrid
- Includes localized descriptions
### 3. Inheritance Support (Connection/ConnectionInfoInheritance.cs)
- Added `ConnectionFrameColor` inheritance property
- Allows folders to set a frame color that child connections can inherit
- Follows the same pattern as other inheritable properties
### 4. Serialization
#### XML Serialization (Config/Serializers/ConnectionSerializers/Xml/)
- **XmlConnectionNodeSerializer28.cs**: Serializes ConnectionFrameColor as an XML attribute
- **XmlConnectionsDeserializer.cs**: Deserializes ConnectionFrameColor from XML
- Includes inheritance attribute handling (InheritConnectionFrameColor)
- Backward compatible: old files without this attribute will default to None
#### CSV Serialization (Config/Serializers/ConnectionSerializers/Csv/)
- **CsvConnectionsSerializerMremotengFormat.cs**: Added ConnectionFrameColor to CSV export
- Includes both value and inheritance columns
- Maintains CSV column order consistency
### 5. Visual Rendering (Connection/InterfaceControl.cs)
- Added custom Paint event handler to InterfaceControl
- Draws a 4-pixel colored border around the connection panel when ConnectionFrameColor is set
- Uses specific colors:
- Red: RGB(220, 53, 69) - Bootstrap danger red
- Yellow: RGB(255, 193, 7) - Warning yellow
- Green: RGB(40, 167, 69) - Success green
- Blue: RGB(0, 123, 255) - Primary blue
- Purple: RGB(111, 66, 193) - Purple
- Border is drawn inside the control bounds to avoid clipping
### 6. Localization (Language/Language.resx)
Added language resources for:
- ConnectionFrameColor: "Connection Frame Color"
- PropertyDescriptionConnectionFrameColor: Description shown in PropertyGrid
- FrameColorNone: "None"
- FrameColorRed: "Red (Production)"
- FrameColorYellow: "Yellow (Staging/UAT)"
- FrameColorGreen: "Green (Test)"
- FrameColorBlue: "Blue (Development)"
- FrameColorPurple: "Purple (Custom)"
### 7. Documentation (mRemoteNGDocumentation/howtos/connection_frame_color.rst)
Created comprehensive documentation including:
- Overview and purpose
- Step-by-step usage instructions
- Visual examples
- Inheritance explanation
- Best practices for environment organization
- Troubleshooting guide
## Technical Design Decisions
### Why 4-pixel border?
- Wide enough to be immediately noticeable
- Not so wide as to obscure content
- Consistent with modern UI design patterns
### Why these specific colors?
- Colors chosen based on common conventions:
- Red = danger/production (universal warning color)
- Yellow = caution/staging (standard warning color)
- Green = safe/test (universal "go" color)
- Blue = development (calm, neutral)
- Purple = custom (distinct but not alarming)
- Colors use accessible, high-contrast RGB values
### Why enum instead of custom color picker?
- Simpler UI (dropdown vs color picker)
- Ensures consistency across team/organization
- Prevents confusion from too many color choices
- Follows principle of "convention over configuration"
- Can be extended in future if needed
### Why inherit from Panel?
- InterfaceControl is already a Panel (see InterfaceControl.Designer.cs)
- Panel has built-in Paint event support
- No need for additional controls or complexity
## Backward Compatibility
- Old connection files (without ConnectionFrameColor attribute) automatically default to None
- No migration needed
- Feature is completely opt-in
- Does not affect existing functionality
## Testing Recommendations
When testing this feature, verify:
1. **Property Display**: ConnectionFrameColor appears in PropertyGrid under Display section
2. **Enum Values**: All color options appear in dropdown
3. **Visual Rendering**: Border appears when color is selected and connection is active
4. **Inheritance**: Setting on folder and enabling inheritance on child works correctly
5. **Serialization**:
- Save connection with frame color set
- Close and reopen file
- Verify color is preserved
6. **CSV Export**: ConnectionFrameColor appears in exported CSV
7. **Backward Compatibility**: Open old connection files without errors
## Future Enhancements (Out of Scope)
Potential future improvements:
- Custom color picker support
- Border width customization
- Border style options (solid, dashed, etc.)
- Tab header color indicator in addition to panel border
- Global warning when connecting to production (confirmation dialog)
- Audit logging for production connections
## Files Modified
1. mRemoteNG/Connection/ConnectionFrameColor.cs (NEW)
2. mRemoteNG/Connection/AbstractConnectionRecord.cs
3. mRemoteNG/Connection/ConnectionInfo.cs
4. mRemoteNG/Connection/ConnectionInfoInheritance.cs
5. mRemoteNG/Connection/InterfaceControl.cs
6. mRemoteNG/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionNodeSerializer28.cs
7. mRemoteNG/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializer.cs
8. mRemoteNG/Config/Serializers/ConnectionSerializers/Csv/CsvConnectionsSerializerMremotengFormat.cs
9. mRemoteNG/Language/Language.resx
10. mRemoteNGDocumentation/howtos/connection_frame_color.rst (NEW)
## Code Review Checklist
- [x] Property follows existing naming conventions
- [x] Enum values are localized
- [x] Inheritance support implemented
- [x] XML serialization/deserialization working
- [x] CSV serialization updated
- [x] Visual rendering implemented
- [x] Documentation created
- [x] Backward compatibility maintained
- [x] No breaking changes
- [x] Code follows existing patterns in codebase

View File

@@ -0,0 +1,15 @@
using System.Reflection;
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("2.9.3.*")]
[assembly: AssemblyFileVersion("2.9.3")]
[assembly: AssemblyInformationalVersion("2.9.3")]
[assembly: System.CLSCompliant(true)]

View File

@@ -0,0 +1,520 @@
/*
* CellEditKeyEngine - A engine that allows the behaviour of arbitrary keys to be configured
*
* Author: Phillip Piper
* Date: 3-March-2011 10:53 pm
*
* Change log:
* v2.8
* 2014-05-30 JPP - When a row is disabled, skip over it when looking for another cell to edit
* v2.5
* 2012-04-14 JPP - Fixed bug where, on a OLV with only a single editable column, tabbing
* to change rows would edit the cell above rather than the cell below
* the cell being edited.
* 2.5
* 2011-03-03 JPP - First version
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using BrightIdeasSoftware;
namespace BrightIdeasSoftware {
/// <summary>
/// Indicates the behavior of a key when a cell "on the edge" is being edited.
/// and the normal behavior of that key would exceed the edge. For example,
/// for a key that normally moves one column to the left, the "edge" would be
/// the left most column, since the normal action of the key cannot be taken
/// (since there are no more columns to the left).
/// </summary>
public enum CellEditAtEdgeBehaviour {
/// <summary>
/// The key press will be ignored
/// </summary>
Ignore,
/// <summary>
/// The key press will result in the cell editing wrapping to the
/// cell on the opposite edge.
/// </summary>
Wrap,
/// <summary>
/// The key press will wrap, but the column will be changed to the
/// appropiate adjacent column. This only makes sense for keys where
/// the normal action is ChangeRow.
/// </summary>
ChangeColumn,
/// <summary>
/// The key press will wrap, but the row will be changed to the
/// appropiate adjacent row. This only makes sense for keys where
/// the normal action is ChangeColumn.
/// </summary>
ChangeRow,
/// <summary>
/// The key will result in the current edit operation being ended.
/// </summary>
EndEdit
};
/// <summary>
/// Indicates the normal behaviour of a key when used during a cell edit
/// operation.
/// </summary>
public enum CellEditCharacterBehaviour {
/// <summary>
/// The key press will be ignored
/// </summary>
Ignore,
/// <summary>
/// The key press will end the current edit and begin an edit
/// operation on the next editable cell to the left.
/// </summary>
ChangeColumnLeft,
/// <summary>
/// The key press will end the current edit and begin an edit
/// operation on the next editable cell to the right.
/// </summary>
ChangeColumnRight,
/// <summary>
/// The key press will end the current edit and begin an edit
/// operation on the row above.
/// </summary>
ChangeRowUp,
/// <summary>
/// The key press will end the current edit and begin an edit
/// operation on the row below
/// </summary>
ChangeRowDown,
/// <summary>
/// The key press will cancel the current edit
/// </summary>
CancelEdit,
/// <summary>
/// The key press will finish the current edit operation
/// </summary>
EndEdit,
/// <summary>
/// Custom verb that can be used for specialized actions.
/// </summary>
CustomVerb1,
/// <summary>
/// Custom verb that can be used for specialized actions.
/// </summary>
CustomVerb2,
/// <summary>
/// Custom verb that can be used for specialized actions.
/// </summary>
CustomVerb3,
/// <summary>
/// Custom verb that can be used for specialized actions.
/// </summary>
CustomVerb4,
/// <summary>
/// Custom verb that can be used for specialized actions.
/// </summary>
CustomVerb5,
/// <summary>
/// Custom verb that can be used for specialized actions.
/// </summary>
CustomVerb6,
/// <summary>
/// Custom verb that can be used for specialized actions.
/// </summary>
CustomVerb7,
/// <summary>
/// Custom verb that can be used for specialized actions.
/// </summary>
CustomVerb8,
/// <summary>
/// Custom verb that can be used for specialized actions.
/// </summary>
CustomVerb9,
/// <summary>
/// Custom verb that can be used for specialized actions.
/// </summary>
CustomVerb10,
};
/// <summary>
/// Instances of this class handle key presses during a cell edit operation.
/// </summary>
public class CellEditKeyEngine {
#region Public interface
/// <summary>
/// Sets the behaviour of a given key
/// </summary>
/// <param name="key"></param>
/// <param name="normalBehaviour"></param>
/// <param name="atEdgeBehaviour"></param>
public virtual void SetKeyBehaviour(Keys key, CellEditCharacterBehaviour normalBehaviour, CellEditAtEdgeBehaviour atEdgeBehaviour) {
this.CellEditKeyMap[key] = normalBehaviour;
this.CellEditKeyAtEdgeBehaviourMap[key] = atEdgeBehaviour;
}
/// <summary>
/// Handle a key press
/// </summary>
/// <param name="olv"></param>
/// <param name="keyData"></param>
/// <returns>True if the key was completely handled.</returns>
public virtual bool HandleKey(ObjectListView olv, Keys keyData) {
if (olv == null) throw new ArgumentNullException("olv");
CellEditCharacterBehaviour behaviour;
if (!CellEditKeyMap.TryGetValue(keyData, out behaviour))
return false;
this.ListView = olv;
switch (behaviour) {
case CellEditCharacterBehaviour.Ignore:
break;
case CellEditCharacterBehaviour.CancelEdit:
this.HandleCancelEdit();
break;
case CellEditCharacterBehaviour.EndEdit:
this.HandleEndEdit();
break;
case CellEditCharacterBehaviour.ChangeColumnLeft:
case CellEditCharacterBehaviour.ChangeColumnRight:
this.HandleColumnChange(keyData, behaviour);
break;
case CellEditCharacterBehaviour.ChangeRowDown:
case CellEditCharacterBehaviour.ChangeRowUp:
this.HandleRowChange(keyData, behaviour);
break;
default:
return this.HandleCustomVerb(keyData, behaviour);
};
return true;
}
#endregion
#region Implementation properties
/// <summary>
/// Gets or sets the ObjectListView on which the current key is being handled.
/// This cannot be null.
/// </summary>
protected ObjectListView ListView {
get { return listView; }
set { listView = value; }
}
private ObjectListView listView;
/// <summary>
/// Gets the row of the cell that is currently being edited
/// </summary>
protected OLVListItem ItemBeingEdited {
get {
return (this.ListView == null || this.ListView.CellEditEventArgs == null) ? null : this.ListView.CellEditEventArgs.ListViewItem;
}
}
/// <summary>
/// Gets the index of the column of the cell that is being edited
/// </summary>
protected int SubItemIndexBeingEdited {
get {
return (this.ListView == null || this.ListView.CellEditEventArgs == null) ? -1 : this.ListView.CellEditEventArgs.SubItemIndex;
}
}
/// <summary>
/// Gets or sets the map that remembers the normal behaviour of keys
/// </summary>
protected IDictionary<Keys, CellEditCharacterBehaviour> CellEditKeyMap {
get {
if (cellEditKeyMap == null)
this.InitializeCellEditKeyMaps();
return cellEditKeyMap;
}
set {
cellEditKeyMap = value;
}
}
private IDictionary<Keys, CellEditCharacterBehaviour> cellEditKeyMap;
/// <summary>
/// Gets or sets the map that remembers the desired behaviour of keys
/// on edge cases.
/// </summary>
protected IDictionary<Keys, CellEditAtEdgeBehaviour> CellEditKeyAtEdgeBehaviourMap {
get {
if (cellEditKeyAtEdgeBehaviourMap == null)
this.InitializeCellEditKeyMaps();
return cellEditKeyAtEdgeBehaviourMap;
}
set {
cellEditKeyAtEdgeBehaviourMap = value;
}
}
private IDictionary<Keys, CellEditAtEdgeBehaviour> cellEditKeyAtEdgeBehaviourMap;
#endregion
#region Initialization
/// <summary>
/// Setup the default key mapping
/// </summary>
protected virtual void InitializeCellEditKeyMaps() {
this.cellEditKeyMap = new Dictionary<Keys, CellEditCharacterBehaviour>();
this.cellEditKeyMap[Keys.Escape] = CellEditCharacterBehaviour.CancelEdit;
this.cellEditKeyMap[Keys.Return] = CellEditCharacterBehaviour.EndEdit;
this.cellEditKeyMap[Keys.Enter] = CellEditCharacterBehaviour.EndEdit;
this.cellEditKeyMap[Keys.Tab] = CellEditCharacterBehaviour.ChangeColumnRight;
this.cellEditKeyMap[Keys.Tab | Keys.Shift] = CellEditCharacterBehaviour.ChangeColumnLeft;
this.cellEditKeyMap[Keys.Left | Keys.Alt] = CellEditCharacterBehaviour.ChangeColumnLeft;
this.cellEditKeyMap[Keys.Right | Keys.Alt] = CellEditCharacterBehaviour.ChangeColumnRight;
this.cellEditKeyMap[Keys.Up | Keys.Alt] = CellEditCharacterBehaviour.ChangeRowUp;
this.cellEditKeyMap[Keys.Down | Keys.Alt] = CellEditCharacterBehaviour.ChangeRowDown;
this.cellEditKeyAtEdgeBehaviourMap = new Dictionary<Keys, CellEditAtEdgeBehaviour>();
this.cellEditKeyAtEdgeBehaviourMap[Keys.Tab] = CellEditAtEdgeBehaviour.Wrap;
this.cellEditKeyAtEdgeBehaviourMap[Keys.Tab | Keys.Shift] = CellEditAtEdgeBehaviour.Wrap;
this.cellEditKeyAtEdgeBehaviourMap[Keys.Left | Keys.Alt] = CellEditAtEdgeBehaviour.Wrap;
this.cellEditKeyAtEdgeBehaviourMap[Keys.Right | Keys.Alt] = CellEditAtEdgeBehaviour.Wrap;
this.cellEditKeyAtEdgeBehaviourMap[Keys.Up | Keys.Alt] = CellEditAtEdgeBehaviour.ChangeColumn;
this.cellEditKeyAtEdgeBehaviourMap[Keys.Down | Keys.Alt] = CellEditAtEdgeBehaviour.ChangeColumn;
}
#endregion
#region Command handling
/// <summary>
/// Handle the end edit command
/// </summary>
protected virtual void HandleEndEdit() {
this.ListView.PossibleFinishCellEditing();
}
/// <summary>
/// Handle the cancel edit command
/// </summary>
protected virtual void HandleCancelEdit() {
this.ListView.CancelCellEdit();
}
/// <summary>
/// Placeholder that subclasses can override to handle any custom verbs
/// </summary>
/// <param name="keyData"></param>
/// <param name="behaviour"></param>
/// <returns></returns>
protected virtual bool HandleCustomVerb(Keys keyData, CellEditCharacterBehaviour behaviour) {
return false;
}
/// <summary>
/// Handle a change row command
/// </summary>
/// <param name="keyData"></param>
/// <param name="behaviour"></param>
protected virtual void HandleRowChange(Keys keyData, CellEditCharacterBehaviour behaviour) {
// If we couldn't finish editing the current cell, don't try to move it
if (!this.ListView.PossibleFinishCellEditing())
return;
OLVListItem olvi = this.ItemBeingEdited;
int subItemIndex = this.SubItemIndexBeingEdited;
bool isGoingUp = behaviour == CellEditCharacterBehaviour.ChangeRowUp;
// Try to find a row above (or below) the currently edited cell
// If we find one, start editing it and we're done.
OLVListItem adjacentOlvi = this.GetAdjacentItemOrNull(olvi, isGoingUp);
if (adjacentOlvi != null) {
this.StartCellEditIfDifferent(adjacentOlvi, subItemIndex);
return;
}
// There is no adjacent row in the direction we want, so we must be on an edge.
CellEditAtEdgeBehaviour atEdgeBehaviour;
if (!this.CellEditKeyAtEdgeBehaviourMap.TryGetValue(keyData, out atEdgeBehaviour))
atEdgeBehaviour = CellEditAtEdgeBehaviour.Wrap;
switch (atEdgeBehaviour) {
case CellEditAtEdgeBehaviour.Ignore:
break;
case CellEditAtEdgeBehaviour.EndEdit:
this.ListView.PossibleFinishCellEditing();
break;
case CellEditAtEdgeBehaviour.Wrap:
adjacentOlvi = this.GetAdjacentItemOrNull(null, isGoingUp);
this.StartCellEditIfDifferent(adjacentOlvi, subItemIndex);
break;
case CellEditAtEdgeBehaviour.ChangeColumn:
// Figure out the next editable column
List<OLVColumn> editableColumnsInDisplayOrder = this.EditableColumnsInDisplayOrder;
int displayIndex = Math.Max(0, editableColumnsInDisplayOrder.IndexOf(this.ListView.GetColumn(subItemIndex)));
if (isGoingUp)
displayIndex = (editableColumnsInDisplayOrder.Count + displayIndex - 1) % editableColumnsInDisplayOrder.Count;
else
displayIndex = (displayIndex + 1) % editableColumnsInDisplayOrder.Count;
subItemIndex = editableColumnsInDisplayOrder[displayIndex].Index;
// Wrap to the next row and start the cell edit
adjacentOlvi = this.GetAdjacentItemOrNull(null, isGoingUp);
this.StartCellEditIfDifferent(adjacentOlvi, subItemIndex);
break;
}
}
/// <summary>
/// Handle a change column command
/// </summary>
/// <param name="keyData"></param>
/// <param name="behaviour"></param>
protected virtual void HandleColumnChange(Keys keyData, CellEditCharacterBehaviour behaviour)
{
// If we couldn't finish editing the current cell, don't try to move it
if (!this.ListView.PossibleFinishCellEditing())
return;
// Changing columns only works in details mode
if (this.ListView.View != View.Details)
return;
List<OLVColumn> editableColumns = this.EditableColumnsInDisplayOrder;
OLVListItem olvi = this.ItemBeingEdited;
int displayIndex = Math.Max(0,
editableColumns.IndexOf(this.ListView.GetColumn(this.SubItemIndexBeingEdited)));
bool isGoingLeft = behaviour == CellEditCharacterBehaviour.ChangeColumnLeft;
// Are we trying to continue past one of the edges?
if ((isGoingLeft && displayIndex == 0) ||
(!isGoingLeft && displayIndex == editableColumns.Count - 1))
{
// Yes, so figure out our at edge behaviour
CellEditAtEdgeBehaviour atEdgeBehaviour;
if (!this.CellEditKeyAtEdgeBehaviourMap.TryGetValue(keyData, out atEdgeBehaviour))
atEdgeBehaviour = CellEditAtEdgeBehaviour.Wrap;
switch (atEdgeBehaviour)
{
case CellEditAtEdgeBehaviour.Ignore:
return;
case CellEditAtEdgeBehaviour.EndEdit:
this.HandleEndEdit();
return;
case CellEditAtEdgeBehaviour.ChangeRow:
case CellEditAtEdgeBehaviour.Wrap:
if (atEdgeBehaviour == CellEditAtEdgeBehaviour.ChangeRow)
olvi = GetAdjacentItem(olvi, isGoingLeft && displayIndex == 0);
if (isGoingLeft)
displayIndex = editableColumns.Count - 1;
else
displayIndex = 0;
break;
}
}
else
{
if (isGoingLeft)
displayIndex -= 1;
else
displayIndex += 1;
}
int subItemIndex = editableColumns[displayIndex].Index;
this.StartCellEditIfDifferent(olvi, subItemIndex);
}
#endregion
#region Utilities
/// <summary>
/// Start editing the indicated cell if that cell is not already being edited
/// </summary>
/// <param name="olvi">The row to edit</param>
/// <param name="subItemIndex">The cell within that row to edit</param>
protected void StartCellEditIfDifferent(OLVListItem olvi, int subItemIndex) {
if (this.ItemBeingEdited == olvi && this.SubItemIndexBeingEdited == subItemIndex)
return;
this.ListView.EnsureVisible(olvi.Index);
this.ListView.StartCellEdit(olvi, subItemIndex);
}
/// <summary>
/// Gets the adjacent item to the given item in the given direction.
/// If that item is disabled, continue in that direction until an enabled item is found.
/// </summary>
/// <param name="olvi">The row whose neighbour is sought</param>
/// <param name="up">The direction of the adjacentness</param>
/// <returns>An OLVListView adjacent to the given item, or null if there are no more enabled items in that direction.</returns>
protected OLVListItem GetAdjacentItemOrNull(OLVListItem olvi, bool up) {
OLVListItem item = up ? this.ListView.GetPreviousItem(olvi) : this.ListView.GetNextItem(olvi);
while (item != null && !item.Enabled)
item = up ? this.ListView.GetPreviousItem(item) : this.ListView.GetNextItem(item);
return item;
}
/// <summary>
/// Gets the adjacent item to the given item in the given direction, wrapping if needed.
/// </summary>
/// <param name="olvi">The row whose neighbour is sought</param>
/// <param name="up">The direction of the adjacentness</param>
/// <returns>An OLVListView adjacent to the given item, or null if there are no more items in that direction.</returns>
protected OLVListItem GetAdjacentItem(OLVListItem olvi, bool up) {
return this.GetAdjacentItemOrNull(olvi, up) ?? this.GetAdjacentItemOrNull(null, up);
}
/// <summary>
/// Gets a collection of columns that are editable in the order they are shown to the user
/// </summary>
protected List<OLVColumn> EditableColumnsInDisplayOrder {
get {
List<OLVColumn> editableColumnsInDisplayOrder = new List<OLVColumn>();
foreach (OLVColumn x in this.ListView.ColumnsInDisplayOrder)
if (x.IsEditable)
editableColumnsInDisplayOrder.Add(x);
return editableColumnsInDisplayOrder;
}
}
#endregion
}
}

View File

@@ -0,0 +1,284 @@
/*
* CellEditors - Several slightly modified controls that are used as celleditors within ObjectListView.
*
* Author: Phillip Piper
* Date: 20/10/2008 5:15 PM
*
* Change log:
* v2.6
* 2012-08-02 JPP - Make most editors public so they can be reused/subclassed
* v2.3
* 2009-08-13 JPP - Standardized code formatting
* v2.2.1
* 2008-01-18 JPP - Added special handling for enums
* 2008-01-16 JPP - Added EditorRegistry
* v2.0.1
* 2008-10-20 JPP - Separated from ObjectListView.cs
*
* Copyright (C) 2006-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
namespace BrightIdeasSoftware
{
/// <summary>
/// These items allow combo boxes to remember a value and its description.
/// </summary>
/// <remarks>
///
/// </remarks>
/// <param name="key"></param>
/// <param name="description"></param>
public class ComboBoxItem(Object key, String description)
{
private readonly String description = description;
/// <summary>
///
/// </summary>
public Object Key {
get { return key; }
}
private readonly Object key = key;
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>
/// A string that represents the current object.
/// </returns>
/// <filterpriority>2</filterpriority>
public override string ToString() {
return this.description;
}
}
//-----------------------------------------------------------------------
// Cell editors
// These classes are simple cell editors that make it easier to get and set
// the value that the control is showing.
// In many cases, you can intercept the CellEditStarting event to
// change the characteristics of the editor. For example, changing
// the acceptable range for a numeric editor or changing the strings
// that respresent true and false values for a boolean editor.
/// <summary>
/// This editor shows and auto completes values from the given listview column.
/// </summary>
[ToolboxItem(false)]
public class AutoCompleteCellEditor : ComboBox
{
/// <summary>
/// Create an AutoCompleteCellEditor
/// </summary>
/// <param name="lv"></param>
/// <param name="column"></param>
public AutoCompleteCellEditor(ObjectListView lv, OLVColumn column) {
this.DropDownStyle = ComboBoxStyle.DropDown;
Dictionary<String, bool> alreadySeen = new Dictionary<string, bool>();
for (int i = 0; i < Math.Min(lv.GetItemCount(), 1000); i++) {
String str = column.GetStringValue(lv.GetModelObject(i));
if (!alreadySeen.ContainsKey(str)) {
this.Items.Add(str);
alreadySeen[str] = true;
}
}
this.Sorted = true;
this.AutoCompleteSource = AutoCompleteSource.ListItems;
this.AutoCompleteMode = AutoCompleteMode.Append;
}
}
/// <summary>
/// This combo box is specialised to allow editing of an enum.
/// </summary>
[ToolboxItem(false)]
public class EnumCellEditor : ComboBox
{
/// <summary>
///
/// </summary>
/// <param name="type"></param>
public EnumCellEditor(Type type) {
this.DropDownStyle = ComboBoxStyle.DropDownList;
this.ValueMember = "Key";
ArrayList values = new ArrayList();
foreach (object value in Enum.GetValues(type))
values.Add(new ComboBoxItem(value, Enum.GetName(type, value)));
this.DataSource = values;
}
}
/// <summary>
/// This editor simply shows and edits integer values.
/// </summary>
[ToolboxItem(false)]
public class IntUpDown : NumericUpDown
{
/// <summary>
///
/// </summary>
public IntUpDown() {
this.DecimalPlaces = 0;
this.Minimum = -9999999;
this.Maximum = 9999999;
}
/// <summary>
/// Gets or sets the value shown by this editor
/// </summary>
new public int Value {
get { return Decimal.ToInt32(base.Value); }
set { base.Value = new Decimal(value); }
}
}
/// <summary>
/// This editor simply shows and edits unsigned integer values.
/// </summary>
/// <remarks>This class can't be made public because unsigned int is not a
/// CLS-compliant type. If you want to use, just copy the code to this class
/// into your project and use it from there.</remarks>
[ToolboxItem(false)]
internal class UintUpDown : NumericUpDown
{
public UintUpDown() {
this.DecimalPlaces = 0;
this.Minimum = 0;
this.Maximum = 9999999;
}
new public uint Value {
get { return Decimal.ToUInt32(base.Value); }
set { base.Value = new Decimal(value); }
}
}
/// <summary>
/// This editor simply shows and edits boolean values.
/// </summary>
[ToolboxItem(false)]
public class BooleanCellEditor : ComboBox
{
/// <summary>
///
/// </summary>
public BooleanCellEditor() {
this.DropDownStyle = ComboBoxStyle.DropDownList;
this.ValueMember = "Key";
ArrayList values = new ArrayList();
values.Add(new ComboBoxItem(false, "False"));
values.Add(new ComboBoxItem(true, "True"));
this.DataSource = values;
}
}
/// <summary>
/// This editor simply shows and edits boolean values using a checkbox
/// </summary>
[ToolboxItem(false)]
public class BooleanCellEditor2 : CheckBox
{
/// <summary>
/// Gets or sets the value shown by this editor
/// </summary>
public bool? Value {
get {
switch (this.CheckState) {
case CheckState.Checked: return true;
case CheckState.Indeterminate: return null;
case CheckState.Unchecked:
default: return false;
}
}
set {
if (value.HasValue)
this.CheckState = value.Value ? CheckState.Checked : CheckState.Unchecked;
else
this.CheckState = CheckState.Indeterminate;
}
}
/// <summary>
/// Gets or sets how the checkbox will be aligned
/// </summary>
public new HorizontalAlignment TextAlign {
get {
switch (this.CheckAlign) {
case ContentAlignment.MiddleRight: return HorizontalAlignment.Right;
case ContentAlignment.MiddleCenter: return HorizontalAlignment.Center;
case ContentAlignment.MiddleLeft:
default: return HorizontalAlignment.Left;
}
}
set {
switch (value) {
case HorizontalAlignment.Left:
this.CheckAlign = ContentAlignment.MiddleLeft;
break;
case HorizontalAlignment.Center:
this.CheckAlign = ContentAlignment.MiddleCenter;
break;
case HorizontalAlignment.Right:
this.CheckAlign = ContentAlignment.MiddleRight;
break;
}
}
}
}
/// <summary>
/// This editor simply shows and edits floating point values.
/// </summary>
/// <remarks>You can intercept the CellEditStarting event if you want
/// to change the characteristics of the editor. For example, by increasing
/// the number of decimal places.</remarks>
[ToolboxItem(false)]
public class FloatCellEditor : NumericUpDown
{
/// <summary>
///
/// </summary>
public FloatCellEditor() {
this.DecimalPlaces = 2;
this.Minimum = -9999999;
this.Maximum = 9999999;
}
/// <summary>
/// Gets or sets the value shown by this editor
/// </summary>
new public double Value {
get { return Convert.ToDouble(base.Value); }
set { base.Value = Convert.ToDecimal(value); }
}
}
}

View File

@@ -0,0 +1,213 @@
/*
* EditorRegistry - A registry mapping types to cell editors.
*
* Author: Phillip Piper
* Date: 6-March-2011 7:53 am
*
* Change log:
* 2011-03-31 JPP - Use OLVColumn.DataType if the value to be edited is null
* 2011-03-06 JPP - Separated from CellEditors.cs
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
namespace BrightIdeasSoftware {
/// <summary>
/// A delegate that creates an editor for the given value
/// </summary>
/// <param name="model">The model from which that value came</param>
/// <param name="column">The column for which the editor is being created</param>
/// <param name="value">A representative value of the type to be edited. This value may not be the exact
/// value for the column/model combination. It could be simply representative of
/// the appropriate type of value.</param>
/// <returns>A control which can edit the given value</returns>
public delegate Control EditorCreatorDelegate(Object model, OLVColumn column, Object value);
/// <summary>
/// An editor registry gives a way to decide what cell editor should be used to edit
/// the value of a cell. Programmers can register non-standard types and the control that
/// should be used to edit instances of that type.
/// </summary>
/// <remarks>
/// <para>All ObjectListViews share the same editor registry.</para>
/// </remarks>
public class EditorRegistry {
#region Initializing
/// <summary>
/// Create an EditorRegistry
/// </summary>
public EditorRegistry() {
this.InitializeStandardTypes();
}
private void InitializeStandardTypes() {
this.Register(typeof(Boolean), typeof(BooleanCellEditor));
this.Register(typeof(Int16), typeof(IntUpDown));
this.Register(typeof(Int32), typeof(IntUpDown));
this.Register(typeof(Int64), typeof(IntUpDown));
this.Register(typeof(UInt16), typeof(UintUpDown));
this.Register(typeof(UInt32), typeof(UintUpDown));
this.Register(typeof(UInt64), typeof(UintUpDown));
this.Register(typeof(Single), typeof(FloatCellEditor));
this.Register(typeof(Double), typeof(FloatCellEditor));
this.Register(typeof(DateTime), delegate(Object model, OLVColumn column, Object value) {
DateTimePicker c = new DateTimePicker();
c.Format = DateTimePickerFormat.Short;
return c;
});
this.Register(typeof(Boolean), delegate(Object model, OLVColumn column, Object value) {
CheckBox c = new BooleanCellEditor2();
c.ThreeState = column.TriStateCheckBoxes;
return c;
});
}
#endregion
#region Registering
/// <summary>
/// Register that values of 'type' should be edited by instances of 'controlType'.
/// </summary>
/// <param name="type">The type of value to be edited</param>
/// <param name="controlType">The type of the Control that will edit values of 'type'</param>
/// <example>
/// ObjectListView.EditorRegistry.Register(typeof(Color), typeof(MySpecialColorEditor));
/// </example>
public void Register(Type type, Type controlType) {
this.Register(type, delegate(Object model, OLVColumn column, Object value) {
return controlType.InvokeMember("", BindingFlags.CreateInstance, null, null, null) as Control;
});
}
/// <summary>
/// Register the given delegate so that it is called to create editors
/// for values of the given type
/// </summary>
/// <param name="type">The type of value to be edited</param>
/// <param name="creator">The delegate that will create a control that can edit values of 'type'</param>
/// <example>
/// ObjectListView.EditorRegistry.Register(typeof(Color), CreateColorEditor);
/// ...
/// public Control CreateColorEditor(Object model, OLVColumn column, Object value)
/// {
/// return new MySpecialColorEditor();
/// }
/// </example>
public void Register(Type type, EditorCreatorDelegate creator) {
this.creatorMap[type] = creator;
}
/// <summary>
/// Register a delegate that will be called to create an editor for values
/// that have not been handled.
/// </summary>
/// <param name="creator">The delegate that will create a editor for all other types</param>
public void RegisterDefault(EditorCreatorDelegate creator) {
this.defaultCreator = creator;
}
/// <summary>
/// Register a delegate that will be given a chance to create a control
/// before any other option is considered.
/// </summary>
/// <param name="creator">The delegate that will create a control</param>
public void RegisterFirstChance(EditorCreatorDelegate creator) {
this.firstChanceCreator = creator;
}
/// <summary>
/// Remove the registered handler for the given type
/// </summary>
/// <remarks>Does nothing if the given type doesn't exist</remarks>
/// <param name="type">The type whose registration is to be removed</param>
public void Unregister(Type type) {
if (this.creatorMap.ContainsKey(type))
this.creatorMap.Remove(type);
}
#endregion
#region Accessing
/// <summary>
/// Create and return an editor that is appropriate for the given value.
/// Return null if no appropriate editor can be found.
/// </summary>
/// <param name="model">The model involved</param>
/// <param name="column">The column to be edited</param>
/// <param name="value">The value to be edited. This value may not be the exact
/// value for the column/model combination. It could be simply representative of
/// the appropriate type of value.</param>
/// <returns>A Control that can edit the given type of values</returns>
public Control GetEditor(Object model, OLVColumn column, Object value) {
Control editor;
// Give the first chance delegate a chance to decide
if (this.firstChanceCreator != null) {
editor = this.firstChanceCreator(model, column, value);
if (editor != null)
return editor;
}
// Try to find a creator based on the type of the value (or the column)
Type type = value == null ? column.DataType : value.GetType();
if (type != null && this.creatorMap.ContainsKey(type)) {
editor = this.creatorMap[type](model, column, value);
if (editor != null)
return editor;
}
// Enums without other processing get a special editor
if (value != null && value.GetType().IsEnum)
return this.CreateEnumEditor(value.GetType());
// Give any default creator a final chance
if (this.defaultCreator != null)
return this.defaultCreator(model, column, value);
return null;
}
/// <summary>
/// Create and return an editor that will edit values of the given type
/// </summary>
/// <param name="type">A enum type</param>
protected Control CreateEnumEditor(Type type) {
return new EnumCellEditor(type);
}
#endregion
#region Private variables
private EditorCreatorDelegate firstChanceCreator;
private EditorCreatorDelegate defaultCreator;
private Dictionary<Type, EditorCreatorDelegate> creatorMap = new Dictionary<Type, EditorCreatorDelegate>();
#endregion
}
}

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<Dictionary>
<Words>
<Recognized>
<Word>br</Word>
<Word>Canceled</Word>
<Word>Center</Word>
<Word>Color</Word>
<Word>Colors</Word>
<Word>f</Word>
<Word>fmt</Word>
<Word>g</Word>
<Word>gdi</Word>
<Word>hti</Word>
<Word>i</Word>
<Word>lightbox</Word>
<Word>lv</Word>
<Word>lvi</Word>
<Word>lvsi</Word>
<Word>m</Word>
<Word>multi</Word>
<Word>Munger</Word>
<Word>n</Word>
<Word>olv</Word>
<Word>olvi</Word>
<Word>p</Word>
<Word>parms</Word>
<Word>r</Word>
<Word>Renderer</Word>
<Word>s</Word>
<Word>SubItem</Word>
<Word>Unapply</Word>
<Word>Unpause</Word>
<Word>x</Word>
<Word>y</Word>
</Recognized>
<Deprecated>
<Term PreferredAlternate="EnterpriseServices">ComPlus</Term>
</Deprecated>
</Words>
<Acronyms>
<CasingExceptions>
<Acronym>OLV</Acronym>
</CasingExceptions>
</Acronyms>
</Dictionary>

View File

@@ -0,0 +1,236 @@
/*
* DataListView - A data-bindable listview
*
* Author: Phillip Piper
* Date: 27/09/2008 9:15 AM
*
* Change log:
* 2015-02-02 JPP - Made Unfreezing more efficient by removing a redundant BuildList() call
* v2.6
* 2011-02-27 JPP - Moved most of the logic to DataSourceAdapter (where it
* can be used by FastDataListView too)
* v2.3
* 2009-01-18 JPP - Boolean columns are now handled as checkboxes
* - Auto-generated columns would fail if the data source was
* reseated, even to the same data source
* v2.0.1
* 2009-01-07 JPP - Made all public and protected methods virtual
* 2008-10-03 JPP - Separated from ObjectListView.cs
*
* Copyright (C) 2006-2015 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing.Design;
using System.Windows.Forms;
namespace BrightIdeasSoftware
{
/// <summary>
/// A DataListView is a ListView that can be bound to a datasource (which would normally be a DataTable or DataView).
/// </summary>
/// <remarks>
/// <para>This listview keeps itself in sync with its source datatable by listening for change events.</para>
/// <para>The DataListView will automatically create columns to show all of the data source's columns/properties, if there is not already
/// a column showing that property. This allows you to define one or two columns in the designer and then have the others generated automatically.
/// If you don't want any column to be auto generated, set <see cref="AutoGenerateColumns"/> to false.
/// These generated columns will be only the simplest view of the world, and would look more interesting with a few delegates installed.</para>
/// <para>This listview will also automatically generate missing aspect getters to fetch the values from the data view.</para>
/// <para>Changing data sources is possible, but error prone. Before changing data sources, the programmer is responsible for modifying/resetting
/// the column collection to be valid for the new data source.</para>
/// <para>Internally, a CurrencyManager controls keeping the data source in-sync with other users of the data source (as per normal .NET
/// behavior). This means that the model objects in the DataListView are DataRowView objects. If you write your own AspectGetters/Setters,
/// they will be given DataRowView objects.</para>
/// </remarks>
public class DataListView : ObjectListView
{
#region Life and death
/// <summary>
/// Make a DataListView
/// </summary>
public DataListView()
{
this.Adapter = new DataSourceAdapter(this);
}
protected override void Dispose(bool disposing) {
this.Adapter.Dispose();
base.Dispose(disposing);
}
#endregion
#region Public Properties
/// <summary>
/// Gets or sets whether or not columns will be automatically generated to show the
/// columns when the DataSource is set.
/// </summary>
/// <remarks>This must be set before the DataSource is set. It has no effect afterwards.</remarks>
[Category("Data"),
Description("Should the control automatically generate columns from the DataSource"),
DefaultValue(true)]
public bool AutoGenerateColumns {
get { return this.Adapter.AutoGenerateColumns; }
set { this.Adapter.AutoGenerateColumns = value; }
}
/// <summary>
/// Get or set the DataSource that will be displayed in this list view.
/// </summary>
/// <remarks>The DataSource should implement either <see cref="IList"/>, <see cref="IBindingList"/>,
/// or <see cref="IListSource"/>. Some common examples are the following types of objects:
/// <list type="unordered">
/// <item><description><see cref="DataView"/></description></item>
/// <item><description><see cref="DataTable"/></description></item>
/// <item><description><see cref="DataSet"/></description></item>
/// <item><description><see cref="DataViewManager"/></description></item>
/// <item><description><see cref="BindingSource"/></description></item>
/// </list>
/// <para>When binding to a list container (i.e. one that implements the
/// <see cref="IListSource"/> interface, such as <see cref="DataSet"/>)
/// you must also set the <see cref="DataMember"/> property in order
/// to identify which particular list you would like to display. You
/// may also set the <see cref="DataMember"/> property even when
/// DataSource refers to a list, since <see cref="DataMember"/> can
/// also be used to navigate relations between lists.</para>
/// <para>When a DataSource is set, the control will create OLVColumns to show any
/// data source columns that are not already shown.</para>
/// <para>If the DataSource is changed, you will have to remove any previously
/// created columns, since they will be configured for the previous DataSource.
/// <see cref="ObjectListView.Reset()"/>.</para>
/// </remarks>
[Category("Data"),
TypeConverter("System.Windows.Forms.Design.DataSourceConverter, System.Design")]
public virtual Object DataSource
{
get { return this.Adapter.DataSource; }
set { this.Adapter.DataSource = value; }
}
/// <summary>
/// Gets or sets the name of the list or table in the data source for which the DataListView is displaying data.
/// </summary>
/// <remarks>If the data source is not a DataSet or DataViewManager, this property has no effect</remarks>
[Category("Data"),
Editor("System.Windows.Forms.Design.DataMemberListEditor, System.Design", typeof(UITypeEditor)),
DefaultValue("")]
public virtual string DataMember
{
get { return this.Adapter.DataMember; }
set { this.Adapter.DataMember = value; }
}
#endregion
#region Implementation properties
/// <summary>
/// Gets or sets the DataSourceAdaptor that does the bulk of the work needed
/// for data binding.
/// </summary>
/// <remarks>
/// Adaptors cannot be shared between controls. Each DataListView needs its own adapter.
/// </remarks>
protected DataSourceAdapter Adapter {
get {
Debug.Assert(adapter != null, "Data adapter should not be null");
return adapter;
}
set { adapter = value; }
}
private DataSourceAdapter adapter;
#endregion
#region Object manipulations
/// <summary>
/// Add the given collection of model objects to this control.
/// </summary>
/// <param name="modelObjects">A collection of model objects</param>
/// <remarks>This is a no-op for data lists, since the data
/// is controlled by the DataSource. Manipulate the data source
/// rather than this view of the data source.</remarks>
public override void AddObjects(ICollection modelObjects)
{
}
/// <summary>
/// Insert the given collection of objects before the given position
/// </summary>
/// <param name="index">Where to insert the objects</param>
/// <param name="modelObjects">The objects to be inserted</param>
/// <remarks>This is a no-op for data lists, since the data
/// is controlled by the DataSource. Manipulate the data source
/// rather than this view of the data source.</remarks>
public override void InsertObjects(int index, ICollection modelObjects) {
}
/// <summary>
/// Remove the given collection of model objects from this control.
/// </summary>
/// <remarks>This is a no-op for data lists, since the data
/// is controlled by the DataSource. Manipulate the data source
/// rather than this view of the data source.</remarks>
public override void RemoveObjects(ICollection modelObjects)
{
}
#endregion
#region Event Handlers
/// <summary>
/// Change the Unfreeze behaviour
/// </summary>
protected override void DoUnfreeze() {
// Copied from base method, but we don't need to BuildList() since we know that our
// data adaptor is going to do that immediately after this method exits.
this.EndUpdate();
this.ResizeFreeSpaceFillingColumns();
// this.BuildList();
}
/// <summary>
/// Handles parent binding context changes
/// </summary>
/// <param name="e">Unused EventArgs.</param>
protected override void OnParentBindingContextChanged(EventArgs e)
{
base.OnParentBindingContextChanged(e);
// BindingContext is an ambient property - by default it simply picks
// up the parent control's context (unless something has explicitly
// given us our own). So we must respond to changes in our parent's
// binding context in the same way we would changes to our own
// binding context.
// THINK: Do we need to forward this to the adapter?
}
#endregion
}
}

View File

@@ -0,0 +1,240 @@
/*
* DataTreeListView - A data bindable TreeListView
*
* Author: Phillip Piper
* Date: 05/05/2012 3:26 PM
*
* Change log:
* 2012-05-05 JPP Initial version
*
* TO DO:
*
* Copyright (C) 2012 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing.Design;
using System.Windows.Forms;
namespace BrightIdeasSoftware
{
/// <summary>
/// A DataTreeListView is a TreeListView that calculates its hierarchy based on
/// information in the data source.
/// </summary>
/// <remarks>
/// <para>Like a <see cref="DataListView"/>, a DataTreeListView sources all its information
/// from a combination of <see cref="DataSource"/> and <see cref="DataMember"/>.
/// <see cref="DataSource"/> can be a DataTable, DataSet,
/// or anything that implements <see cref="IList"/>.
/// </para>
/// <para>
/// To function properly, the DataTreeListView requires:
/// <list type="bullet">
/// <item>the table to have a column which holds a unique for the row. The name of this column must be set in <see cref="KeyAspectName"/>.</item>
/// <item>the table to have a column which holds id of the hierarchical parent of the row. The name of this column must be set in <see cref="ParentKeyAspectName"/>.</item>
/// <item>a value which identifies which rows are the roots of the tree (<see cref="RootKeyValue"/>).</item>
/// </list>
/// The hierarchy structure is determined finding all the rows where the parent key is equal to <see cref="RootKeyValue"/>. These rows
/// become the root objects of the hierarchy.
/// </para>
/// <para>Like a TreeListView, the hierarchy must not contain cycles. Bad things will happen if the data is cyclic.</para>
/// </remarks>
public partial class DataTreeListView : TreeListView
{
#region Public Properties
/// <summary>
/// Gets or sets whether or not columns will be automatically generated to show the
/// columns when the DataSource is set.
/// </summary>
/// <remarks>This must be set before the DataSource is set. It has no effect afterwards.</remarks>
[Category("Data"),
Description("Should the control automatically generate columns from the DataSource"),
DefaultValue(true)]
public bool AutoGenerateColumns
{
get { return this.Adapter.AutoGenerateColumns; }
set { this.Adapter.AutoGenerateColumns = value; }
}
/// <summary>
/// Get or set the DataSource that will be displayed in this list view.
/// </summary>
/// <remarks>The DataSource should implement either <see cref="IList"/>, <see cref="IBindingList"/>,
/// or <see cref="IListSource"/>. Some common examples are the following types of objects:
/// <list type="unordered">
/// <item><description><see cref="DataView"/></description></item>
/// <item><description><see cref="DataTable"/></description></item>
/// <item><description><see cref="DataSet"/></description></item>
/// <item><description><see cref="DataViewManager"/></description></item>
/// <item><description><see cref="BindingSource"/></description></item>
/// </list>
/// <para>When binding to a list container (i.e. one that implements the
/// <see cref="IListSource"/> interface, such as <see cref="DataSet"/>)
/// you must also set the <see cref="DataMember"/> property in order
/// to identify which particular list you would like to display. You
/// may also set the <see cref="DataMember"/> property even when
/// DataSource refers to a list, since <see cref="DataMember"/> can
/// also be used to navigate relations between lists.</para>
/// </remarks>
[Category("Data"),
TypeConverter("System.Windows.Forms.Design.DataSourceConverter, System.Design")]
public virtual Object DataSource {
get { return this.Adapter.DataSource; }
set { this.Adapter.DataSource = value; }
}
/// <summary>
/// Gets or sets the name of the list or table in the data source for which the DataListView is displaying data.
/// </summary>
/// <remarks>If the data source is not a DataSet or DataViewManager, this property has no effect</remarks>
[Category("Data"),
Editor("System.Windows.Forms.Design.DataMemberListEditor, System.Design", typeof(UITypeEditor)),
DefaultValue("")]
public virtual string DataMember {
get { return this.Adapter.DataMember; }
set { this.Adapter.DataMember = value; }
}
/// <summary>
/// Gets or sets the name of the property/column that uniquely identifies each row.
/// </summary>
/// <remarks>
/// <para>
/// The value contained by this column must be unique across all rows
/// in the data source. Odd and unpredictable things will happen if two
/// rows have the same id.
/// </para>
/// <para>Null cannot be a valid key value.</para>
/// </remarks>
[Category("Data"),
Description("The name of the property/column that holds the key of a row"),
DefaultValue(null)]
public virtual string KeyAspectName {
get { return this.Adapter.KeyAspectName; }
set { this.Adapter.KeyAspectName = value; }
}
/// <summary>
/// Gets or sets the name of the property/column that contains the key of
/// the parent of a row.
/// </summary>
/// <remarks>
/// <para>
/// The test condition for deciding if one row is the parent of another is functionally
/// equivilent to this:
/// <code>
/// Object.Equals(candidateParentRow[this.KeyAspectName], row[this.ParentKeyAspectName])
/// </code>
/// </para>
/// <para>Unlike key value, parent keys can be null but a null parent key can only be used
/// to identify root objects.</para>
/// </remarks>
[Category("Data"),
Description("The name of the property/column that holds the key of the parent of a row"),
DefaultValue(null)]
public virtual string ParentKeyAspectName {
get { return this.Adapter.ParentKeyAspectName; }
set { this.Adapter.ParentKeyAspectName = value; }
}
/// <summary>
/// Gets or sets the value that identifies a row as a root object.
/// When the ParentKey of a row equals the RootKeyValue, that row will
/// be treated as root of the TreeListView.
/// </summary>
/// <remarks>
/// <para>
/// The test condition for deciding a root object is functionally
/// equivilent to this:
/// <code>
/// Object.Equals(candidateRow[this.ParentKeyAspectName], this.RootKeyValue)
/// </code>
/// </para>
/// <para>The RootKeyValue can be null. Actually, it can be any value that can
/// be compared for equality against a basic type.</para>
/// <para>If this is set to the wrong value (i.e. to a value that no row
/// has in the parent id column), the list will be empty.</para>
/// </remarks>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public virtual object RootKeyValue {
get { return this.Adapter.RootKeyValue; }
set { this.Adapter.RootKeyValue = value; }
}
/// <summary>
/// Gets or sets the value that identifies a row as a root object.
/// <see cref="RootKeyValue"/>. The RootKeyValue can be of any type,
/// but the IDE cannot sensibly represent a value of any type,
/// so this is a typed wrapper around that property.
/// </summary>
/// <remarks>
/// If you want the root value to be something other than a string,
/// you will have set it yourself.
/// </remarks>
[Category("Data"),
Description("The parent id value that identifies a row as a root object"),
DefaultValue(null)]
public virtual string RootKeyValueString {
get { return Convert.ToString(this.Adapter.RootKeyValue); }
set { this.Adapter.RootKeyValue = value; }
}
/// <summary>
/// Gets or sets whether or not the key columns (id and parent id) should
/// be shown to the user.
/// </summary>
/// <remarks>This must be set before the DataSource is set. It has no effect
/// afterwards.</remarks>
[Category("Data"),
Description("Should the keys columns (id and parent id) be shown to the user?"),
DefaultValue(true)]
public virtual bool ShowKeyColumns {
get { return this.Adapter.ShowKeyColumns; }
set { this.Adapter.ShowKeyColumns = value; }
}
#endregion
#region Implementation properties
/// <summary>
/// Gets or sets the DataSourceAdaptor that does the bulk of the work needed
/// for data binding.
/// </summary>
protected TreeDataSourceAdapter Adapter {
get {
if (this.adapter == null)
this.adapter = new TreeDataSourceAdapter(this);
return adapter;
}
set { adapter = value; }
}
private TreeDataSourceAdapter adapter;
#endregion
}
}

View File

@@ -0,0 +1,219 @@
/*
* DragSource.cs - Add drag source functionality to an ObjectListView
*
* Author: Phillip Piper
* Date: 2009-03-17 5:15 PM
*
* Change log:
* 2011-03-29 JPP - Separate OLVDataObject.cs
* v2.3
* 2009-07-06 JPP - Make sure Link is acceptable as an drop effect by default
* (since MS didn't make it part of the 'All' value)
* v2.2
* 2009-04-15 JPP - Separated DragSource.cs into DropSink.cs
* 2009-03-17 JPP - Initial version
*
* Copyright (C) 2009-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
namespace BrightIdeasSoftware
{
/// <summary>
/// An IDragSource controls how drag out from the ObjectListView will behave
/// </summary>
public interface IDragSource
{
/// <summary>
/// A drag operation is beginning. Return the data object that will be used
/// for data transfer. Return null to prevent the drag from starting. The data
/// object will normally include all the selected objects.
/// </summary>
/// <remarks>
/// The returned object is later passed to the GetAllowedEffect() and EndDrag()
/// methods.
/// </remarks>
/// <param name="olv">What ObjectListView is being dragged from.</param>
/// <param name="button">Which mouse button is down?</param>
/// <param name="item">What item was directly dragged by the user? There may be more than just this
/// item selected.</param>
/// <returns>The data object that will be used for data transfer. This will often be a subclass
/// of DataObject, but does not need to be.</returns>
Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item);
/// <summary>
/// What operations are possible for this drag? This controls the icon shown during the drag
/// </summary>
/// <param name="dragObject">The data object returned by StartDrag()</param>
/// <returns>A combination of DragDropEffects flags</returns>
DragDropEffects GetAllowedEffects(Object dragObject);
/// <summary>
/// The drag operation is complete. Do whatever is necessary to complete the action.
/// </summary>
/// <param name="dragObject">The data object returned by StartDrag()</param>
/// <param name="effect">The value returned from GetAllowedEffects()</param>
void EndDrag(Object dragObject, DragDropEffects effect);
}
/// <summary>
/// A do-nothing implementation of IDragSource that can be safely subclassed.
/// </summary>
public class AbstractDragSource : IDragSource
{
#region IDragSource Members
/// <summary>
/// See IDragSource documentation
/// </summary>
/// <param name="olv"></param>
/// <param name="button"></param>
/// <param name="item"></param>
/// <returns></returns>
public virtual Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item) {
return null;
}
/// <summary>
/// See IDragSource documentation
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public virtual DragDropEffects GetAllowedEffects(Object data) {
return DragDropEffects.None;
}
/// <summary>
/// See IDragSource documentation
/// </summary>
/// <param name="dragObject"></param>
/// <param name="effect"></param>
public virtual void EndDrag(Object dragObject, DragDropEffects effect) {
}
#endregion
}
/// <summary>
/// A reasonable implementation of IDragSource that provides normal
/// drag source functionality. It creates a data object that supports
/// inter-application dragging of text and HTML representation of
/// the dragged rows. It can optionally force a refresh of all dragged
/// rows when the drag is complete.
/// </summary>
/// <remarks>Subclasses can override GetDataObject() to add new
/// data formats to the data transfer object.</remarks>
public class SimpleDragSource : IDragSource
{
#region Constructors
/// <summary>
/// Construct a SimpleDragSource
/// </summary>
public SimpleDragSource() {
}
/// <summary>
/// Construct a SimpleDragSource that refreshes the dragged rows when
/// the drag is complete
/// </summary>
/// <param name="refreshAfterDrop"></param>
public SimpleDragSource(bool refreshAfterDrop) {
this.RefreshAfterDrop = refreshAfterDrop;
}
#endregion
#region Public properties
/// <summary>
/// Gets or sets whether the dragged rows should be refreshed when the
/// drag operation is complete.
/// </summary>
public bool RefreshAfterDrop {
get { return refreshAfterDrop; }
set { refreshAfterDrop = value; }
}
private bool refreshAfterDrop;
#endregion
#region IDragSource Members
/// <summary>
/// Create a DataObject when the user does a left mouse drag operation.
/// See IDragSource for further information.
/// </summary>
/// <param name="olv"></param>
/// <param name="button"></param>
/// <param name="item"></param>
/// <returns></returns>
public virtual Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item) {
// We only drag on left mouse
if (button != MouseButtons.Left)
return null;
return this.CreateDataObject(olv);
}
/// <summary>
/// Which operations are allowed in the operation? By default, all operations are supported.
/// </summary>
/// <param name="data"></param>
/// <returns>All opertions are supported</returns>
public virtual DragDropEffects GetAllowedEffects(Object data) {
return DragDropEffects.All | DragDropEffects.Link; // why didn't MS include 'Link' in 'All'??
}
/// <summary>
/// The drag operation is finished. Refreshe the dragged rows if so configured.
/// </summary>
/// <param name="dragObject"></param>
/// <param name="effect"></param>
public virtual void EndDrag(Object dragObject, DragDropEffects effect) {
OLVDataObject data = dragObject as OLVDataObject;
if (data == null)
return;
if (this.RefreshAfterDrop)
data.ListView.RefreshObjects(data.ModelObjects);
}
/// <summary>
/// Create a data object that will be used to as the data object
/// for the drag operation.
/// </summary>
/// <remarks>
/// Subclasses can override this method add new formats to the data object.
/// </remarks>
/// <param name="olv">The ObjectListView that is the source of the drag</param>
/// <returns>A data object for the drag</returns>
protected virtual object CreateDataObject(ObjectListView olv) {
return new OLVDataObject(olv);
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,185 @@
/*
* OLVDataObject.cs - An OLE DataObject that knows how to convert rows of an OLV to text and HTML
*
* Author: Phillip Piper
* Date: 2011-03-29 3:34PM
*
* Change log:
* v2.8
* 2014-05-02 JPP - When the listview is completely empty, don't try to set CSV text in the clipboard.
* v2.6
* 2012-08-08 JPP - Changed to use OLVExporter.
* - Added CSV to formats exported to Clipboard
* v2.4
* 2011-03-29 JPP - Initial version
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.Windows.Forms;
namespace BrightIdeasSoftware {
/// <summary>
/// A data transfer object that knows how to transform a list of model
/// objects into a text and HTML representation.
/// </summary>
public class OLVDataObject : DataObject {
#region Life and death
/// <summary>
/// Create a data object from the selected objects in the given ObjectListView
/// </summary>
/// <param name="olv">The source of the data object</param>
public OLVDataObject(ObjectListView olv)
: this(olv, olv.SelectedObjects) {
}
/// <summary>
/// Create a data object which operates on the given model objects
/// in the given ObjectListView
/// </summary>
/// <param name="olv">The source of the data object</param>
/// <param name="modelObjects">The model objects to be put into the data object</param>
public OLVDataObject(ObjectListView olv, IList modelObjects) {
this.objectListView = olv;
this.modelObjects = modelObjects;
this.includeHiddenColumns = olv.IncludeHiddenColumnsInDataTransfer;
this.includeColumnHeaders = olv.IncludeColumnHeadersInCopy;
this.CreateTextFormats();
}
#endregion
#region Properties
/// <summary>
/// Gets or sets whether hidden columns will also be included in the text
/// and HTML representation. If this is false, only visible columns will
/// be included.
/// </summary>
public bool IncludeHiddenColumns {
get { return includeHiddenColumns; }
}
private readonly bool includeHiddenColumns;
/// <summary>
/// Gets or sets whether column headers will also be included in the text
/// and HTML representation.
/// </summary>
public bool IncludeColumnHeaders {
get { return includeColumnHeaders; }
}
private readonly bool includeColumnHeaders;
/// <summary>
/// Gets the ObjectListView that is being used as the source of the data
/// </summary>
public ObjectListView ListView {
get { return objectListView; }
}
private readonly ObjectListView objectListView;
/// <summary>
/// Gets the model objects that are to be placed in the data object
/// </summary>
public IList ModelObjects {
get { return modelObjects; }
}
private readonly IList modelObjects;
#endregion
/// <summary>
/// Put a text and HTML representation of our model objects
/// into the data object.
/// </summary>
public void CreateTextFormats() {
OLVExporter exporter = this.CreateExporter();
// Put both the text and html versions onto the clipboard.
// For some reason, SetText() with UnicodeText doesn't set the basic CF_TEXT format,
// but using SetData() does.
//this.SetText(sbText.ToString(), TextDataFormat.UnicodeText);
this.SetData(exporter.ExportTo(OLVExporter.ExportFormat.TabSeparated));
string exportTo = exporter.ExportTo(OLVExporter.ExportFormat.CSV);
if (!String.IsNullOrEmpty(exportTo))
this.SetText(exportTo, TextDataFormat.CommaSeparatedValue);
this.SetText(ConvertToHtmlFragment(exporter.ExportTo(OLVExporter.ExportFormat.HTML)), TextDataFormat.Html);
}
/// <summary>
/// Create an exporter for the data contained in this object
/// </summary>
/// <returns></returns>
protected OLVExporter CreateExporter() {
OLVExporter exporter = new OLVExporter(this.ListView);
exporter.IncludeColumnHeaders = this.IncludeColumnHeaders;
exporter.IncludeHiddenColumns = this.IncludeHiddenColumns;
exporter.ModelObjects = this.ModelObjects;
return exporter;
}
/// <summary>
/// Make a HTML representation of our model objects
/// </summary>
[Obsolete("Use OLVExporter directly instead", false)]
public string CreateHtml() {
OLVExporter exporter = this.CreateExporter();
return exporter.ExportTo(OLVExporter.ExportFormat.HTML);
}
/// <summary>
/// Convert the fragment of HTML into the Clipboards HTML format.
/// </summary>
/// <remarks>The HTML format is found here http://msdn2.microsoft.com/en-us/library/aa767917.aspx
/// </remarks>
/// <param name="fragment">The HTML to put onto the clipboard. It must be valid HTML!</param>
/// <returns>A string that can be put onto the clipboard and will be recognized as HTML</returns>
private string ConvertToHtmlFragment(string fragment) {
// Minimal implementation of HTML clipboard format
const string SOURCE = "http://www.codeproject.com/Articles/16009/A-Much-Easier-to-Use-ListView";
const String MARKER_BLOCK =
"Version:1.0\r\n" +
"StartHTML:{0,8}\r\n" +
"EndHTML:{1,8}\r\n" +
"StartFragment:{2,8}\r\n" +
"EndFragment:{3,8}\r\n" +
"StartSelection:{2,8}\r\n" +
"EndSelection:{3,8}\r\n" +
"SourceURL:{4}\r\n" +
"{5}";
int prefixLength = String.Format(MARKER_BLOCK, 0, 0, 0, 0, SOURCE, "").Length;
const String DEFAULT_HTML_BODY =
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">" +
"<HTML><HEAD></HEAD><BODY><!--StartFragment-->{0}<!--EndFragment--></BODY></HTML>";
string html = String.Format(DEFAULT_HTML_BODY, fragment);
int startFragment = prefixLength + html.IndexOf(fragment, StringComparison.Ordinal);
int endFragment = startFragment + fragment.Length;
return String.Format(MARKER_BLOCK, prefixLength, prefixLength + html.Length, startFragment, endFragment, SOURCE, html);
}
}
}

View File

@@ -0,0 +1,165 @@
/*
* FastDataListView - A data bindable listview that has the speed of a virtual list
*
* Author: Phillip Piper
* Date: 22/09/2010 8:11 AM
*
* Change log:
* 2015-02-02 JPP - Made Unfreezing more efficient by removing a redundant BuildList() call
* v2.6
* 2010-09-22 JPP - Initial version
*
* Copyright (C) 2006-2015 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.ComponentModel;
using System.Windows.Forms;
using System.Drawing.Design;
namespace BrightIdeasSoftware
{
/// <summary>
/// A FastDataListView virtualizes the display of data from a DataSource. It operates on
/// DataSets and DataTables in the same way as a DataListView, but does so much more efficiently.
/// </summary>
/// <remarks>
/// <para>
/// A FastDataListView still has to load all its data from the DataSource. If you have SQL statement
/// that returns 1 million rows, all 1 million rows will still need to read from the database.
/// However, once the rows are loaded, the FastDataListView will only build rows as they are displayed.
/// </para>
/// </remarks>
public class FastDataListView : FastObjectListView
{
protected override void Dispose(bool disposing)
{
if (this.adapter != null) {
this.adapter.Dispose();
this.adapter = null;
}
base.Dispose(disposing);
}
#region Public Properties
/// <summary>
/// Gets or sets whether or not columns will be automatically generated to show the
/// columns when the DataSource is set.
/// </summary>
/// <remarks>This must be set before the DataSource is set. It has no effect afterwards.</remarks>
[Category("Data"),
Description("Should the control automatically generate columns from the DataSource"),
DefaultValue(true)]
public bool AutoGenerateColumns
{
get { return this.Adapter.AutoGenerateColumns; }
set { this.Adapter.AutoGenerateColumns = value; }
}
/// <summary>
/// Get or set the VirtualListDataSource that will be displayed in this list view.
/// </summary>
/// <remarks>The VirtualListDataSource should implement either <see cref="IList"/>, <see cref="IBindingList"/>,
/// or <see cref="IListSource"/>. Some common examples are the following types of objects:
/// <list type="unordered">
/// <item><description><see cref="DataView"/></description></item>
/// <item><description><see cref="DataTable"/></description></item>
/// <item><description><see cref="DataSet"/></description></item>
/// <item><description><see cref="DataViewManager"/></description></item>
/// <item><description><see cref="BindingSource"/></description></item>
/// </list>
/// <para>When binding to a list container (i.e. one that implements the
/// <see cref="IListSource"/> interface, such as <see cref="DataSet"/>)
/// you must also set the <see cref="DataMember"/> property in order
/// to identify which particular list you would like to display. You
/// may also set the <see cref="DataMember"/> property even when
/// VirtualListDataSource refers to a list, since <see cref="DataMember"/> can
/// also be used to navigate relations between lists.</para>
/// </remarks>
[Category("Data"),
TypeConverter("System.Windows.Forms.Design.DataSourceConverter, System.Design")]
public virtual Object DataSource {
get { return this.Adapter.DataSource; }
set { this.Adapter.DataSource = value; }
}
/// <summary>
/// Gets or sets the name of the list or table in the data source for which the DataListView is displaying data.
/// </summary>
/// <remarks>If the data source is not a DataSet or DataViewManager, this property has no effect</remarks>
[Category("Data"),
Editor("System.Windows.Forms.Design.DataMemberListEditor, System.Design", typeof(UITypeEditor)),
DefaultValue("")]
public virtual string DataMember {
get { return this.Adapter.DataMember; }
set { this.Adapter.DataMember = value; }
}
#endregion
#region Implementation properties
/// <summary>
/// Gets or sets the DataSourceAdaptor that does the bulk of the work needed
/// for data binding.
/// </summary>
protected DataSourceAdapter Adapter {
get {
if (adapter == null)
adapter = this.CreateDataSourceAdapter();
return adapter;
}
set { adapter = value; }
}
private DataSourceAdapter adapter;
#endregion
#region Implementation
/// <summary>
/// Create the DataSourceAdapter that this control will use.
/// </summary>
/// <returns>A DataSourceAdapter configured for this list</returns>
/// <remarks>Subclasses should override this to create their
/// own specialized adapters</remarks>
protected virtual DataSourceAdapter CreateDataSourceAdapter() {
return new DataSourceAdapter(this);
}
/// <summary>
/// Change the Unfreeze behaviour
/// </summary>
protected override void DoUnfreeze()
{
// Copied from base method, but we don't need to BuildList() since we know that our
// data adaptor is going to do that immediately after this method exits.
this.EndUpdate();
this.ResizeFreeSpaceFillingColumns();
// this.BuildList();
}
#endregion
}
}

View File

@@ -0,0 +1,419 @@
/*
* FastObjectListView - A listview that behaves like an ObjectListView but has the speed of a virtual list
*
* Author: Phillip Piper
* Date: 27/09/2008 9:15 AM
*
* Change log:
* 2014-10-15 JPP - Fire Filter event when applying filters
* v2.8
* 2012-06-11 JPP - Added more efficient version of FilteredObjects
* v2.5.1
* 2011-04-25 JPP - Fixed problem with removing objects from filtered or sorted list
* v2.4
* 2010-04-05 JPP - Added filtering
* v2.3
* 2009-08-27 JPP - Added GroupingStrategy
* - Added optimized Objects property
* v2.2.1
* 2009-01-07 JPP - Made all public and protected methods virtual
* 2008-09-27 JPP - Separated from ObjectListView.cs
*
* Copyright (C) 2006-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
namespace BrightIdeasSoftware
{
/// <summary>
/// A FastObjectListView trades function for speed.
/// </summary>
/// <remarks>
/// <para>On my mid-range laptop, this view builds a list of 10,000 objects in 0.1 seconds,
/// as opposed to a normal ObjectListView which takes 10-15 seconds. Lists of up to 50,000 items should be
/// able to be handled with sub-second response times even on low end machines.</para>
/// <para>
/// A FastObjectListView is implemented as a virtual list with many of the virtual modes limits (e.g. no sorting)
/// fixed through coding. There are some functions that simply cannot be provided. Specifically, a FastObjectListView cannot:
/// <list type="bullet">
/// <item><description>use Tile view</description></item>
/// <item><description>show groups on XP</description></item>
/// </list>
/// </para>
/// </remarks>
public class FastObjectListView : VirtualObjectListView
{
/// <summary>
/// Make a FastObjectListView
/// </summary>
public FastObjectListView() {
this.VirtualListDataSource = new FastObjectListDataSource(this);
this.GroupingStrategy = new FastListGroupingStrategy();
}
/// <summary>
/// Gets the collection of objects that survive any filtering that may be in place.
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public override IEnumerable FilteredObjects {
get {
// This is much faster than the base method
return ((FastObjectListDataSource)this.VirtualListDataSource).FilteredObjectList;
}
}
/// <summary>
/// Get/set the collection of objects that this list will show
/// </summary>
/// <remarks>
/// <para>
/// The contents of the control will be updated immediately after setting this property.
/// </para>
/// <para>This method preserves selection, if possible. Use SetObjects() if
/// you do not want to preserve the selection. Preserving selection is the slowest part of this
/// code and performance is O(n) where n is the number of selected rows.</para>
/// <para>This method is not thread safe.</para>
/// </remarks>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public override IEnumerable Objects {
get {
// This is much faster than the base method
return ((FastObjectListDataSource)this.VirtualListDataSource).ObjectList;
}
set { base.Objects = value; }
}
/// <summary>
/// Move the given collection of objects to the given index.
/// </summary>
/// <remarks>This operation only makes sense on non-grouped ObjectListViews.</remarks>
/// <param name="index"></param>
/// <param name="modelObjects"></param>
public override void MoveObjects(int index, ICollection modelObjects) {
if (this.InvokeRequired) {
this.Invoke((MethodInvoker)delegate() { this.MoveObjects(index, modelObjects); });
return;
}
// If any object that is going to be moved is before the point where the insertion
// will occur, then we have to reduce the location of our insertion point
int displacedObjectCount = 0;
foreach (object modelObject in modelObjects) {
int i = this.IndexOf(modelObject);
if (i >= 0 && i <= index)
displacedObjectCount++;
}
index -= displacedObjectCount;
this.BeginUpdate();
try {
this.RemoveObjects(modelObjects);
this.InsertObjects(index, modelObjects);
}
finally {
this.EndUpdate();
}
}
/// <summary>
/// Remove any sorting and revert to the given order of the model objects
/// </summary>
/// <remarks>To be really honest, Unsort() doesn't work on FastObjectListViews since
/// the original ordering of model objects is lost when Sort() is called. So this method
/// effectively just turns off sorting.</remarks>
public override void Unsort() {
this.ShowGroups = false;
this.PrimarySortColumn = null;
this.PrimarySortOrder = SortOrder.None;
this.SetObjects(this.Objects);
}
}
/// <summary>
/// Provide a data source for a FastObjectListView
/// </summary>
/// <remarks>
/// This class isn't intended to be used directly, but it is left as a public
/// class just in case someone wants to subclass it.
/// </remarks>
/// <remarks>
/// Create a FastObjectListDataSource
/// </remarks>
/// <param name="listView"></param>
public class FastObjectListDataSource(FastObjectListView listView) : AbstractVirtualListDataSource(listView)
{
#region IVirtualListDataSource Members
/// <summary>
/// Get n'th object
/// </summary>
/// <param name="n"></param>
/// <returns></returns>
public override object GetNthObject(int n) {
if (n >= 0 && n < this.filteredObjectList.Count)
return this.filteredObjectList[n];
return null;
}
/// <summary>
/// How many items are in the data source
/// </summary>
/// <returns></returns>
public override int GetObjectCount() {
return this.filteredObjectList.Count;
}
/// <summary>
/// Get the index of the given model
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public override int GetObjectIndex(object model) {
int index;
if (model != null && this.objectsToIndexMap.TryGetValue(model, out index))
return index;
return -1;
}
/// <summary>
///
/// </summary>
/// <param name="text"></param>
/// <param name="first"></param>
/// <param name="last"></param>
/// <param name="column"></param>
/// <returns></returns>
public override int SearchText(string text, int first, int last, OLVColumn column) {
if (first <= last) {
for (int i = first; i <= last; i++) {
string data = column.GetStringValue(this.listView.GetNthItemInDisplayOrder(i).RowObject);
if (data.StartsWith(text, StringComparison.CurrentCultureIgnoreCase))
return i;
}
} else {
for (int i = first; i >= last; i--) {
string data = column.GetStringValue(this.listView.GetNthItemInDisplayOrder(i).RowObject);
if (data.StartsWith(text, StringComparison.CurrentCultureIgnoreCase))
return i;
}
}
return -1;
}
/// <summary>
///
/// </summary>
/// <param name="column"></param>
/// <param name="sortOrder"></param>
public override void Sort(OLVColumn column, SortOrder sortOrder) {
if (sortOrder != SortOrder.None) {
ModelObjectComparer comparer = new ModelObjectComparer(column, sortOrder, this.listView.SecondarySortColumn, this.listView.SecondarySortOrder);
this.fullObjectList.Sort(comparer);
this.filteredObjectList.Sort(comparer);
}
this.RebuildIndexMap();
}
/// <summary>
///
/// </summary>
/// <param name="modelObjects"></param>
public override void AddObjects(ICollection modelObjects) {
foreach (object modelObject in modelObjects) {
if (modelObject != null)
this.fullObjectList.Add(modelObject);
}
this.FilterObjects();
this.RebuildIndexMap();
}
/// <summary>
///
/// </summary>
/// <param name="index"></param>
/// <param name="modelObjects"></param>
public override void InsertObjects(int index, ICollection modelObjects) {
this.fullObjectList.InsertRange(index, modelObjects);
this.FilterObjects();
this.RebuildIndexMap();
}
/// <summary>
/// Remove the given collection of models from this source.
/// </summary>
/// <param name="modelObjects"></param>
public override void RemoveObjects(ICollection modelObjects) {
// We have to unselect any object that is about to be deleted
List<int> indicesToRemove = new List<int>();
foreach (object modelObject in modelObjects) {
int i = this.GetObjectIndex(modelObject);
if (i >= 0)
indicesToRemove.Add(i);
}
// Sort the indices from highest to lowest so that we
// remove latter ones before earlier ones. In this way, the
// indices of the rows doesn't change after the deletes.
indicesToRemove.Sort();
indicesToRemove.Reverse();
foreach (int i in indicesToRemove)
this.listView.SelectedIndices.Remove(i);
// Remove the objects from the unfiltered list
foreach (object modelObject in modelObjects)
this.fullObjectList.Remove(modelObject);
this.FilterObjects();
this.RebuildIndexMap();
}
/// <summary>
///
/// </summary>
/// <param name="collection"></param>
public override void SetObjects(IEnumerable collection) {
ArrayList newObjects = ObjectListView.EnumerableToArray(collection, true);
this.fullObjectList = newObjects;
this.FilterObjects();
this.RebuildIndexMap();
}
/// <summary>
/// Update/replace the nth object with the given object
/// </summary>
/// <param name="index"></param>
/// <param name="modelObject"></param>
public override void UpdateObject(int index, object modelObject) {
if (index < 0 || index >= this.filteredObjectList.Count)
return;
int i = this.fullObjectList.IndexOf(this.filteredObjectList[index]);
if (i < 0)
return;
if (ReferenceEquals(this.fullObjectList[i], modelObject))
return;
this.fullObjectList[i] = modelObject;
this.filteredObjectList[index] = modelObject;
this.objectsToIndexMap[modelObject] = index;
}
private ArrayList fullObjectList = new ArrayList();
private ArrayList filteredObjectList = new ArrayList();
private IModelFilter modelFilter;
private IListFilter listFilter;
#endregion
#region IFilterableDataSource Members
/// <summary>
/// Apply the given filters to this data source. One or both may be null.
/// </summary>
/// <param name="iModelFilter"></param>
/// <param name="iListFilter"></param>
public override void ApplyFilters(IModelFilter iModelFilter, IListFilter iListFilter) {
this.modelFilter = iModelFilter;
this.listFilter = iListFilter;
this.SetObjects(this.fullObjectList);
}
#endregion
#region Implementation
/// <summary>
/// Gets the full list of objects being used for this fast list.
/// This list is unfiltered.
/// </summary>
public ArrayList ObjectList {
get { return fullObjectList; }
}
/// <summary>
/// Gets the list of objects from ObjectList which survive any installed filters.
/// </summary>
public ArrayList FilteredObjectList {
get { return filteredObjectList; }
}
/// <summary>
/// Rebuild the map that remembers which model object is displayed at which line
/// </summary>
protected void RebuildIndexMap() {
this.objectsToIndexMap.Clear();
for (int i = 0; i < this.filteredObjectList.Count; i++)
this.objectsToIndexMap[this.filteredObjectList[i]] = i;
}
readonly Dictionary<Object, int> objectsToIndexMap = new Dictionary<Object, int>();
/// <summary>
/// Build our filtered list from our full list.
/// </summary>
protected void FilterObjects() {
// If this list isn't filtered, we don't need to do anything else
if (!this.listView.UseFiltering) {
this.filteredObjectList = new ArrayList(this.fullObjectList);
return;
}
// Tell the world to filter the objects. If they do so, don't do anything else
// ReSharper disable PossibleMultipleEnumeration
FilterEventArgs args = new FilterEventArgs(this.fullObjectList);
this.listView.OnFilter(args);
if (args.FilteredObjects != null) {
this.filteredObjectList = ObjectListView.EnumerableToArray(args.FilteredObjects, false);
return;
}
IEnumerable objects = (this.listFilter == null) ?
this.fullObjectList : this.listFilter.Filter(this.fullObjectList);
// Apply the object filter if there is one
if (this.modelFilter == null) {
this.filteredObjectList = ObjectListView.EnumerableToArray(objects, false);
} else {
this.filteredObjectList = new ArrayList();
foreach (object model in objects) {
if (this.modelFilter.Filter(model))
this.filteredObjectList.Add(model);
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,125 @@
/*
* Cluster - Implements a simple cluster
*
* Author: Phillip Piper
* Date: 3-March-2011 10:53 pm
*
* Change log:
* 2011-03-03 JPP - First version
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace BrightIdeasSoftware {
/// <summary>
/// Concrete implementation of the ICluster interface.
/// </summary>
public class Cluster : ICluster {
#region Life and death
/// <summary>
/// Create a cluster
/// </summary>
/// <param name="key">The key for the cluster</param>
public Cluster(object key) {
this.Count = 1;
this.ClusterKey = key;
}
#endregion
#region Public overrides
/// <summary>
/// Return a string representation of this cluster
/// </summary>
/// <returns></returns>
public override string ToString() {
return this.DisplayLabel ?? "[empty]";
}
#endregion
#region Implementation of ICluster
/// <summary>
/// Gets or sets how many items belong to this cluster
/// </summary>
public int Count {
get { return count; }
set { count = value; }
}
private int count;
/// <summary>
/// Gets or sets the label that will be shown to the user to represent
/// this cluster
/// </summary>
public string DisplayLabel {
get { return displayLabel; }
set { displayLabel = value; }
}
private string displayLabel;
/// <summary>
/// Gets or sets the actual data object that all members of this cluster
/// have commonly returned.
/// </summary>
public object ClusterKey {
get { return clusterKey; }
set { clusterKey = value; }
}
private object clusterKey;
#endregion
#region Implementation of IComparable
/// <summary>
/// Return an indication of the ordering between this object and the given one
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
public int CompareTo(object other) {
if (other == null || other == System.DBNull.Value)
return 1;
ICluster otherCluster = other as ICluster;
if (otherCluster == null)
return 1;
string keyAsString = this.ClusterKey as string;
if (keyAsString != null)
return String.Compare(keyAsString, otherCluster.ClusterKey as string, StringComparison.CurrentCultureIgnoreCase);
IComparable keyAsComparable = this.ClusterKey as IComparable;
if (keyAsComparable != null)
return keyAsComparable.CompareTo(otherCluster.ClusterKey);
return -1;
}
#endregion
}
}

View File

@@ -0,0 +1,189 @@
/*
* ClusteringStrategy - Implements a simple clustering strategy
*
* Author: Phillip Piper
* Date: 3-March-2011 10:53 pm
*
* Change log:
* 2011-03-03 JPP - First version
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.Text;
namespace BrightIdeasSoftware {
/// <summary>
/// This class provides a useful base implemention of a clustering
/// strategy where the clusters are grouped around the value of a given column.
/// </summary>
public class ClusteringStrategy : IClusteringStrategy {
#region Static properties
/// <summary>
/// This field is the text that will be shown to the user when a cluster
/// key is null. It is exposed so it can be localized.
/// </summary>
static public string NULL_LABEL = "[null]";
/// <summary>
/// This field is the text that will be shown to the user when a cluster
/// key is empty (i.e. a string of zero length). It is exposed so it can be localized.
/// </summary>
static public string EMPTY_LABEL = "[empty]";
/// <summary>
/// Gets or sets the format that will be used by default for clusters that only
/// contain 1 item. The format string must accept two placeholders:
/// - {0} is the cluster key converted to a string
/// - {1} is the number of items in the cluster (always 1 in this case)
/// </summary>
static public string DefaultDisplayLabelFormatSingular {
get { return defaultDisplayLabelFormatSingular; }
set { defaultDisplayLabelFormatSingular = value; }
}
static private string defaultDisplayLabelFormatSingular = "{0} ({1} item)";
/// <summary>
/// Gets or sets the format that will be used by default for clusters that
/// contain 0 or two or more items. The format string must accept two placeholders:
/// - {0} is the cluster key converted to a string
/// - {1} is the number of items in the cluster
/// </summary>
static public string DefaultDisplayLabelFormatPlural {
get { return defaultDisplayLabelFormatPural; }
set { defaultDisplayLabelFormatPural = value; }
}
static private string defaultDisplayLabelFormatPural = "{0} ({1} items)";
#endregion
#region Life and death
/// <summary>
/// Create a clustering strategy
/// </summary>
public ClusteringStrategy() {
this.DisplayLabelFormatSingular = DefaultDisplayLabelFormatSingular;
this.DisplayLabelFormatPlural = DefaultDisplayLabelFormatPlural;
}
#endregion
#region Public properties
/// <summary>
/// Gets or sets the column upon which this strategy is operating
/// </summary>
public OLVColumn Column {
get { return column; }
set { column = value; }
}
private OLVColumn column;
/// <summary>
/// Gets or sets the format that will be used when the cluster
/// contains only 1 item. The format string must accept two placeholders:
/// - {0} is the cluster key converted to a string
/// - {1} is the number of items in the cluster (always 1 in this case)
/// </summary>
/// <remarks>If this is not set, the value from
/// ClusteringStrategy.DefaultDisplayLabelFormatSingular will be used</remarks>
public string DisplayLabelFormatSingular {
get { return displayLabelFormatSingular; }
set { displayLabelFormatSingular = value; }
}
private string displayLabelFormatSingular;
/// <summary>
/// Gets or sets the format that will be used when the cluster
/// contains 0 or two or more items. The format string must accept two placeholders:
/// - {0} is the cluster key converted to a string
/// - {1} is the number of items in the cluster
/// </summary>
/// <remarks>If this is not set, the value from
/// ClusteringStrategy.DefaultDisplayLabelFormatPlural will be used</remarks>
public string DisplayLabelFormatPlural {
get { return displayLabelFormatPural; }
set { displayLabelFormatPural = value; }
}
private string displayLabelFormatPural;
#endregion
#region ICluster implementation
/// <summary>
/// Get the cluster key by which the given model will be partitioned by this strategy
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
virtual public object GetClusterKey(object model) {
return this.Column.GetValue(model);
}
/// <summary>
/// Create a cluster to hold the given cluster key
/// </summary>
/// <param name="clusterKey"></param>
/// <returns></returns>
virtual public ICluster CreateCluster(object clusterKey) {
return new Cluster(clusterKey);
}
/// <summary>
/// Gets the display label that the given cluster should use
/// </summary>
/// <param name="cluster"></param>
/// <returns></returns>
virtual public string GetClusterDisplayLabel(ICluster cluster) {
string s = this.Column.ValueToString(cluster.ClusterKey) ?? NULL_LABEL;
if (String.IsNullOrEmpty(s))
s = EMPTY_LABEL;
return this.ApplyDisplayFormat(cluster, s);
}
/// <summary>
/// Create a filter that will include only model objects that
/// match one or more of the given values.
/// </summary>
/// <param name="valuesChosenForFiltering"></param>
/// <returns></returns>
virtual public IModelFilter CreateFilter(IList valuesChosenForFiltering) {
return new OneOfFilter(this.GetClusterKey, valuesChosenForFiltering);
}
/// <summary>
/// Create a label that combines the string representation of the cluster
/// key with a format string that holds an "X [N items in cluster]" type layout.
/// </summary>
/// <param name="cluster"></param>
/// <param name="s"></param>
/// <returns></returns>
virtual protected string ApplyDisplayFormat(ICluster cluster, string s) {
string format = (cluster.Count == 1) ? this.DisplayLabelFormatSingular : this.DisplayLabelFormatPlural;
return String.IsNullOrEmpty(format) ? s : String.Format(format, s, cluster.Count);
}
#endregion
}
}

View File

@@ -0,0 +1,70 @@
/*
* ClusteringStrategy - Implements a simple clustering strategy
*
* Author: Phillip Piper
* Date: 1-April-2011 8:12am
*
* Change log:
* 2011-04-01 JPP - First version
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace BrightIdeasSoftware {
/// <summary>
/// This class calculates clusters from the groups that the column uses.
/// </summary>
/// <remarks>
/// <para>
/// This is the default strategy for all non-date, filterable columns.
/// </para>
/// <para>
/// This class does not strictly mimic the groups created by the given column.
/// In particular, if the programmer changes the default grouping technique
/// by listening for grouping events, this class will not mimic that behaviour.
/// </para>
/// </remarks>
public class ClustersFromGroupsStrategy : ClusteringStrategy {
/// <summary>
/// Get the cluster key by which the given model will be partitioned by this strategy
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public override object GetClusterKey(object model) {
return this.Column.GetGroupKey(model);
}
/// <summary>
/// Gets the display label that the given cluster should use
/// </summary>
/// <param name="cluster"></param>
/// <returns></returns>
public override string GetClusterDisplayLabel(ICluster cluster) {
string s = this.Column.ConvertGroupKeyToTitle(cluster.ClusterKey);
if (String.IsNullOrEmpty(s))
s = EMPTY_LABEL;
return this.ApplyDisplayFormat(cluster, s);
}
}
}

View File

@@ -0,0 +1,187 @@
/*
* DateTimeClusteringStrategy - A strategy to cluster objects by a date time
*
* Author: Phillip Piper
* Date: 30-March-2011 9:40am
*
* Change log:
* 2011-03-30 JPP - First version
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Globalization;
namespace BrightIdeasSoftware {
/// <summary>
/// This enum is used to indicate various portions of a datetime
/// </summary>
[Flags]
public enum DateTimePortion {
/// <summary>
/// Year
/// </summary>
Year = 0x01,
/// <summary>
/// Month
/// </summary>
Month = 0x02,
/// <summary>
/// Day of the month
/// </summary>
Day = 0x04,
/// <summary>
/// Hour
/// </summary>
Hour = 0x08,
/// <summary>
/// Minute
/// </summary>
Minute = 0x10,
/// <summary>
/// Second
/// </summary>
Second = 0x20
}
/// <summary>
/// This class implements a strategy where the model objects are clustered
/// according to some portion of the datetime value in the configured column.
/// </summary>
/// <remarks>To create a strategy that grouped people who were born in
/// the same month, you would create a strategy that extracted just
/// the month, and formatted it to show just the month's name. Like this:
/// </remarks>
/// <example>
/// someColumn.ClusteringStrategy = new DateTimeClusteringStrategy(DateTimePortion.Month, "MMMM");
/// </example>
public class DateTimeClusteringStrategy : ClusteringStrategy {
#region Life and death
/// <summary>
/// Create a strategy that clusters by month/year
/// </summary>
public DateTimeClusteringStrategy()
: this(DateTimePortion.Year | DateTimePortion.Month, "MMMM yyyy") {
}
/// <summary>
/// Create a strategy that clusters around the given parts
/// </summary>
/// <param name="portions"></param>
/// <param name="format"></param>
public DateTimeClusteringStrategy(DateTimePortion portions, string format) {
this.Portions = portions;
this.Format = format;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the format string will will be used to create a user-presentable
/// version of the cluster key.
/// </summary>
/// <remarks>The format should use the date/time format strings, as documented
/// in the Windows SDK. Both standard formats and custom format will work.</remarks>
/// <example>"D" - long date pattern</example>
/// <example>"MMMM, yyyy" - "January, 1999"</example>
public string Format {
get { return format; }
set { format = value; }
}
private string format;
/// <summary>
/// Gets or sets the parts of the DateTime that will be extracted when
/// determining the clustering key for an object.
/// </summary>
public DateTimePortion Portions {
get { return portions; }
set { portions = value; }
}
private DateTimePortion portions = DateTimePortion.Year | DateTimePortion.Month;
#endregion
#region IClusterStrategy implementation
/// <summary>
/// Get the cluster key by which the given model will be partitioned by this strategy
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public override object GetClusterKey(object model) {
// Get the data attribute we want from the given model
// Make sure the returned value is a DateTime
DateTime? dateTime = this.Column.GetValue(model) as DateTime?;
if (!dateTime.HasValue)
return null;
// Extract the parts of the datetime that we are intereted in.
// Even if we aren't interested in a particular portion, we still have to give it a reasonable default
// otherwise we won't be able to build a DateTime object for it
int year = ((this.Portions & DateTimePortion.Year) == DateTimePortion.Year) ? dateTime.Value.Year : 1;
int month = ((this.Portions & DateTimePortion.Month) == DateTimePortion.Month) ? dateTime.Value.Month : 1;
int day = ((this.Portions & DateTimePortion.Day) == DateTimePortion.Day) ? dateTime.Value.Day : 1;
int hour = ((this.Portions & DateTimePortion.Hour) == DateTimePortion.Hour) ? dateTime.Value.Hour : 0;
int minute = ((this.Portions & DateTimePortion.Minute) == DateTimePortion.Minute) ? dateTime.Value.Minute : 0;
int second = ((this.Portions & DateTimePortion.Second) == DateTimePortion.Second) ? dateTime.Value.Second : 0;
return new DateTime(year, month, day, hour, minute, second);
}
/// <summary>
/// Gets the display label that the given cluster should use
/// </summary>
/// <param name="cluster"></param>
/// <returns></returns>
public override string GetClusterDisplayLabel(ICluster cluster) {
DateTime? dateTime = cluster.ClusterKey as DateTime?;
return this.ApplyDisplayFormat(cluster, dateTime.HasValue ? this.DateToString(dateTime.Value) : NULL_LABEL);
}
/// <summary>
/// Convert the given date into a user presentable string
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
protected virtual string DateToString(DateTime dateTime) {
if (String.IsNullOrEmpty(this.Format))
return dateTime.ToString(CultureInfo.CurrentUICulture);
try {
return dateTime.ToString(this.Format);
}
catch (FormatException) {
return String.Format("Bad format string '{0}' for value '{1}'", this.Format, dateTime);
}
}
#endregion
}
}

View File

@@ -0,0 +1,369 @@
/*
* FilterMenuBuilder - Responsible for creating a Filter menu
*
* Author: Phillip Piper
* Date: 4-March-2011 11:59 pm
*
* Change log:
* 2012-05-20 JPP - Allow the same model object to be in multiple clusters
* Useful for xor'ed flag fields, and multi-value strings
* (e.g. hobbies that are stored as comma separated values).
* v2.5.1
* 2012-04-14 JPP - Fixed rare bug with clustering an empty list (SF #3445118)
* v2.5
* 2011-04-12 JPP - Added some images to menu
* 2011-03-04 JPP - First version
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using System.Drawing;
namespace BrightIdeasSoftware {
/// <summary>
/// Instances of this class know how to build a Filter menu.
/// It is responsible for clustering the values in the target column,
/// build a menu that shows those clusters, and then constructing
/// a filter that will enact the users choices.
/// </summary>
/// <remarks>
/// Almost all of the methods in this class are declared as "virtual protected"
/// so that subclasses can provide alternative behaviours.
/// </remarks>
public class FilterMenuBuilder {
#region Static properties
/// <summary>
/// Gets or sets the string that labels the Apply button.
/// Exposed so it can be localized.
/// </summary>
static public string APPLY_LABEL = "Apply";
/// <summary>
/// Gets or sets the string that labels the Clear All menu item.
/// Exposed so it can be localized.
/// </summary>
static public string CLEAR_ALL_FILTERS_LABEL = "Clear All Filters";
/// <summary>
/// Gets or sets the string that labels the Filtering menu as a whole..
/// Exposed so it can be localized.
/// </summary>
static public string FILTERING_LABEL = "Filtering";
/// <summary>
/// Gets or sets the string that represents Select All values.
/// If this is set to null or empty, no Select All option will be included.
/// Exposed so it can be localized.
/// </summary>
static public string SELECT_ALL_LABEL = "Select All";
/// <summary>
/// Gets or sets the image that will be placed next to the Clear Filtering menu item
/// </summary>
static public Bitmap ClearFilteringImage = BrightIdeasSoftware.Properties.Resources.ClearFiltering;
/// <summary>
/// Gets or sets the image that will be placed next to all "Apply" menu items on the filtering menu
/// </summary>
static public Bitmap FilteringImage = BrightIdeasSoftware.Properties.Resources.Filtering;
#endregion
#region Public properties
/// <summary>
/// Gets or sets whether null should be considered as a valid data value.
/// If this is true (the default), then a cluster will null as a key will be allow.
/// If this is false, object that return a cluster key of null will ignored.
/// </summary>
public bool TreatNullAsDataValue {
get { return treatNullAsDataValue; }
set { treatNullAsDataValue = value; }
}
private bool treatNullAsDataValue = true;
/// <summary>
/// Gets or sets the maximum number of objects that the clustering strategy
/// will consider. This should be large enough to collect all unique clusters,
/// but small enough to finish in a reasonable time.
/// </summary>
/// <remarks>The default value is 10,000. This should be perfectly
/// acceptable for almost all lists.</remarks>
public int MaxObjectsToConsider {
get { return maxObjectsToConsider; }
set { maxObjectsToConsider = value; }
}
private int maxObjectsToConsider = 10000;
#endregion
/// <summary>
/// Create a Filter menu on the given tool tip for the given column in the given ObjectListView.
/// </summary>
/// <remarks>This is the main entry point into this class.</remarks>
/// <param name="strip"></param>
/// <param name="listView"></param>
/// <param name="column"></param>
/// <returns>The strip that should be shown to the user</returns>
virtual public ToolStripDropDown MakeFilterMenu(ToolStripDropDown strip, ObjectListView listView, OLVColumn column) {
if (strip == null) throw new ArgumentNullException("strip");
if (listView == null) throw new ArgumentNullException("listView");
if (column == null) throw new ArgumentNullException("column");
if (!column.UseFiltering || column.ClusteringStrategy == null || listView.Objects == null)
return strip;
List<ICluster> clusters = this.Cluster(column.ClusteringStrategy, listView, column);
if (clusters.Count > 0) {
this.SortClusters(column.ClusteringStrategy, clusters);
strip.Items.Add(this.CreateFilteringMenuItem(column, clusters));
}
return strip;
}
/// <summary>
/// Create a collection of clusters that should be presented to the user
/// </summary>
/// <param name="strategy"></param>
/// <param name="listView"></param>
/// <param name="column"></param>
/// <returns></returns>
virtual protected List<ICluster> Cluster(IClusteringStrategy strategy, ObjectListView listView, OLVColumn column) {
// Build a map that correlates cluster key to clusters
NullableDictionary<object, ICluster> map = new NullableDictionary<object, ICluster>();
int count = 0;
foreach (object model in listView.ObjectsForClustering) {
this.ClusterOneModel(strategy, map, model);
if (count++ > this.MaxObjectsToConsider)
break;
}
// Now that we know exactly how many items are in each cluster, create a label for it
foreach (ICluster cluster in map.Values)
cluster.DisplayLabel = strategy.GetClusterDisplayLabel(cluster);
return new List<ICluster>(map.Values);
}
private void ClusterOneModel(IClusteringStrategy strategy, NullableDictionary<object, ICluster> map, object model) {
object clusterKey = strategy.GetClusterKey(model);
// If the returned value is an IEnumerable, that means the given model can belong to more than one cluster
IEnumerable keyEnumerable = clusterKey as IEnumerable;
if (clusterKey is string || keyEnumerable == null)
keyEnumerable = new object[] {clusterKey};
// Deal with nulls and DBNulls
ArrayList nullCorrected = new ArrayList();
foreach (object key in keyEnumerable) {
if (key == null || key == System.DBNull.Value) {
if (this.TreatNullAsDataValue)
nullCorrected.Add(null);
} else nullCorrected.Add(key);
}
// Group by key
foreach (object key in nullCorrected) {
if (map.ContainsKey(key))
map[key].Count += 1;
else
map[key] = strategy.CreateCluster(key);
}
}
/// <summary>
/// Order the given list of clusters in the manner in which they should be presented to the user.
/// </summary>
/// <param name="strategy"></param>
/// <param name="clusters"></param>
virtual protected void SortClusters(IClusteringStrategy strategy, List<ICluster> clusters) {
clusters.Sort();
}
/// <summary>
/// Do the work of making a menu that shows the clusters to the users
/// </summary>
/// <param name="column"></param>
/// <param name="clusters"></param>
/// <returns></returns>
virtual protected ToolStripMenuItem CreateFilteringMenuItem(OLVColumn column, List<ICluster> clusters) {
ToolStripCheckedListBox checkedList = new ToolStripCheckedListBox();
checkedList.Tag = column;
foreach (ICluster cluster in clusters)
checkedList.AddItem(cluster, column.ValuesChosenForFiltering.Contains(cluster.ClusterKey));
if (!String.IsNullOrEmpty(SELECT_ALL_LABEL)) {
int checkedCount = checkedList.CheckedItems.Count;
if (checkedCount == 0)
checkedList.AddItem(SELECT_ALL_LABEL, CheckState.Unchecked);
else
checkedList.AddItem(SELECT_ALL_LABEL, checkedCount == clusters.Count ? CheckState.Checked : CheckState.Indeterminate);
}
checkedList.ItemCheck += new ItemCheckEventHandler(HandleItemCheckedWrapped);
ToolStripMenuItem clearAll = new ToolStripMenuItem(CLEAR_ALL_FILTERS_LABEL, ClearFilteringImage, delegate(object sender, EventArgs args) {
this.ClearAllFilters(column);
});
ToolStripMenuItem apply = new ToolStripMenuItem(APPLY_LABEL, FilteringImage, delegate(object sender, EventArgs args) {
this.EnactFilter(checkedList, column);
});
ToolStripMenuItem subMenu = new ToolStripMenuItem(FILTERING_LABEL, null, new ToolStripItem[] {
clearAll, new ToolStripSeparator(), checkedList, apply });
return subMenu;
}
/// <summary>
/// Wrap a protected section around the real HandleItemChecked method, so that if
/// that method tries to change a "checkedness" of an item, we don't get a recursive
/// stack error. Effectively, this ensure that HandleItemChecked is only called
/// in response to a user action.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void HandleItemCheckedWrapped(object sender, ItemCheckEventArgs e) {
if (alreadyInHandleItemChecked)
return;
try {
alreadyInHandleItemChecked = true;
this.HandleItemChecked(sender, e);
}
finally {
alreadyInHandleItemChecked = false;
}
}
bool alreadyInHandleItemChecked = false;
/// <summary>
/// Handle a user-generated ItemCheck event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
virtual protected void HandleItemChecked(object sender, ItemCheckEventArgs e) {
ToolStripCheckedListBox checkedList = sender as ToolStripCheckedListBox;
if (checkedList == null) return;
OLVColumn column = checkedList.Tag as OLVColumn;
if (column == null) return;
ObjectListView listView = column.ListView as ObjectListView;
if (listView == null) return;
// Deal with the "Select All" item if there is one
int selectAllIndex = checkedList.Items.IndexOf(SELECT_ALL_LABEL);
if (selectAllIndex >= 0)
HandleSelectAllItem(e, checkedList, selectAllIndex);
}
/// <summary>
/// Handle any checking/unchecking of the Select All option, and keep
/// its checkedness in sync with everything else that is checked.
/// </summary>
/// <param name="e"></param>
/// <param name="checkedList"></param>
/// <param name="selectAllIndex"></param>
virtual protected void HandleSelectAllItem(ItemCheckEventArgs e, ToolStripCheckedListBox checkedList, int selectAllIndex) {
// Did they check/uncheck the "Select All"?
if (e.Index == selectAllIndex) {
if (e.NewValue == CheckState.Checked)
checkedList.CheckAll();
if (e.NewValue == CheckState.Unchecked)
checkedList.UncheckAll();
return;
}
// OK. The user didn't check/uncheck SelectAll. Now we have to update it's
// checkedness to reflect the state of everything else
// If all clusters are checked, we check the Select All.
// If no clusters are checked, the uncheck the Select All.
// For everything else, Select All is set to indeterminate.
// How many items are currenty checked?
int count = checkedList.CheckedItems.Count;
// First complication.
// The value of the Select All itself doesn't count
if (checkedList.GetItemCheckState(selectAllIndex) != CheckState.Unchecked)
count -= 1;
// Another complication.
// CheckedItems does not yet know about the item the user has just
// clicked, so we have to adjust the count of checked items to what
// it is going to be
if (e.NewValue != e.CurrentValue) {
if (e.NewValue == CheckState.Checked)
count += 1;
else
count -= 1;
}
// Update the state of the Select All item
if (count == 0)
checkedList.SetItemState(selectAllIndex, CheckState.Unchecked);
else if (count == checkedList.Items.Count - 1)
checkedList.SetItemState(selectAllIndex, CheckState.Checked);
else
checkedList.SetItemState(selectAllIndex, CheckState.Indeterminate);
}
/// <summary>
/// Clear all the filters that are applied to the given column
/// </summary>
/// <param name="column">The column from which filters are to be removed</param>
virtual protected void ClearAllFilters(OLVColumn column) {
ObjectListView olv = column.ListView as ObjectListView;
if (olv == null || olv.IsDisposed)
return;
olv.ResetColumnFiltering();
}
/// <summary>
/// Apply the selected values from the given list as a filter on the given column
/// </summary>
/// <param name="checkedList">A list in which the checked items should be used as filters</param>
/// <param name="column">The column for which a filter should be generated</param>
virtual protected void EnactFilter(ToolStripCheckedListBox checkedList, OLVColumn column) {
ObjectListView olv = column.ListView as ObjectListView;
if (olv == null || olv.IsDisposed)
return;
// Collect all the checked values
ArrayList chosenValues = new ArrayList();
foreach (object x in checkedList.CheckedItems) {
ICluster cluster = x as ICluster;
if (cluster != null) {
chosenValues.Add(cluster.ClusterKey);
}
}
column.ValuesChosenForFiltering = chosenValues;
olv.UpdateColumnFiltering();
}
}
}

View File

@@ -0,0 +1,481 @@
/*
* Filters - Filtering on ObjectListViews
*
* Author: Phillip Piper
* Date: 03/03/2010 17:00
*
* Change log:
* 2011-03-01 JPP Added CompositeAllFilter, CompositeAnyFilter and OneOfFilter
* v2.4.1
* 2010-06-23 JPP Extended TextMatchFilter to handle regular expressions and string prefix matching.
* v2.4
* 2010-03-03 JPP Initial version
*
* TO DO:
*
* Copyright (C) 2010-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using System.Drawing;
namespace BrightIdeasSoftware
{
/// <summary>
/// Interface for model-by-model filtering
/// </summary>
public interface IModelFilter
{
/// <summary>
/// Should the given model be included when this filter is installed
/// </summary>
/// <param name="modelObject">The model object to consider</param>
/// <returns>Returns true if the model will be included by the filter</returns>
bool Filter(object modelObject);
}
/// <summary>
/// Interface for whole list filtering
/// </summary>
public interface IListFilter
{
/// <summary>
/// Return a subset of the given list of model objects as the new
/// contents of the ObjectListView
/// </summary>
/// <param name="modelObjects">The collection of model objects that the list will possibly display</param>
/// <returns>The filtered collection that holds the model objects that will be displayed.</returns>
IEnumerable Filter(IEnumerable modelObjects);
}
/// <summary>
/// Base class for model-by-model filters
/// </summary>
public class AbstractModelFilter : IModelFilter
{
/// <summary>
/// Should the given model be included when this filter is installed
/// </summary>
/// <param name="modelObject">The model object to consider</param>
/// <returns>Returns true if the model will be included by the filter</returns>
virtual public bool Filter(object modelObject) {
return true;
}
}
/// <summary>
/// This filter calls a given Predicate to decide if a model object should be included
/// </summary>
public class ModelFilter : IModelFilter
{
/// <summary>
/// Create a filter based on the given predicate
/// </summary>
/// <param name="predicate">The function that will filter objects</param>
public ModelFilter(Predicate<object> predicate) {
this.Predicate = predicate;
}
/// <summary>
/// Gets or sets the predicate used to filter model objects
/// </summary>
protected Predicate<object> Predicate {
get { return predicate; }
set { predicate = value; }
}
private Predicate<object> predicate;
/// <summary>
/// Should the given model object be included?
/// </summary>
/// <param name="modelObject"></param>
/// <returns></returns>
virtual public bool Filter(object modelObject) {
return this.Predicate == null ? true : this.Predicate(modelObject);
}
}
/// <summary>
/// A CompositeFilter joins several other filters together.
/// If there are no filters, all model objects are included
/// </summary>
abstract public class CompositeFilter : IModelFilter {
/// <summary>
/// Create an empty filter
/// </summary>
public CompositeFilter() {
}
/// <summary>
/// Create a composite filter from the given list of filters
/// </summary>
/// <param name="filters">A list of filters</param>
public CompositeFilter(IEnumerable<IModelFilter> filters) {
foreach (IModelFilter filter in filters) {
if (filter != null)
Filters.Add(filter);
}
}
/// <summary>
/// Gets or sets the filters used by this composite
/// </summary>
public IList<IModelFilter> Filters {
get { return filters; }
set { filters = value; }
}
private IList<IModelFilter> filters = new List<IModelFilter>();
/// <summary>
/// Get the sub filters that are text match filters
/// </summary>
public IEnumerable<TextMatchFilter> TextFilters {
get {
foreach (IModelFilter filter in this.Filters) {
TextMatchFilter textFilter = filter as TextMatchFilter;
if (textFilter != null)
yield return textFilter;
}
}
}
/// <summary>
/// Decide whether or not the given model should be included by the filter
/// </summary>
/// <param name="modelObject"></param>
/// <returns>True if the object is included by the filter</returns>
virtual public bool Filter(object modelObject) {
if (this.Filters == null || this.Filters.Count == 0)
return true;
return this.FilterObject(modelObject);
}
/// <summary>
/// Decide whether or not the given model should be included by the filter
/// </summary>
/// <remarks>Filters is guaranteed to be non-empty when this method is called</remarks>
/// <param name="modelObject">The model object under consideration</param>
/// <returns>True if the object is included by the filter</returns>
abstract public bool FilterObject(object modelObject);
}
/// <summary>
/// A CompositeAllFilter joins several other filters together.
/// A model object must satisfy all filters to be included.
/// If there are no filters, all model objects are included
/// </summary>
/// <remarks>
/// Create a filter
/// </remarks>
/// <param name="filters"></param>
public class CompositeAllFilter(List<IModelFilter> filters) : CompositeFilter(filters) {
/// <summary>
/// Decide whether or not the given model should be included by the filter
/// </summary>
/// <remarks>Filters is guaranteed to be non-empty when this method is called</remarks>
/// <param name="modelObject">The model object under consideration</param>
/// <returns>True if the object is included by the filter</returns>
override public bool FilterObject(object modelObject) {
foreach (IModelFilter filter in this.Filters)
if (!filter.Filter(modelObject))
return false;
return true;
}
}
/// <summary>
/// A CompositeAllFilter joins several other filters together.
/// A model object must only satisfy one of the filters to be included.
/// If there are no filters, all model objects are included
/// </summary>
/// <remarks>
/// Create a filter from the given filters
/// </remarks>
/// <param name="filters"></param>
public class CompositeAnyFilter(List<IModelFilter> filters) : CompositeFilter(filters) {
/// <summary>
/// Decide whether or not the given model should be included by the filter
/// </summary>
/// <remarks>Filters is guaranteed to be non-empty when this method is called</remarks>
/// <param name="modelObject">The model object under consideration</param>
/// <returns>True if the object is included by the filter</returns>
override public bool FilterObject(object modelObject) {
foreach (IModelFilter filter in this.Filters)
if (filter.Filter(modelObject))
return true;
return false;
}
}
/// <summary>
/// Instances of this class extract a value from the model object
/// and compare that value to a list of fixed values. The model
/// object is included if the extracted value is in the list
/// </summary>
/// <remarks>If there is no delegate installed or there are
/// no values to match, no model objects will be matched</remarks>
public class OneOfFilter : IModelFilter {
/// <summary>
/// Create a filter that will use the given delegate to extract values
/// </summary>
/// <param name="valueGetter"></param>
public OneOfFilter(AspectGetterDelegate valueGetter) :
this(valueGetter, new ArrayList()) {
}
/// <summary>
/// Create a filter that will extract values using the given delegate
/// and compare them to the values in the given list.
/// </summary>
/// <param name="valueGetter"></param>
/// <param name="possibleValues"></param>
public OneOfFilter(AspectGetterDelegate valueGetter, ICollection possibleValues) {
this.ValueGetter = valueGetter;
this.PossibleValues = new ArrayList(possibleValues);
}
/// <summary>
/// Gets or sets the delegate that will be used to extract values
/// from model objects
/// </summary>
virtual public AspectGetterDelegate ValueGetter {
get { return valueGetter; }
set { valueGetter = value; }
}
private AspectGetterDelegate valueGetter;
/// <summary>
/// Gets or sets the list of values that the value extracted from
/// the model object must match in order to be included.
/// </summary>
virtual public IList PossibleValues {
get { return possibleValues; }
set { possibleValues = value; }
}
private IList possibleValues;
/// <summary>
/// Should the given model object be included?
/// </summary>
/// <param name="modelObject"></param>
/// <returns></returns>
public virtual bool Filter(object modelObject) {
if (this.ValueGetter == null || this.PossibleValues == null || this.PossibleValues.Count == 0)
return false;
object result = this.ValueGetter(modelObject);
IEnumerable enumerable = result as IEnumerable;
if (result is string || enumerable == null)
return this.DoesValueMatch(result);
foreach (object x in enumerable) {
if (this.DoesValueMatch(x))
return true;
}
return false;
}
/// <summary>
/// Decides if the given property is a match for the values in the PossibleValues collection
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
protected virtual bool DoesValueMatch(object result) {
return this.PossibleValues.Contains(result);
}
}
/// <summary>
/// Instances of this class match a property of a model objects against
/// a list of bit flags. The property should be an xor-ed collection
/// of bits flags.
/// </summary>
/// <remarks>Both the property compared and the list of possible values
/// must be convertible to ulongs.</remarks>
public class FlagBitSetFilter : OneOfFilter {
/// <summary>
/// Create an instance
/// </summary>
/// <param name="valueGetter"></param>
/// <param name="possibleValues"></param>
public FlagBitSetFilter(AspectGetterDelegate valueGetter, ICollection possibleValues) : base(valueGetter, possibleValues) {
this.ConvertPossibleValues();
}
/// <summary>
/// Gets or sets the collection of values that will be matched.
/// These must be ulongs (or convertible to ulongs).
/// </summary>
public override IList PossibleValues {
get { return base.PossibleValues; }
set {
base.PossibleValues = value;
this.ConvertPossibleValues();
}
}
private void ConvertPossibleValues() {
this.possibleValuesAsUlongs = new List<UInt64>();
foreach (object x in this.PossibleValues)
this.possibleValuesAsUlongs.Add(Convert.ToUInt64(x));
}
/// <summary>
/// Decides if the given property is a match for the values in the PossibleValues collection
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
protected override bool DoesValueMatch(object result) {
try {
UInt64 value = Convert.ToUInt64(result);
foreach (ulong flag in this.possibleValuesAsUlongs) {
if ((value & flag) == flag)
return true;
}
return false;
}
catch (InvalidCastException) {
return false;
}
catch (FormatException) {
return false;
}
}
private List<UInt64> possibleValuesAsUlongs = new List<UInt64>();
}
/// <summary>
/// Base class for whole list filters
/// </summary>
public class AbstractListFilter : IListFilter
{
/// <summary>
/// Return a subset of the given list of model objects as the new
/// contents of the ObjectListView
/// </summary>
/// <param name="modelObjects">The collection of model objects that the list will possibly display</param>
/// <returns>The filtered collection that holds the model objects that will be displayed.</returns>
virtual public IEnumerable Filter(IEnumerable modelObjects) {
return modelObjects;
}
}
/// <summary>
/// Instance of this class implement delegate based whole list filtering
/// </summary>
public class ListFilter : AbstractListFilter
{
/// <summary>
/// A delegate that filters on a whole list
/// </summary>
/// <param name="rowObjects"></param>
/// <returns></returns>
public delegate IEnumerable ListFilterDelegate(IEnumerable rowObjects);
/// <summary>
/// Create a ListFilter
/// </summary>
/// <param name="function"></param>
public ListFilter(ListFilterDelegate function) {
this.Function = function;
}
/// <summary>
/// Gets or sets the delegate that will filter the list
/// </summary>
public ListFilterDelegate Function {
get { return function; }
set { function = value; }
}
private ListFilterDelegate function;
/// <summary>
/// Do the actual work of filtering
/// </summary>
/// <param name="modelObjects"></param>
/// <returns></returns>
public override IEnumerable Filter(IEnumerable modelObjects) {
if (this.Function == null)
return modelObjects;
return this.Function(modelObjects);
}
}
/// <summary>
/// Filter the list so only the last N entries are displayed
/// </summary>
public class TailFilter : AbstractListFilter
{
/// <summary>
/// Create a no-op tail filter
/// </summary>
public TailFilter() {
}
/// <summary>
/// Create a filter that includes on the last N model objects
/// </summary>
/// <param name="numberOfObjects"></param>
public TailFilter(int numberOfObjects) {
this.Count = numberOfObjects;
}
/// <summary>
/// Gets or sets the number of model objects that will be
/// returned from the tail of the list
/// </summary>
public int Count {
get { return count; }
set { count = value; }
}
private int count;
/// <summary>
/// Return the last N subset of the model objects
/// </summary>
/// <param name="modelObjects"></param>
/// <returns></returns>
public override IEnumerable Filter(IEnumerable modelObjects) {
if (this.Count <= 0)
return modelObjects;
ArrayList list = ObjectListView.EnumerableToArray(modelObjects, false);
if (this.Count > list.Count)
return list;
object[] tail = new object[this.Count];
list.CopyTo(list.Count - this.Count, tail, 0, this.Count);
return new ArrayList(tail);
}
}
}

View File

@@ -0,0 +1,160 @@
/*
* FlagClusteringStrategy - Implements a clustering strategy for a field which is a single integer
* containing an XOR'ed collection of bit flags
*
* Author: Phillip Piper
* Date: 23-March-2012 8:33 am
*
* Change log:
* 2012-03-23 JPP - First version
*
* Copyright (C) 2012 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
namespace BrightIdeasSoftware {
/// <summary>
/// Instances of this class cluster model objects on the basis of a
/// property that holds an xor-ed collection of bit flags.
/// </summary>
public class FlagClusteringStrategy : ClusteringStrategy
{
#region Life and death
/// <summary>
/// Create a clustering strategy that operates on the flags of the given enum
/// </summary>
/// <param name="enumType"></param>
public FlagClusteringStrategy(Type enumType) {
if (enumType == null) throw new ArgumentNullException("enumType");
if (!enumType.IsEnum) throw new ArgumentException("Type must be enum", "enumType");
if (enumType.GetCustomAttributes(typeof(FlagsAttribute), false) == null) throw new ArgumentException("Type must have [Flags] attribute", "enumType");
List<long> flags = new List<long>();
foreach (object x in Enum.GetValues(enumType))
flags.Add(Convert.ToInt64(x));
List<string> flagLabels = new List<string>();
foreach (string x in Enum.GetNames(enumType))
flagLabels.Add(x);
this.SetValues(flags.ToArray(), flagLabels.ToArray());
}
/// <summary>
/// Create a clustering strategy around the given collections of flags and their display labels.
/// There must be the same number of elements in both collections.
/// </summary>
/// <param name="values">The list of flags. </param>
/// <param name="labels"></param>
public FlagClusteringStrategy(long[] values, string[] labels) {
this.SetValues(values, labels);
}
#endregion
#region Implementation
/// <summary>
/// Gets the value that will be xor-ed to test for the presence of a particular value.
/// </summary>
public long[] Values {
get { return this.values; }
private set { this.values = value; }
}
private long[] values;
/// <summary>
/// Gets the labels that will be used when the corresponding Value is XOR present in the data.
/// </summary>
public string[] Labels {
get { return this.labels; }
private set { this.labels = value; }
}
private string[] labels;
private void SetValues(long[] flags, string[] flagLabels) {
if (flags == null || flags.Length == 0) throw new ArgumentNullException("flags");
if (flagLabels == null || flagLabels.Length == 0) throw new ArgumentNullException("flagLabels");
if (flags.Length != flagLabels.Length) throw new ArgumentException("values and labels must have the same number of entries", "flags");
this.Values = flags;
this.Labels = flagLabels;
}
#endregion
#region Implementation of IClusteringStrategy
/// <summary>
/// Get the cluster key by which the given model will be partitioned by this strategy
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public override object GetClusterKey(object model) {
List<long> flags = new List<long>();
try {
long modelValue = Convert.ToInt64(this.Column.GetValue(model));
foreach (long x in this.Values) {
if ((x & modelValue) == x)
flags.Add(x);
}
return flags;
}
catch (InvalidCastException ex) {
System.Diagnostics.Debug.Write(ex);
return flags;
}
catch (FormatException ex) {
System.Diagnostics.Debug.Write(ex);
return flags;
}
}
/// <summary>
/// Gets the display label that the given cluster should use
/// </summary>
/// <param name="cluster"></param>
/// <returns></returns>
public override string GetClusterDisplayLabel(ICluster cluster) {
long clusterKeyAsUlong = Convert.ToInt64(cluster.ClusterKey);
for (int i = 0; i < this.Values.Length; i++ ) {
if (clusterKeyAsUlong == this.Values[i])
return this.ApplyDisplayFormat(cluster, this.Labels[i]);
}
return this.ApplyDisplayFormat(cluster, clusterKeyAsUlong.ToString(CultureInfo.CurrentUICulture));
}
/// <summary>
/// Create a filter that will include only model objects that
/// match one or more of the given values.
/// </summary>
/// <param name="valuesChosenForFiltering"></param>
/// <returns></returns>
public override IModelFilter CreateFilter(IList valuesChosenForFiltering) {
return new FlagBitSetFilter(this.GetClusterKey, valuesChosenForFiltering);
}
#endregion
}
}

View File

@@ -0,0 +1,56 @@
/*
* ICluster - A cluster is a group of objects that can be included or excluded as a whole
*
* Author: Phillip Piper
* Date: 4-March-2011 11:59 pm
*
* Change log:
* 2011-03-04 JPP - First version
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace BrightIdeasSoftware {
/// <summary>
/// A cluster is a like collection of objects that can be usefully filtered
/// as whole using the filtering UI provided by the ObjectListView.
/// </summary>
public interface ICluster : IComparable {
/// <summary>
/// Gets or sets how many items belong to this cluster
/// </summary>
int Count { get; set; }
/// <summary>
/// Gets or sets the label that will be shown to the user to represent
/// this cluster
/// </summary>
string DisplayLabel { get; set; }
/// <summary>
/// Gets or sets the actual data object that all members of this cluster
/// have commonly returned.
/// </summary>
object ClusterKey { get; set; }
}
}

View File

@@ -0,0 +1,80 @@
/*
* IClusterStrategy - Encapsulates the ability to create a list of clusters from an ObjectListView
*
* Author: Phillip Piper
* Date: 4-March-2011 11:59 pm
*
* Change log:
* 2012-05-23 JPP - Added CreateFilter() method to interface to allow the strategy
* to control the actual model filter that is created.
* v2.5
* 2011-03-04 JPP - First version
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace BrightIdeasSoftware{
/// <summary>
/// Implementation of this interface control the selecting of cluster keys
/// and how those clusters will be presented to the user
/// </summary>
public interface IClusteringStrategy {
/// <summary>
/// Gets or sets the column upon which this strategy will operate
/// </summary>
OLVColumn Column { get; set; }
/// <summary>
/// Get the cluster key by which the given model will be partitioned by this strategy
/// </summary>
/// <remarks>If the returned value is an IEnumerable, the given model is considered
/// to belong to multiple clusters</remarks>
/// <param name="model"></param>
/// <returns></returns>
object GetClusterKey(object model);
/// <summary>
/// Create a cluster to hold the given cluster key
/// </summary>
/// <param name="clusterKey"></param>
/// <returns></returns>
ICluster CreateCluster(object clusterKey);
/// <summary>
/// Gets the display label that the given cluster should use
/// </summary>
/// <param name="cluster"></param>
/// <returns></returns>
string GetClusterDisplayLabel(ICluster cluster);
/// <summary>
/// Create a filter that will include only model objects that
/// match one or more of the given values.
/// </summary>
/// <param name="valuesChosenForFiltering"></param>
/// <returns></returns>
IModelFilter CreateFilter(IList valuesChosenForFiltering);
}
}

View File

@@ -0,0 +1,629 @@
/*
* TextMatchFilter - Text based filtering on ObjectListViews
*
* Author: Phillip Piper
* Date: 31/05/2011 7:45am
*
* Change log:
* v2.6
* 2012-10-13 JPP Allow filtering to consider additional columns
* v2.5.1
* 2011-06-22 JPP Handle searching for empty strings
* v2.5.0
* 2011-05-31 JPP Initial version
*
* TO DO:
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Drawing;
namespace BrightIdeasSoftware {
/// <summary>
/// Instances of this class include only those rows of the listview
/// that match one or more given strings.
/// </summary>
/// <remarks>This class can match strings by prefix, regex, or simple containment.
/// There are factory methods for each of these matching strategies.</remarks>
public class TextMatchFilter : AbstractModelFilter {
#region Life and death
/// <summary>
/// Create a text filter that will include rows where any cell matches
/// any of the given regex expressions.
/// </summary>
/// <param name="olv"></param>
/// <param name="texts"></param>
/// <returns></returns>
/// <remarks>Any string that is not a valid regex expression will be ignored.</remarks>
public static TextMatchFilter Regex(ObjectListView olv, params string[] texts) {
TextMatchFilter filter = new TextMatchFilter(olv);
filter.RegexStrings = texts;
return filter;
}
/// <summary>
/// Create a text filter that includes rows where any cell begins with one of the given strings
/// </summary>
/// <param name="olv"></param>
/// <param name="texts"></param>
/// <returns></returns>
public static TextMatchFilter Prefix(ObjectListView olv, params string[] texts) {
TextMatchFilter filter = new TextMatchFilter(olv);
filter.PrefixStrings = texts;
return filter;
}
/// <summary>
/// Create a text filter that includes rows where any cell contains any of the given strings.
/// </summary>
/// <param name="olv"></param>
/// <param name="texts"></param>
/// <returns></returns>
public static TextMatchFilter Contains(ObjectListView olv, params string[] texts) {
TextMatchFilter filter = new TextMatchFilter(olv);
filter.ContainsStrings = texts;
return filter;
}
/// <summary>
/// Create a TextFilter
/// </summary>
/// <param name="olv"></param>
public TextMatchFilter(ObjectListView olv) {
this.ListView = olv;
}
/// <summary>
/// Create a TextFilter that finds the given string
/// </summary>
/// <param name="olv"></param>
/// <param name="text"></param>
public TextMatchFilter(ObjectListView olv, string text) {
this.ListView = olv;
this.ContainsStrings = new string[] { text };
}
/// <summary>
/// Create a TextFilter that finds the given string using the given comparison
/// </summary>
/// <param name="olv"></param>
/// <param name="text"></param>
/// <param name="comparison"></param>
public TextMatchFilter(ObjectListView olv, string text, StringComparison comparison) {
this.ListView = olv;
this.ContainsStrings = new string[] { text };
this.StringComparison = comparison;
}
#endregion
#region Public properties
/// <summary>
/// Gets or sets which columns will be used for the comparisons? If this is null, all columns will be used
/// </summary>
public OLVColumn[] Columns {
get { return columns; }
set { columns = value; }
}
private OLVColumn[] columns;
/// <summary>
/// Gets or sets additional columns which will be used in the comparison. These will be used
/// in addition to either the Columns property or to all columns taken from the control.
/// </summary>
public OLVColumn[] AdditionalColumns {
get { return additionalColumns; }
set { additionalColumns = value; }
}
private OLVColumn[] additionalColumns;
/// <summary>
/// Gets or sets the collection of strings that will be used for
/// contains matching. Setting this replaces all previous texts
/// of any kind.
/// </summary>
public IEnumerable<string> ContainsStrings {
get {
foreach (TextMatchingStrategy component in this.MatchingStrategies)
yield return component.Text;
}
set {
this.MatchingStrategies = new List<TextMatchingStrategy>();
if (value != null) {
foreach (string text in value)
this.MatchingStrategies.Add(new TextContainsMatchingStrategy(this, text));
}
}
}
/// <summary>
/// Gets whether or not this filter has any search criteria
/// </summary>
public bool HasComponents {
get {
return this.MatchingStrategies.Count > 0;
}
}
/// <summary>
/// Gets or set the ObjectListView upon which this filter will work
/// </summary>
/// <remarks>
/// You cannot really rebase a filter after it is created, so do not change this value.
/// It is included so that it can be set in an object initializer.
/// </remarks>
public ObjectListView ListView {
get { return listView; }
set { listView = value; }
}
private ObjectListView listView;
/// <summary>
/// Gets or sets the collection of strings that will be used for
/// prefix matching. Setting this replaces all previous texts
/// of any kind.
/// </summary>
public IEnumerable<string> PrefixStrings {
get {
foreach (TextMatchingStrategy component in this.MatchingStrategies)
yield return component.Text;
}
set {
this.MatchingStrategies = new List<TextMatchingStrategy>();
if (value != null) {
foreach (string text in value)
this.MatchingStrategies.Add(new TextBeginsMatchingStrategy(this, text));
}
}
}
/// <summary>
/// Gets or sets the options that will be used when compiling the regular expression.
/// </summary>
/// <remarks>
/// This is only used when doing Regex matching (obviously).
/// If this is not set specifically, the appropriate options are chosen to match the
/// StringComparison setting (culture invariant, case sensitive).
/// </remarks>
public RegexOptions RegexOptions {
get {
if (!regexOptions.HasValue) {
switch (this.StringComparison) {
case StringComparison.CurrentCulture:
regexOptions = RegexOptions.None;
break;
case StringComparison.CurrentCultureIgnoreCase:
regexOptions = RegexOptions.IgnoreCase;
break;
case StringComparison.Ordinal:
case StringComparison.InvariantCulture:
regexOptions = RegexOptions.CultureInvariant;
break;
case StringComparison.OrdinalIgnoreCase:
case StringComparison.InvariantCultureIgnoreCase:
regexOptions = RegexOptions.CultureInvariant | RegexOptions.IgnoreCase;
break;
default:
regexOptions = RegexOptions.None;
break;
}
}
return regexOptions.Value;
}
set {
regexOptions = value;
}
}
private RegexOptions? regexOptions;
/// <summary>
/// Gets or sets the collection of strings that will be used for
/// regex pattern matching. Setting this replaces all previous texts
/// of any kind.
/// </summary>
public IEnumerable<string> RegexStrings {
get {
foreach (TextMatchingStrategy component in this.MatchingStrategies)
yield return component.Text;
}
set {
this.MatchingStrategies = new List<TextMatchingStrategy>();
if (value != null) {
foreach (string text in value)
this.MatchingStrategies.Add(new TextRegexMatchingStrategy(this, text));
}
}
}
/// <summary>
/// Gets or sets how the filter will match text
/// </summary>
public StringComparison StringComparison {
get { return this.stringComparison; }
set { this.stringComparison = value; }
}
private StringComparison stringComparison = StringComparison.InvariantCultureIgnoreCase;
#endregion
#region Implementation
/// <summary>
/// Loop over the columns that are being considering by the filter
/// </summary>
/// <returns></returns>
protected virtual IEnumerable<OLVColumn> IterateColumns() {
if (this.Columns == null) {
foreach (OLVColumn column in this.ListView.Columns)
yield return column;
} else {
foreach (OLVColumn column in this.Columns)
yield return column;
}
if (this.AdditionalColumns != null) {
foreach (OLVColumn column in this.AdditionalColumns)
yield return column;
}
}
#endregion
#region Public interface
/// <summary>
/// Do the actual work of filtering
/// </summary>
/// <param name="modelObject"></param>
/// <returns></returns>
public override bool Filter(object modelObject) {
if (this.ListView == null || !this.HasComponents)
return true;
foreach (OLVColumn column in this.IterateColumns()) {
if (column.IsVisible && column.Searchable) {
string[] cellTexts = column.GetSearchValues(modelObject);
if (cellTexts != null && cellTexts.Length > 0) {
foreach (TextMatchingStrategy filter in this.MatchingStrategies) {
if (String.IsNullOrEmpty(filter.Text))
return true;
foreach (string cellText in cellTexts) {
if (filter.MatchesText(cellText))
return true;
}
}
}
}
}
return false;
}
/// <summary>
/// Find all the ways in which this filter matches the given string.
/// </summary>
/// <remarks>This is used by the renderer to decide which bits of
/// the string should be highlighted</remarks>
/// <param name="cellText"></param>
/// <returns>A list of character ranges indicating the matched substrings</returns>
public IEnumerable<CharacterRange> FindAllMatchedRanges(string cellText) {
List<CharacterRange> ranges = new List<CharacterRange>();
foreach (TextMatchingStrategy filter in this.MatchingStrategies) {
if (!String.IsNullOrEmpty(filter.Text))
ranges.AddRange(filter.FindAllMatchedRanges(cellText));
}
return ranges;
}
/// <summary>
/// Is the given column one of the columns being used by this filter?
/// </summary>
/// <param name="column"></param>
/// <returns></returns>
public bool IsIncluded(OLVColumn column) {
if (this.Columns == null) {
return column.ListView == this.ListView;
}
foreach (OLVColumn x in this.Columns) {
if (x == column)
return true;
}
return false;
}
#endregion
#region Implementation members
private List<TextMatchingStrategy> MatchingStrategies = new List<TextMatchingStrategy>();
#endregion
#region Components
/// <summary>
/// Base class for the various types of string matching that TextMatchFilter provides
/// </summary>
abstract protected class TextMatchingStrategy {
/// <summary>
/// Gets how the filter will match text
/// </summary>
public StringComparison StringComparison {
get { return this.TextFilter.StringComparison; }
}
/// <summary>
/// Gets the text filter to which this component belongs
/// </summary>
public TextMatchFilter TextFilter {
get { return textFilter; }
set { textFilter = value; }
}
private TextMatchFilter textFilter;
/// <summary>
/// Gets or sets the text that will be matched
/// </summary>
public string Text {
get { return this.text; }
set { this.text = value; }
}
private string text;
/// <summary>
/// Find all the ways in which this filter matches the given string.
/// </summary>
/// <remarks>
/// <para>
/// This is used by the renderer to decide which bits of
/// the string should be highlighted.
/// </para>
/// <para>this.Text will not be null or empty when this is called.</para>
/// </remarks>
/// <param name="cellText">The text of the cell we want to search</param>
/// <returns>A list of character ranges indicating the matched substrings</returns>
abstract public IEnumerable<CharacterRange> FindAllMatchedRanges(string cellText);
/// <summary>
/// Does the given text match the filter
/// </summary>
/// <remarks>
/// <para>this.Text will not be null or empty when this is called.</para>
/// </remarks>
/// <param name="cellText">The text of the cell we want to search</param>
/// <returns>Return true if the given cellText matches our strategy</returns>
abstract public bool MatchesText(string cellText);
}
/// <summary>
/// This component provides text contains matching strategy.
/// </summary>
protected class TextContainsMatchingStrategy : TextMatchingStrategy {
/// <summary>
/// Create a text contains strategy
/// </summary>
/// <param name="filter"></param>
/// <param name="text"></param>
public TextContainsMatchingStrategy(TextMatchFilter filter, string text) {
this.TextFilter = filter;
this.Text = text;
}
/// <summary>
/// Does the given text match the filter
/// </summary>
/// <remarks>
/// <para>this.Text will not be null or empty when this is called.</para>
/// </remarks>
/// <param name="cellText">The text of the cell we want to search</param>
/// <returns>Return true if the given cellText matches our strategy</returns>
override public bool MatchesText(string cellText) {
return cellText.IndexOf(this.Text, this.StringComparison) != -1;
}
/// <summary>
/// Find all the ways in which this filter matches the given string.
/// </summary>
/// <remarks>
/// <para>
/// This is used by the renderer to decide which bits of
/// the string should be highlighted.
/// </para>
/// <para>this.Text will not be null or empty when this is called.</para>
/// </remarks>
/// <param name="cellText">The text of the cell we want to search</param>
/// <returns>A list of character ranges indicating the matched substrings</returns>
override public IEnumerable<CharacterRange> FindAllMatchedRanges(string cellText) {
List<CharacterRange> ranges = new List<CharacterRange>();
int matchIndex = cellText.IndexOf(this.Text, this.StringComparison);
while (matchIndex != -1) {
ranges.Add(new CharacterRange(matchIndex, this.Text.Length));
matchIndex = cellText.IndexOf(this.Text, matchIndex + this.Text.Length, this.StringComparison);
}
return ranges;
}
}
/// <summary>
/// This component provides text begins with matching strategy.
/// </summary>
protected class TextBeginsMatchingStrategy : TextMatchingStrategy {
/// <summary>
/// Create a text begins strategy
/// </summary>
/// <param name="filter"></param>
/// <param name="text"></param>
public TextBeginsMatchingStrategy(TextMatchFilter filter, string text) {
this.TextFilter = filter;
this.Text = text;
}
/// <summary>
/// Does the given text match the filter
/// </summary>
/// <remarks>
/// <para>this.Text will not be null or empty when this is called.</para>
/// </remarks>
/// <param name="cellText">The text of the cell we want to search</param>
/// <returns>Return true if the given cellText matches our strategy</returns>
override public bool MatchesText(string cellText) {
return cellText.StartsWith(this.Text, this.StringComparison);
}
/// <summary>
/// Find all the ways in which this filter matches the given string.
/// </summary>
/// <remarks>
/// <para>
/// This is used by the renderer to decide which bits of
/// the string should be highlighted.
/// </para>
/// <para>this.Text will not be null or empty when this is called.</para>
/// </remarks>
/// <param name="cellText">The text of the cell we want to search</param>
/// <returns>A list of character ranges indicating the matched substrings</returns>
override public IEnumerable<CharacterRange> FindAllMatchedRanges(string cellText) {
List<CharacterRange> ranges = new List<CharacterRange>();
if (cellText.StartsWith(this.Text, this.StringComparison))
ranges.Add(new CharacterRange(0, this.Text.Length));
return ranges;
}
}
/// <summary>
/// This component provides regex matching strategy.
/// </summary>
protected class TextRegexMatchingStrategy : TextMatchingStrategy {
/// <summary>
/// Creates a regex strategy
/// </summary>
/// <param name="filter"></param>
/// <param name="text"></param>
public TextRegexMatchingStrategy(TextMatchFilter filter, string text) {
this.TextFilter = filter;
this.Text = text;
}
/// <summary>
/// Gets or sets the options that will be used when compiling the regular expression.
/// </summary>
public RegexOptions RegexOptions {
get {
return this.TextFilter.RegexOptions;
}
}
/// <summary>
/// Gets or sets a compilex regular expression, based on our current Text and RegexOptions.
/// </summary>
/// <remarks>
/// If Text fails to compile as a regular expression, this will return a Regex object
/// that will match all strings.
/// </remarks>
protected Regex Regex {
get {
if (this.regex == null) {
try {
this.regex = new Regex(this.Text, this.RegexOptions);
}
catch (ArgumentException) {
this.regex = TextRegexMatchingStrategy.InvalidRegexMarker;
}
}
return this.regex;
}
set {
this.regex = value;
}
}
private Regex regex;
/// <summary>
/// Gets whether or not our current regular expression is a valid regex
/// </summary>
protected bool IsRegexInvalid {
get {
return this.Regex == TextRegexMatchingStrategy.InvalidRegexMarker;
}
}
static private Regex InvalidRegexMarker = new Regex(".*");
/// <summary>
/// Does the given text match the filter
/// </summary>
/// <remarks>
/// <para>this.Text will not be null or empty when this is called.</para>
/// </remarks>
/// <param name="cellText">The text of the cell we want to search</param>
/// <returns>Return true if the given cellText matches our strategy</returns>
public override bool MatchesText(string cellText) {
if (this.IsRegexInvalid)
return true;
return this.Regex.Match(cellText).Success;
}
/// <summary>
/// Find all the ways in which this filter matches the given string.
/// </summary>
/// <remarks>
/// <para>
/// This is used by the renderer to decide which bits of
/// the string should be highlighted.
/// </para>
/// <para>this.Text will not be null or empty when this is called.</para>
/// </remarks>
/// <param name="cellText">The text of the cell we want to search</param>
/// <returns>A list of character ranges indicating the matched substrings</returns>
override public IEnumerable<CharacterRange> FindAllMatchedRanges(string cellText) {
List<CharacterRange> ranges = new List<CharacterRange>();
if (!this.IsRegexInvalid) {
foreach (Match match in this.Regex.Matches(cellText)) {
if (match.Length > 0)
ranges.Add(new CharacterRange(match.Index, match.Length));
}
}
return ranges;
}
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,335 @@
/*
* Attributes - Attributes that can be attached to properties of models to allow columns to be
* built from them directly
*
* Author: Phillip Piper
* Date: 15/08/2009 22:01
*
* Change log:
* v2.6
* 2012-08-16 JPP - Added [OLVChildren] and [OLVIgnore]
* - OLV attributes can now only be set on properties
* v2.4
* 2010-04-14 JPP - Allow Name property to be set
*
* v2.3
* 2009-08-15 JPP - Initial version
*
* To do:
*
* Copyright (C) 2009-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace BrightIdeasSoftware
{
/// <summary>
/// This attribute is used to mark a property of a model
/// class that should be noticed by Generator class.
/// </summary>
/// <remarks>
/// All the attributes of this class match their equivilent properties on OLVColumn.
/// </remarks>
[AttributeUsage(AttributeTargets.Property)]
public class OLVColumnAttribute : Attribute
{
#region Constructor
// There are several property where we actually want nullable value (bool?, int?),
// but it seems attribute properties can't be nullable types.
// So we explicitly track if those properties have been set.
/// <summary>
/// Create a new OLVColumnAttribute
/// </summary>
public OLVColumnAttribute() {
}
/// <summary>
/// Create a new OLVColumnAttribute with the given title
/// </summary>
/// <param name="title">The title of the column</param>
public OLVColumnAttribute(string title) {
this.Title = title;
}
#endregion
#region Public properties
/// <summary>
///
/// </summary>
public string AspectToStringFormat {
get { return aspectToStringFormat; }
set { aspectToStringFormat = value; }
}
private string aspectToStringFormat;
/// <summary>
///
/// </summary>
public bool CheckBoxes {
get { return checkBoxes; }
set {
checkBoxes = value;
this.IsCheckBoxesSet = true;
}
}
private bool checkBoxes;
internal bool IsCheckBoxesSet = false;
/// <summary>
///
/// </summary>
public int DisplayIndex {
get { return displayIndex; }
set { displayIndex = value; }
}
private int displayIndex = -1;
/// <summary>
///
/// </summary>
public bool FillsFreeSpace {
get { return fillsFreeSpace; }
set { fillsFreeSpace = value; }
}
private bool fillsFreeSpace;
/// <summary>
///
/// </summary>
public int FreeSpaceProportion {
get { return freeSpaceProportion; }
set {
freeSpaceProportion = value;
IsFreeSpaceProportionSet = true;
}
}
private int freeSpaceProportion;
internal bool IsFreeSpaceProportionSet = false;
/// <summary>
/// An array of IComparables that mark the cutoff points for values when
/// grouping on this column.
/// </summary>
public object[] GroupCutoffs {
get { return groupCutoffs; }
set { groupCutoffs = value; }
}
private object[] groupCutoffs;
/// <summary>
///
/// </summary>
public string[] GroupDescriptions {
get { return groupDescriptions; }
set { groupDescriptions = value; }
}
private string[] groupDescriptions;
/// <summary>
///
/// </summary>
public string GroupWithItemCountFormat {
get { return groupWithItemCountFormat; }
set { groupWithItemCountFormat = value; }
}
private string groupWithItemCountFormat;
/// <summary>
///
/// </summary>
public string GroupWithItemCountSingularFormat {
get { return groupWithItemCountSingularFormat; }
set { groupWithItemCountSingularFormat = value; }
}
private string groupWithItemCountSingularFormat;
/// <summary>
///
/// </summary>
public bool Hyperlink {
get { return hyperlink; }
set { hyperlink = value; }
}
private bool hyperlink;
/// <summary>
///
/// </summary>
public string ImageAspectName {
get { return imageAspectName; }
set { imageAspectName = value; }
}
private string imageAspectName;
/// <summary>
///
/// </summary>
public bool IsEditable {
get { return isEditable; }
set {
isEditable = value;
this.IsEditableSet = true;
}
}
private bool isEditable = true;
internal bool IsEditableSet = false;
/// <summary>
///
/// </summary>
public bool IsVisible {
get { return isVisible; }
set { isVisible = value; }
}
private bool isVisible = true;
/// <summary>
///
/// </summary>
public bool IsTileViewColumn {
get { return isTileViewColumn; }
set { isTileViewColumn = value; }
}
private bool isTileViewColumn;
/// <summary>
///
/// </summary>
public int MaximumWidth {
get { return maximumWidth; }
set { maximumWidth = value; }
}
private int maximumWidth = -1;
/// <summary>
///
/// </summary>
public int MinimumWidth {
get { return minimumWidth; }
set { minimumWidth = value; }
}
private int minimumWidth = -1;
/// <summary>
///
/// </summary>
public String Name {
get { return name; }
set { name = value; }
}
private String name;
/// <summary>
///
/// </summary>
public HorizontalAlignment TextAlign {
get { return this.textAlign; }
set {
this.textAlign = value;
IsTextAlignSet = true;
}
}
private HorizontalAlignment textAlign = HorizontalAlignment.Left;
internal bool IsTextAlignSet = false;
/// <summary>
///
/// </summary>
public String Tag {
get { return tag; }
set { tag = value; }
}
private String tag;
/// <summary>
///
/// </summary>
public String Title {
get { return title; }
set { title = value; }
}
private String title;
/// <summary>
///
/// </summary>
public String ToolTipText {
get { return toolTipText; }
set { toolTipText = value; }
}
private String toolTipText;
/// <summary>
///
/// </summary>
public bool TriStateCheckBoxes {
get { return triStateCheckBoxes; }
set {
triStateCheckBoxes = value;
this.IsTriStateCheckBoxesSet = true;
}
}
private bool triStateCheckBoxes;
internal bool IsTriStateCheckBoxesSet = false;
/// <summary>
///
/// </summary>
public bool UseInitialLetterForGroup {
get { return useInitialLetterForGroup; }
set { useInitialLetterForGroup = value; }
}
private bool useInitialLetterForGroup;
/// <summary>
///
/// </summary>
public int Width {
get { return width; }
set { width = value; }
}
private int width = 150;
#endregion
}
/// <summary>
/// Properties marked with [OLVChildren] will be used as the children source in a TreeListView.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class OLVChildrenAttribute : Attribute
{
}
/// <summary>
/// Properties marked with [OLVIgnore] will not have columns generated for them.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class OLVIgnoreAttribute : Attribute
{
}
}

View File

@@ -0,0 +1,315 @@
/*
* Comparers - Various Comparer classes used within ObjectListView
*
* Author: Phillip Piper
* Date: 25/11/2008 17:15
*
* Change log:
* v2.8.1
* 2014-12-03 JPP - Added StringComparer
* v2.3
* 2009-08-24 JPP - Added OLVGroupComparer
* 2009-06-01 JPP - ModelObjectComparer would crash if secondary sort column was null.
* 2008-12-20 JPP - Fixed bug with group comparisons when a group key was null (SF#2445761)
* 2008-11-25 JPP Initial version
*
* TO DO:
*
* Copyright (C) 2006-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Forms;
namespace BrightIdeasSoftware
{
/// <summary>
/// ColumnComparer is the workhorse for all comparison between two values of a particular column.
/// If the column has a specific comparer, use that to compare the values. Otherwise, do
/// a case insensitive string compare of the string representations of the values.
/// </summary>
/// <remarks><para>This class inherits from both IComparer and its generic counterpart
/// so that it can be used on untyped and typed collections.</para>
/// <para>This is used by normal (non-virtual) ObjectListViews. Virtual lists use
/// ModelObjectComparer</para>
/// </remarks>
/// <remarks>
/// Create a ColumnComparer that will order the rows in a list view according
/// to the values in a given column
/// </remarks>
/// <param name="col">The column whose values will be compared</param>
/// <param name="order">The ordering for column values</param>
public class ColumnComparer(OLVColumn col, SortOrder order) : IComparer, IComparer<OLVListItem>
{
/// <summary>
/// Gets or sets the method that will be used to compare two strings.
/// The default is to compare on the current culture, case-insensitive
/// </summary>
public static StringCompareDelegate StringComparer
{
get { return stringComparer; }
set { stringComparer = value; }
}
private static StringCompareDelegate stringComparer;
/// <summary>
/// Create a ColumnComparer that will order the rows in a list view according
/// to the values in a given column, and by a secondary column if the primary
/// column is equal.
/// </summary>
/// <param name="col">The column whose values will be compared</param>
/// <param name="order">The ordering for column values</param>
/// <param name="col2">The column whose values will be compared for secondary sorting</param>
/// <param name="order2">The ordering for secondary column values</param>
public ColumnComparer(OLVColumn col, SortOrder order, OLVColumn col2, SortOrder order2)
: this(col, order)
{
// There is no point in secondary sorting on the same column
if (col != col2)
this.secondComparer = new ColumnComparer(col2, order2);
}
/// <summary>
/// Compare two rows
/// </summary>
/// <param name="x">row1</param>
/// <param name="y">row2</param>
/// <returns>An ordering indication: -1, 0, 1</returns>
public int Compare(object x, object y)
{
return this.Compare((OLVListItem)x, (OLVListItem)y);
}
/// <summary>
/// Compare two rows
/// </summary>
/// <param name="x">row1</param>
/// <param name="y">row2</param>
/// <returns>An ordering indication: -1, 0, 1</returns>
public int Compare(OLVListItem x, OLVListItem y)
{
if (this.sortOrder == SortOrder.None)
return 0;
int result = 0;
object x1 = this.column.GetValue(x.RowObject);
object y1 = this.column.GetValue(y.RowObject);
// Handle nulls. Null values come last
bool xIsNull = (x1 == null || x1 == System.DBNull.Value);
bool yIsNull = (y1 == null || y1 == System.DBNull.Value);
if (xIsNull || yIsNull) {
if (xIsNull && yIsNull)
result = 0;
else
result = (xIsNull ? -1 : 1);
} else {
result = this.CompareValues(x1, y1);
}
if (this.sortOrder == SortOrder.Descending)
result = 0 - result;
// If the result was equality, use the secondary comparer to resolve it
if (result == 0 && this.secondComparer != null)
result = this.secondComparer.Compare(x, y);
return result;
}
/// <summary>
/// Compare the actual values to be used for sorting
/// </summary>
/// <param name="x">The aspect extracted from the first row</param>
/// <param name="y">The aspect extracted from the second row</param>
/// <returns>An ordering indication: -1, 0, 1</returns>
public int CompareValues(object x, object y)
{
// Force case insensitive compares on strings
String xAsString = x as String;
if (xAsString != null)
return CompareStrings(xAsString, y as String);
IComparable comparable = x as IComparable;
return comparable != null ? comparable.CompareTo(y) : 0;
}
private static int CompareStrings(string x, string y)
{
if (StringComparer == null)
return String.Compare(x, y, StringComparison.CurrentCultureIgnoreCase);
else
return StringComparer(x, y);
}
private OLVColumn column = col;
private SortOrder sortOrder = order;
private ColumnComparer secondComparer;
}
/// <summary>
/// This comparer sort list view groups. OLVGroups have a "SortValue" property,
/// which is used if present. Otherwise, the titles of the groups will be compared.
/// </summary>
/// <remarks>
/// Create a group comparer
/// </remarks>
/// <param name="order">The ordering for column values</param>
public class OLVGroupComparer(SortOrder order) : IComparer<OLVGroup>
{
/// <summary>
/// Compare the two groups. OLVGroups have a "SortValue" property,
/// which is used if present. Otherwise, the titles of the groups will be compared.
/// </summary>
/// <param name="x">group1</param>
/// <param name="y">group2</param>
/// <returns>An ordering indication: -1, 0, 1</returns>
public int Compare(OLVGroup x, OLVGroup y) {
// If we can compare the sort values, do that.
// Otherwise do a case insensitive compare on the group header.
int result;
if (x.SortValue != null && y.SortValue != null)
result = x.SortValue.CompareTo(y.SortValue);
else
result = String.Compare(x.Header, y.Header, StringComparison.CurrentCultureIgnoreCase);
if (this.sortOrder == SortOrder.Descending)
result = 0 - result;
return result;
}
private SortOrder sortOrder = order;
}
/// <summary>
/// This comparer can be used to sort a collection of model objects by a given column
/// </summary>
/// <remarks>
/// <para>This is used by virtual ObjectListViews. Non-virtual lists use
/// ColumnComparer</para>
/// </remarks>
/// <remarks>
/// Create a model object comparer
/// </remarks>
/// <param name="col"></param>
/// <param name="order"></param>
public class ModelObjectComparer(OLVColumn col, SortOrder order) : IComparer, IComparer<object>
{
/// <summary>
/// Gets or sets the method that will be used to compare two strings.
/// The default is to compare on the current culture, case-insensitive
/// </summary>
public static StringCompareDelegate StringComparer
{
get { return stringComparer; }
set { stringComparer = value; }
}
private static StringCompareDelegate stringComparer;
/// <summary>
/// Create a model object comparer with a secondary sorting column
/// </summary>
/// <param name="col"></param>
/// <param name="order"></param>
/// <param name="col2"></param>
/// <param name="order2"></param>
public ModelObjectComparer(OLVColumn col, SortOrder order, OLVColumn col2, SortOrder order2)
: this(col, order)
{
// There is no point in secondary sorting on the same column
if (col != col2 && col2 != null && order2 != SortOrder.None)
this.secondComparer = new ModelObjectComparer(col2, order2);
}
/// <summary>
/// Compare the two model objects
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public int Compare(object x, object y)
{
int result = 0;
object x1 = this.column.GetValue(x);
object y1 = this.column.GetValue(y);
if (this.sortOrder == SortOrder.None)
return 0;
// Handle nulls. Null values come last
bool xIsNull = (x1 == null || x1 == System.DBNull.Value);
bool yIsNull = (y1 == null || y1 == System.DBNull.Value);
if (xIsNull || yIsNull) {
if (xIsNull && yIsNull)
result = 0;
else
result = (xIsNull ? -1 : 1);
} else {
result = this.CompareValues(x1, y1);
}
if (this.sortOrder == SortOrder.Descending)
result = 0 - result;
// If the result was equality, use the secondary comparer to resolve it
if (result == 0 && this.secondComparer != null)
result = this.secondComparer.Compare(x, y);
return result;
}
/// <summary>
/// Compare the actual values
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public int CompareValues(object x, object y)
{
// Force case insensitive compares on strings
String xStr = x as String;
if (xStr != null)
return CompareStrings(xStr, y as String);
IComparable comparable = x as IComparable;
return comparable != null ? comparable.CompareTo(y) : 0;
}
private static int CompareStrings(string x, string y)
{
if (StringComparer == null)
return String.Compare(x, y, StringComparison.CurrentCultureIgnoreCase);
else
return StringComparer(x, y);
}
private OLVColumn column = col;
private SortOrder sortOrder = order;
private ModelObjectComparer secondComparer;
#region IComparer<object> Members
#endregion
}
}

View File

@@ -0,0 +1,630 @@
/*
* DataSourceAdapter - A helper class that translates DataSource events for an ObjectListView
*
* Author: Phillip Piper
* Date: 20/09/2010 7:42 AM
*
* Change log:
* v2.9
* 2015-10-31 JPP - Put back sanity check on upper limit of source items
* 2015-02-02 JPP - Made CreateColumnsFromSource() only rebuild columns when new ones were added
* v2.8.1
* 2014-11-23 JPP - Honour initial CurrencyManager.Position when setting DataSource.
* 2014-10-27 JPP - Fix issue where SelectedObject was not sync'ed with CurrencyManager.Position (SF #129)
* v2.6
* 2012-08-16 JPP - Unify common column creation functionality with Generator when possible
*
* 2010-09-20 JPP - Initial version
*
* Copyright (C) 2010-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.ComponentModel;
using System.Data;
using System.Windows.Forms;
using System.Diagnostics;
namespace BrightIdeasSoftware
{
/// <summary>
/// A helper class that translates DataSource events for an ObjectListView
/// </summary>
public class DataSourceAdapter : IDisposable
{
#region Life and death
/// <summary>
/// Make a DataSourceAdapter
/// </summary>
public DataSourceAdapter(ObjectListView olv) {
if (olv == null) throw new ArgumentNullException("olv");
this.ListView = olv;
// ReSharper disable once DoNotCallOverridableMethodsInConstructor
this.BindListView(this.ListView);
}
/// <summary>
/// Finalize this object
/// </summary>
~DataSourceAdapter() {
this.Dispose(false);
}
/// <summary>
/// Release all the resources used by this instance
/// </summary>
public void Dispose() {
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Release all the resources used by this instance
/// </summary>
public virtual void Dispose(bool fromUser) {
this.UnbindListView(this.ListView);
this.UnbindDataSource();
}
#endregion
#region Public Properties
/// <summary>
/// Gets or sets whether or not columns will be automatically generated to show the
/// columns when the DataSource is set.
/// </summary>
/// <remarks>This must be set before the DataSource is set. It has no effect afterwards.</remarks>
public bool AutoGenerateColumns {
get { return this.autoGenerateColumns; }
set { this.autoGenerateColumns = value; }
}
private bool autoGenerateColumns = true;
/// <summary>
/// Get or set the DataSource that will be displayed in this list view.
/// </summary>
public virtual Object DataSource {
get { return dataSource; }
set {
dataSource = value;
this.RebindDataSource(true);
}
}
private Object dataSource;
/// <summary>
/// Gets or sets the name of the list or table in the data source for which the DataListView is displaying data.
/// </summary>
/// <remarks>If the data source is not a DataSet or DataViewManager, this property has no effect</remarks>
public virtual string DataMember {
get { return dataMember; }
set {
if (dataMember != value) {
dataMember = value;
RebindDataSource();
}
}
}
private string dataMember = "";
/// <summary>
/// Gets the ObjectListView upon which this adaptor will operate
/// </summary>
public ObjectListView ListView {
get { return listView; }
internal set { listView = value; }
}
private ObjectListView listView;
#endregion
#region Implementation properties
/// <summary>
/// Gets or sets the currency manager which is handling our binding context
/// </summary>
protected CurrencyManager CurrencyManager {
get { return currencyManager; }
set { currencyManager = value; }
}
private CurrencyManager currencyManager;
#endregion
#region Binding and unbinding
/// <summary>
///
/// </summary>
/// <param name="olv"></param>
protected virtual void BindListView(ObjectListView olv) {
if (olv == null)
return;
olv.Freezing += new EventHandler<FreezeEventArgs>(HandleListViewFreezing);
olv.SelectionChanged += new EventHandler(HandleListViewSelectionChanged);
olv.BindingContextChanged += new EventHandler(HandleListViewBindingContextChanged);
}
/// <summary>
///
/// </summary>
/// <param name="olv"></param>
protected virtual void UnbindListView(ObjectListView olv) {
if (olv == null)
return;
olv.Freezing -= new EventHandler<FreezeEventArgs>(HandleListViewFreezing);
olv.SelectionChanged -= new EventHandler(HandleListViewSelectionChanged);
olv.BindingContextChanged -= new EventHandler(HandleListViewBindingContextChanged);
}
/// <summary>
///
/// </summary>
protected virtual void BindDataSource() {
if (this.CurrencyManager == null)
return;
this.CurrencyManager.MetaDataChanged += new EventHandler(HandleCurrencyManagerMetaDataChanged);
this.CurrencyManager.PositionChanged += new EventHandler(HandleCurrencyManagerPositionChanged);
this.CurrencyManager.ListChanged += new ListChangedEventHandler(CurrencyManagerListChanged);
}
/// <summary>
///
/// </summary>
protected virtual void UnbindDataSource() {
if (this.CurrencyManager == null)
return;
this.CurrencyManager.MetaDataChanged -= new EventHandler(HandleCurrencyManagerMetaDataChanged);
this.CurrencyManager.PositionChanged -= new EventHandler(HandleCurrencyManagerPositionChanged);
this.CurrencyManager.ListChanged -= new ListChangedEventHandler(CurrencyManagerListChanged);
}
#endregion
#region Initialization
/// <summary>
/// Our data source has changed. Figure out how to handle the new source
/// </summary>
protected virtual void RebindDataSource() {
RebindDataSource(false);
}
/// <summary>
/// Our data source has changed. Figure out how to handle the new source
/// </summary>
protected virtual void RebindDataSource(bool forceDataInitialization) {
CurrencyManager tempCurrencyManager = null;
if (this.ListView != null && this.ListView.BindingContext != null && this.DataSource != null) {
tempCurrencyManager = this.ListView.BindingContext[this.DataSource, this.DataMember] as CurrencyManager;
}
// Has our currency manager changed?
if (this.CurrencyManager != tempCurrencyManager) {
this.UnbindDataSource();
this.CurrencyManager = tempCurrencyManager;
this.BindDataSource();
// Our currency manager has changed so we have to initialize a new data source
forceDataInitialization = true;
}
if (forceDataInitialization)
InitializeDataSource();
}
/// <summary>
/// The data source for this control has changed. Reconfigure the control for the new source
/// </summary>
protected virtual void InitializeDataSource() {
if (this.ListView.Frozen || this.CurrencyManager == null)
return;
this.CreateColumnsFromSource();
this.CreateMissingAspectGettersAndPutters();
this.SetListContents();
this.ListView.AutoSizeColumns();
// Fake a position change event so that the control matches any initial Position
this.HandleCurrencyManagerPositionChanged(null, null);
}
/// <summary>
/// Take the contents of the currently bound list and put them into the control
/// </summary>
protected virtual void SetListContents() {
this.ListView.Objects = this.CurrencyManager.List;
}
/// <summary>
/// Create columns for the listview based on what properties are available in the data source
/// </summary>
/// <remarks>
/// <para>This method will create columns if there is not already a column displaying that property.</para>
/// </remarks>
protected virtual void CreateColumnsFromSource() {
if (this.CurrencyManager == null)
return;
// Don't generate any columns in design mode. If we do, the user will see them,
// but the Designer won't know about them and won't persist them, which is very confusing
if (this.ListView.IsDesignMode)
return;
// Don't create columns if we've been told not to
if (!this.AutoGenerateColumns)
return;
// Use a Generator to create columns
Generator generator = Generator.Instance as Generator ?? new Generator();
PropertyDescriptorCollection properties = this.CurrencyManager.GetItemProperties();
if (properties.Count == 0)
return;
bool wereColumnsAdded = false;
foreach (PropertyDescriptor property in properties) {
if (!this.ShouldCreateColumn(property))
continue;
// Create a column
OLVColumn column = generator.MakeColumnFromPropertyDescriptor(property);
this.ConfigureColumn(column, property);
// Add it to our list
this.ListView.AllColumns.Add(column);
wereColumnsAdded = true;
}
if (wereColumnsAdded)
generator.PostCreateColumns(this.ListView);
}
/// <summary>
/// Decide if a new column should be added to the control to display
/// the given property
/// </summary>
/// <param name="property"></param>
/// <returns></returns>
protected virtual bool ShouldCreateColumn(PropertyDescriptor property) {
// Is there a column that already shows this property? If so, we don't show it again
if (this.ListView.AllColumns.Exists(delegate(OLVColumn x) { return x.AspectName == property.Name; }))
return false;
// Relationships to other tables turn up as IBindibleLists. Don't make columns to show them.
// CHECK: Is this always true? What other things could be here? Constraints? Triggers?
if (property.PropertyType == typeof(IBindingList))
return false;
// Ignore anything marked with [OLVIgnore]
return property.Attributes[typeof(OLVIgnoreAttribute)] == null;
}
/// <summary>
/// Configure the given column to show the given property.
/// The title and aspect name of the column are already filled in.
/// </summary>
/// <param name="column"></param>
/// <param name="property"></param>
protected virtual void ConfigureColumn(OLVColumn column, PropertyDescriptor property) {
column.LastDisplayIndex = this.ListView.AllColumns.Count;
// If our column is a BLOB, it could be an image, so assign a renderer to draw it.
// CONSIDER: Is this a common enough case to warrant this code?
if (property.PropertyType == typeof(System.Byte[]))
column.Renderer = new ImageRenderer();
}
/// <summary>
/// Generate aspect getters and putters for any columns that are missing them (and for which we have
/// enough information to actually generate a getter)
/// </summary>
protected virtual void CreateMissingAspectGettersAndPutters() {
foreach (OLVColumn x in this.ListView.AllColumns) {
OLVColumn column = x; // stack based variable accessible from closures
if (column.AspectGetter == null && !String.IsNullOrEmpty(column.AspectName)) {
column.AspectGetter = delegate(object row) {
// In most cases, rows will be DataRowView objects
DataRowView drv = row as DataRowView;
if (drv == null)
return column.GetAspectByName(row);
return (drv.Row.RowState == DataRowState.Detached) ? null : drv[column.AspectName];
};
}
if (column.IsEditable && column.AspectPutter == null && !String.IsNullOrEmpty(column.AspectName)) {
column.AspectPutter = delegate(object row, object newValue) {
// In most cases, rows will be DataRowView objects
DataRowView drv = row as DataRowView;
if (drv == null)
column.PutAspectByName(row, newValue);
else {
if (drv.Row.RowState != DataRowState.Detached)
drv[column.AspectName] = newValue;
}
};
}
}
}
#endregion
#region Event Handlers
/// <summary>
/// CurrencyManager ListChanged event handler.
/// Deals with fine-grained changes to list items.
/// </summary>
/// <remarks>
/// It's actually difficult to deal with these changes in a fine-grained manner.
/// If our listview is grouped, then any change may make a new group appear or
/// an old group disappear. It is rarely enough to simply update the affected row.
/// </remarks>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void CurrencyManagerListChanged(object sender, ListChangedEventArgs e) {
Debug.Assert(sender == this.CurrencyManager);
// Ignore changes make while frozen, since we will do a complete rebuild when we unfreeze
if (this.ListView.Frozen)
return;
//System.Diagnostics.Debug.WriteLine(e.ListChangedType);
Stopwatch sw = Stopwatch.StartNew();
switch (e.ListChangedType) {
case ListChangedType.Reset:
this.HandleListChangedReset(e);
break;
case ListChangedType.ItemChanged:
this.HandleListChangedItemChanged(e);
break;
case ListChangedType.ItemAdded:
this.HandleListChangedItemAdded(e);
break;
// An item has gone away.
case ListChangedType.ItemDeleted:
this.HandleListChangedItemDeleted(e);
break;
// An item has changed its index.
case ListChangedType.ItemMoved:
this.HandleListChangedItemMoved(e);
break;
// Something has changed in the metadata.
// CHECK: When are these events actually fired?
case ListChangedType.PropertyDescriptorAdded:
case ListChangedType.PropertyDescriptorChanged:
case ListChangedType.PropertyDescriptorDeleted:
this.HandleListChangedMetadataChanged(e);
break;
}
sw.Stop();
System.Diagnostics.Debug.WriteLine(String.Format("PERF - Processing {0} event on {1} rows took {2}ms", e.ListChangedType, this.ListView.GetItemCount(), sw.ElapsedMilliseconds));
}
/// <summary>
/// Handle PropertyDescriptor* events
/// </summary>
/// <param name="e"></param>
protected virtual void HandleListChangedMetadataChanged(ListChangedEventArgs e) {
this.InitializeDataSource();
}
/// <summary>
/// Handle ItemMoved event
/// </summary>
/// <param name="e"></param>
protected virtual void HandleListChangedItemMoved(ListChangedEventArgs e) {
// When is this actually triggered?
this.InitializeDataSource();
}
/// <summary>
/// Handle the ItemDeleted event
/// </summary>
/// <param name="e"></param>
protected virtual void HandleListChangedItemDeleted(ListChangedEventArgs e) {
this.InitializeDataSource();
}
/// <summary>
/// Handle an ItemAdded event.
/// </summary>
/// <param name="e"></param>
protected virtual void HandleListChangedItemAdded(ListChangedEventArgs e) {
// We get this event twice if certain grid controls are used to add a new row to a
// datatable: once when the editing of a new row begins, and once again when that
// editing commits. (If the user cancels the creation of the new row, we never see
// the second creation.) We detect this by seeing if this is a view on a row in a
// DataTable, and if it is, testing to see if it's a new row under creation.
Object newRow = this.CurrencyManager.List[e.NewIndex];
DataRowView drv = newRow as DataRowView;
if (drv == null || !drv.IsNew) {
// Either we're not dealing with a view on a data table, or this is the commit
// notification. Either way, this is the final notification, so we want to
// handle the new row now!
this.InitializeDataSource();
}
}
/// <summary>
/// Handle the Reset event
/// </summary>
/// <param name="e"></param>
protected virtual void HandleListChangedReset(ListChangedEventArgs e) {
// The whole list has changed utterly, so reload it.
this.InitializeDataSource();
}
/// <summary>
/// Handle ItemChanged event. This is triggered when a single item
/// has changed, so just refresh that one item.
/// </summary>
/// <param name="e"></param>
/// <remarks>Even in this simple case, we should probably rebuild the list.
/// For example, the change could put the item into its own new group.</remarks>
protected virtual void HandleListChangedItemChanged(ListChangedEventArgs e) {
// A single item has changed, so just refresh that.
//System.Diagnostics.Debug.WriteLine(String.Format("HandleListChangedItemChanged: {0}, {1}", e.NewIndex, e.PropertyDescriptor.Name));
Object changedRow = this.CurrencyManager.List[e.NewIndex];
this.ListView.RefreshObject(changedRow);
}
/// <summary>
/// The CurrencyManager calls this if the data source looks
/// different. We just reload everything.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks>
/// CHECK: Do we need this if we are handle ListChanged metadata events?
/// </remarks>
protected virtual void HandleCurrencyManagerMetaDataChanged(object sender, EventArgs e) {
this.InitializeDataSource();
}
/// <summary>
/// Called by the CurrencyManager when the currently selected item
/// changes. We update the ListView selection so that we stay in sync
/// with any other controls bound to the same source.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void HandleCurrencyManagerPositionChanged(object sender, EventArgs e) {
int index = this.CurrencyManager.Position;
// Make sure the index is sane (-1 pops up from time to time)
if (index < 0 || index >= this.ListView.GetItemCount())
return;
// Avoid recursion. If we are currently changing the index, don't
// start the process again.
if (this.isChangingIndex)
return;
try {
this.isChangingIndex = true;
this.ChangePosition(index);
}
finally {
this.isChangingIndex = false;
}
}
private bool isChangingIndex = false;
/// <summary>
/// Change the control's position (which is it's currently selected row)
/// to the nth row in the dataset
/// </summary>
/// <param name="index">The index of the row to be selected</param>
protected virtual void ChangePosition(int index) {
// We can't use the index directly, since our listview may be sorted
// Only assign if not null
if (this.ListView.SelectedObject != null)
{
this.ListView.SelectedObject = this.CurrencyManager.List[index];
}
// THINK: Do we always want to bring it into view?
if (this.ListView.SelectedIndices.Count > 0)
this.ListView.EnsureVisible(this.ListView.SelectedIndices[0]);
}
#endregion
#region ObjectListView event handlers
/// <summary>
/// Handle the selection changing in our ListView.
/// We need to tell our currency manager about the new position.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void HandleListViewSelectionChanged(object sender, EventArgs e) {
// Prevent recursion
if (this.isChangingIndex)
return;
// Sanity
if (this.CurrencyManager == null)
return;
// If only one item is selected, tell the currency manager which item is selected.
// CurrencyManager can't handle multiple selection so there's nothing we can do
// if more than one row is selected.
if (this.ListView.SelectedIndices.Count != 1)
return;
try {
this.isChangingIndex = true;
// We can't use the selectedIndex directly, since our listview may be sorted and/or filtered
// So we have to find the index of the selected object within the original list.
this.CurrencyManager.Position = this.CurrencyManager.List.IndexOf(this.ListView.SelectedObject);
} finally {
this.isChangingIndex = false;
}
}
/// <summary>
/// Handle the frozenness of our ListView changing.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void HandleListViewFreezing(object sender, FreezeEventArgs e) {
if (!alreadyFreezing && e.FreezeLevel == 0) {
try {
alreadyFreezing = true;
this.RebindDataSource(true);
} finally {
alreadyFreezing = false;
}
}
}
private bool alreadyFreezing = false;
/// <summary>
/// Handle a change to the BindingContext of our ListView.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void HandleListViewBindingContextChanged(object sender, EventArgs e) {
this.RebindDataSource(false);
}
#endregion
}
}

View File

@@ -0,0 +1,168 @@
/*
* Delegates - All delegate definitions used in ObjectListView
*
* Author: Phillip Piper
* Date: 31-March-2011 5:53 pm
*
* Change log:
* v2.10
* 2015-12-30 JPP - Added CellRendererGetterDelegate
* v2.?
* 2011-03-31 JPP - Split into its own file
*
* Copyright (C) 2011-2015 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Windows.Forms;
using System.Drawing;
namespace BrightIdeasSoftware {
#region Delegate declarations
/// <summary>
/// These delegates are used to extract an aspect from a row object
/// </summary>
public delegate Object AspectGetterDelegate(Object rowObject);
/// <summary>
/// These delegates are used to put a changed value back into a model object
/// </summary>
public delegate void AspectPutterDelegate(Object rowObject, Object newValue);
/// <summary>
/// These delegates can be used to convert an aspect value to a display string,
/// instead of using the default ToString()
/// </summary>
public delegate string AspectToStringConverterDelegate(Object value);
/// <summary>
/// These delegates are used to get the tooltip for a cell
/// </summary>
public delegate String CellToolTipGetterDelegate(OLVColumn column, Object modelObject);
/// <summary>
/// These delegates are used to the state of the checkbox for a row object.
/// </summary>
/// <remarks><para>
/// For reasons known only to someone in Microsoft, we can only set
/// a boolean on the ListViewItem to indicate it's "checked-ness", but when
/// we receive update events, we have to use a tristate CheckState. So we can
/// be told about an indeterminate state, but we can't set it ourselves.
/// </para>
/// <para>As of version 2.0, we can now return indeterminate state.</para>
/// </remarks>
public delegate CheckState CheckStateGetterDelegate(Object rowObject);
/// <summary>
/// These delegates are used to get the state of the checkbox for a row object.
/// </summary>
/// <param name="rowObject"></param>
/// <returns></returns>
public delegate bool BooleanCheckStateGetterDelegate(Object rowObject);
/// <summary>
/// These delegates are used to put a changed check state back into a model object
/// </summary>
public delegate CheckState CheckStatePutterDelegate(Object rowObject, CheckState newValue);
/// <summary>
/// These delegates are used to put a changed check state back into a model object
/// </summary>
/// <param name="rowObject"></param>
/// <param name="newValue"></param>
/// <returns></returns>
public delegate bool BooleanCheckStatePutterDelegate(Object rowObject, bool newValue);
/// <summary>
/// These delegates are used to get the renderer for a particular cell
/// </summary>
public delegate IRenderer CellRendererGetterDelegate(Object rowObject, OLVColumn column);
/// <summary>
/// The callbacks for RightColumnClick events
/// </summary>
public delegate void ColumnRightClickEventHandler(object sender, ColumnClickEventArgs e);
/// <summary>
/// This delegate will be used to own draw header column.
/// </summary>
public delegate bool HeaderDrawingDelegate(Graphics g, Rectangle r, int columnIndex, OLVColumn column, bool isPressed, HeaderStateStyle stateStyle);
/// <summary>
/// This delegate is called when a group has been created but not yet made
/// into a real ListViewGroup. The user can take this opportunity to fill
/// in lots of other details about the group.
/// </summary>
public delegate void GroupFormatterDelegate(OLVGroup group, GroupingParameters parms);
/// <summary>
/// These delegates are used to retrieve the object that is the key of the group to which the given row belongs.
/// </summary>
public delegate Object GroupKeyGetterDelegate(Object rowObject);
/// <summary>
/// These delegates are used to convert a group key into a title for the group
/// </summary>
public delegate string GroupKeyToTitleConverterDelegate(Object groupKey);
/// <summary>
/// These delegates are used to get the tooltip for a column header
/// </summary>
public delegate String HeaderToolTipGetterDelegate(OLVColumn column);
/// <summary>
/// These delegates are used to fetch the image selector that should be used
/// to choose an image for this column.
/// </summary>
public delegate Object ImageGetterDelegate(Object rowObject);
/// <summary>
/// These delegates are used to draw a cell
/// </summary>
public delegate bool RenderDelegate(EventArgs e, Graphics g, Rectangle r, Object rowObject);
/// <summary>
/// These delegates are used to fetch a row object for virtual lists
/// </summary>
public delegate Object RowGetterDelegate(int rowIndex);
/// <summary>
/// These delegates are used to format a listviewitem before it is added to the control.
/// </summary>
public delegate void RowFormatterDelegate(OLVListItem olvItem);
/// <summary>
/// These delegates can be used to return the array of texts that should be searched for text filtering
/// </summary>
public delegate string[] SearchValueGetterDelegate(Object value);
/// <summary>
/// These delegates are used to sort the listview in some custom fashion
/// </summary>
public delegate void SortDelegate(OLVColumn column, SortOrder sortOrder);
/// <summary>
/// These delegates are used to order two strings.
/// x cannot be null. y can be null.
/// </summary>
public delegate int StringCompareDelegate(string x, string y);
#endregion
}

View File

@@ -0,0 +1,407 @@
///*
// * DragSource.cs - Add drag source functionality to an ObjectListView
// *
// * UNFINISHED
// *
// * Author: Phillip Piper
// * Date: 2009-03-17 5:15 PM
// *
// * Change log:
// * v2.3
// * 2009-07-06 JPP - Make sure Link is acceptable as an drop effect by default
// * (since MS didn't make it part of the 'All' value)
// * v2.2
// * 2009-04-15 JPP - Separated DragSource.cs into DropSink.cs
// * 2009-03-17 JPP - Initial version
// *
// * Copyright (C) 2009 Phillip Piper
// *
// * This program is free software: you can redistribute it and/or modify
// * it under the terms of the GNU General Public License as published by
// * the Free Software Foundation, either version 3 of the License, or
// * (at your option) any later version.
// *
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// * GNU General Public License for more details.
// *
// * You should have received a copy of the GNU General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// *
// * If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
// */
//using System;
//using System.Collections;
//using System.Collections.Generic;
//using System.Text;
//using System.Windows.Forms;
//using System.Drawing;
//using System.Drawing.Drawing2D;
//namespace BrightIdeasSoftware
//{
// /// <summary>
// /// An IDragSource controls how drag out from the ObjectListView will behave
// /// </summary>
// public interface IDragSource
// {
// /// <summary>
// /// A drag operation is beginning. Return the data object that will be used
// /// for data transfer. Return null to prevent the drag from starting.
// /// </summary>
// /// <remarks>
// /// The returned object is later passed to the GetAllowedEffect() and EndDrag()
// /// methods.
// /// </remarks>
// /// <param name="olv">What ObjectListView is being dragged from.</param>
// /// <param name="button">Which mouse button is down?</param>
// /// <param name="item">What item was directly dragged by the user? There may be more than just this
// /// item selected.</param>
// /// <returns>The data object that will be used for data transfer. This will often be a subclass
// /// of DataObject, but does not need to be.</returns>
// Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item);
// /// <summary>
// /// What operations are possible for this drag? This controls the icon shown during the drag
// /// </summary>
// /// <param name="dragObject">The data object returned by StartDrag()</param>
// /// <returns>A combination of DragDropEffects flags</returns>
// DragDropEffects GetAllowedEffects(Object dragObject);
// /// <summary>
// /// The drag operation is complete. Do whatever is necessary to complete the action.
// /// </summary>
// /// <param name="dragObject">The data object returned by StartDrag()</param>
// /// <param name="effect">The value returned from GetAllowedEffects()</param>
// void EndDrag(Object dragObject, DragDropEffects effect);
// }
// /// <summary>
// /// A do-nothing implementation of IDragSource that can be safely subclassed.
// /// </summary>
// public class AbstractDragSource : IDragSource
// {
// #region IDragSource Members
// /// <summary>
// /// See IDragSource documentation
// /// </summary>
// /// <param name="olv"></param>
// /// <param name="button"></param>
// /// <param name="item"></param>
// /// <returns></returns>
// public virtual Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item) {
// return null;
// }
// /// <summary>
// /// See IDragSource documentation
// /// </summary>
// /// <param name="data"></param>
// /// <returns></returns>
// public virtual DragDropEffects GetAllowedEffects(Object data) {
// return DragDropEffects.None;
// }
// /// <summary>
// /// See IDragSource documentation
// /// </summary>
// /// <param name="dragObject"></param>
// /// <param name="effect"></param>
// public virtual void EndDrag(Object dragObject, DragDropEffects effect) {
// }
// #endregion
// }
// /// <summary>
// /// A reasonable implementation of IDragSource that provides normal
// /// drag source functionality. It creates a data object that supports
// /// inter-application dragging of text and HTML representation of
// /// the dragged rows. It can optionally force a refresh of all dragged
// /// rows when the drag is complete.
// /// </summary>
// /// <remarks>Subclasses can override GetDataObject() to add new
// /// data formats to the data transfer object.</remarks>
// public class SimpleDragSource : IDragSource
// {
// #region Constructors
// /// <summary>
// /// Construct a SimpleDragSource
// /// </summary>
// public SimpleDragSource() {
// }
// /// <summary>
// /// Construct a SimpleDragSource that refreshes the dragged rows when
// /// the drag is complete
// /// </summary>
// /// <param name="refreshAfterDrop"></param>
// public SimpleDragSource(bool refreshAfterDrop) {
// this.RefreshAfterDrop = refreshAfterDrop;
// }
// #endregion
// #region Public properties
// /// <summary>
// /// Gets or sets whether the dragged rows should be refreshed when the
// /// drag operation is complete.
// /// </summary>
// public bool RefreshAfterDrop {
// get { return refreshAfterDrop; }
// set { refreshAfterDrop = value; }
// }
// private bool refreshAfterDrop;
// #endregion
// #region IDragSource Members
// /// <summary>
// /// Create a DataObject when the user does a left mouse drag operation.
// /// See IDragSource for further information.
// /// </summary>
// /// <param name="olv"></param>
// /// <param name="button"></param>
// /// <param name="item"></param>
// /// <returns></returns>
// public virtual Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item) {
// // We only drag on left mouse
// if (button != MouseButtons.Left)
// return null;
// return this.CreateDataObject(olv);
// }
// /// <summary>
// /// Which operations are allowed in the operation? By default, all operations are supported.
// /// </summary>
// /// <param name="data"></param>
// /// <returns>All opertions are supported</returns>
// public virtual DragDropEffects GetAllowedEffects(Object data) {
// return DragDropEffects.All | DragDropEffects.Link; // why didn't MS include 'Link' in 'All'??
// }
// /// <summary>
// /// The drag operation is finished. Refreshe the dragged rows if so configured.
// /// </summary>
// /// <param name="dragObject"></param>
// /// <param name="effect"></param>
// public virtual void EndDrag(Object dragObject, DragDropEffects effect) {
// OLVDataObject data = dragObject as OLVDataObject;
// if (data == null)
// return;
// if (this.RefreshAfterDrop)
// data.ListView.RefreshObjects(data.ModelObjects);
// }
// /// <summary>
// /// Create a data object that will be used to as the data object
// /// for the drag operation.
// /// </summary>
// /// <remarks>
// /// Subclasses can override this method add new formats to the data object.
// /// </remarks>
// /// <param name="olv">The ObjectListView that is the source of the drag</param>
// /// <returns>A data object for the drag</returns>
// protected virtual object CreateDataObject(ObjectListView olv) {
// OLVDataObject data = new OLVDataObject(olv);
// data.CreateTextFormats();
// return data;
// }
// #endregion
// }
// /// <summary>
// /// A data transfer object that knows how to transform a list of model
// /// objects into a text and HTML representation.
// /// </summary>
// public class OLVDataObject : DataObject
// {
// #region Life and death
// /// <summary>
// /// Create a data object from the selected objects in the given ObjectListView
// /// </summary>
// /// <param name="olv">The source of the data object</param>
// public OLVDataObject(ObjectListView olv) : this(olv, olv.SelectedObjects) {
// }
// /// <summary>
// /// Create a data object which operates on the given model objects
// /// in the given ObjectListView
// /// </summary>
// /// <param name="olv">The source of the data object</param>
// /// <param name="modelObjects">The model objects to be put into the data object</param>
// public OLVDataObject(ObjectListView olv, IList modelObjects) {
// this.objectListView = olv;
// this.modelObjects = modelObjects;
// this.includeHiddenColumns = olv.IncludeHiddenColumnsInDataTransfer;
// this.includeColumnHeaders = olv.IncludeColumnHeadersInCopy;
// }
// #endregion
// #region Properties
// /// <summary>
// /// Gets or sets whether hidden columns will also be included in the text
// /// and HTML representation. If this is false, only visible columns will
// /// be included.
// /// </summary>
// public bool IncludeHiddenColumns {
// get { return includeHiddenColumns; }
// }
// private bool includeHiddenColumns;
// /// <summary>
// /// Gets or sets whether column headers will also be included in the text
// /// and HTML representation.
// /// </summary>
// public bool IncludeColumnHeaders
// {
// get { return includeColumnHeaders; }
// }
// private bool includeColumnHeaders;
// /// <summary>
// /// Gets the ObjectListView that is being used as the source of the data
// /// </summary>
// public ObjectListView ListView {
// get { return objectListView; }
// }
// private ObjectListView objectListView;
// /// <summary>
// /// Gets the model objects that are to be placed in the data object
// /// </summary>
// public IList ModelObjects {
// get { return modelObjects; }
// }
// private IList modelObjects = new ArrayList();
// #endregion
// /// <summary>
// /// Put a text and HTML representation of our model objects
// /// into the data object.
// /// </summary>
// public void CreateTextFormats() {
// IList<OLVColumn> columns = this.IncludeHiddenColumns ? this.ListView.AllColumns : this.ListView.ColumnsInDisplayOrder;
// // Build text and html versions of the selection
// StringBuilder sbText = new StringBuilder();
// StringBuilder sbHtml = new StringBuilder("<table>");
// // Include column headers
// if (includeColumnHeaders)
// {
// sbHtml.Append("<tr><td>");
// foreach (OLVColumn col in columns)
// {
// if (col != columns[0])
// {
// sbText.Append("\t");
// sbHtml.Append("</td><td>");
// }
// string strValue = col.Text;
// sbText.Append(strValue);
// sbHtml.Append(strValue); //TODO: Should encode the string value
// }
// sbText.AppendLine();
// sbHtml.AppendLine("</td></tr>");
// }
// foreach (object modelObject in this.ModelObjects)
// {
// sbHtml.Append("<tr><td>");
// foreach (OLVColumn col in columns) {
// if (col != columns[0]) {
// sbText.Append("\t");
// sbHtml.Append("</td><td>");
// }
// string strValue = col.GetStringValue(modelObject);
// sbText.Append(strValue);
// sbHtml.Append(strValue); //TODO: Should encode the string value
// }
// sbText.AppendLine();
// sbHtml.AppendLine("</td></tr>");
// }
// sbHtml.AppendLine("</table>");
// // Put both the text and html versions onto the clipboard.
// // For some reason, SetText() with UnicodeText doesn't set the basic CF_TEXT format,
// // but using SetData() does.
// //this.SetText(sbText.ToString(), TextDataFormat.UnicodeText);
// this.SetData(sbText.ToString());
// this.SetText(ConvertToHtmlFragment(sbHtml.ToString()), TextDataFormat.Html);
// }
// /// <summary>
// /// Make a HTML representation of our model objects
// /// </summary>
// public string CreateHtml() {
// IList<OLVColumn> columns = this.ListView.ColumnsInDisplayOrder;
// // Build html version of the selection
// StringBuilder sbHtml = new StringBuilder("<table>");
// foreach (object modelObject in this.ModelObjects) {
// sbHtml.Append("<tr><td>");
// foreach (OLVColumn col in columns) {
// if (col != columns[0]) {
// sbHtml.Append("</td><td>");
// }
// string strValue = col.GetStringValue(modelObject);
// sbHtml.Append(strValue); //TODO: Should encode the string value
// }
// sbHtml.AppendLine("</td></tr>");
// }
// sbHtml.AppendLine("</table>");
// return sbHtml.ToString();
// }
// /// <summary>
// /// Convert the fragment of HTML into the Clipboards HTML format.
// /// </summary>
// /// <remarks>The HTML format is found here http://msdn2.microsoft.com/en-us/library/aa767917.aspx
// /// </remarks>
// /// <param name="fragment">The HTML to put onto the clipboard. It must be valid HTML!</param>
// /// <returns>A string that can be put onto the clipboard and will be recognized as HTML</returns>
// private string ConvertToHtmlFragment(string fragment) {
// // Minimal implementation of HTML clipboard format
// string source = "http://www.codeproject.com/KB/list/ObjectListView.aspx";
// const String MARKER_BLOCK =
// "Version:1.0\r\n" +
// "StartHTML:{0,8}\r\n" +
// "EndHTML:{1,8}\r\n" +
// "StartFragment:{2,8}\r\n" +
// "EndFragment:{3,8}\r\n" +
// "StartSelection:{2,8}\r\n" +
// "EndSelection:{3,8}\r\n" +
// "SourceURL:{4}\r\n" +
// "{5}";
// int prefixLength = String.Format(MARKER_BLOCK, 0, 0, 0, 0, source, "").Length;
// const String DEFAULT_HTML_BODY =
// "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">" +
// "<HTML><HEAD></HEAD><BODY><!--StartFragment-->{0}<!--EndFragment--></BODY></HTML>";
// string html = String.Format(DEFAULT_HTML_BODY, fragment);
// int startFragment = prefixLength + html.IndexOf(fragment);
// int endFragment = startFragment + fragment.Length;
// return String.Format(MARKER_BLOCK, prefixLength, prefixLength + html.Length, startFragment, endFragment, source, html);
// }
// }
//}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,104 @@
/*
* Enums - All enum definitions used in ObjectListView
*
* Author: Phillip Piper
* Date: 31-March-2011 5:53 pm
*
* Change log:
* 2011-03-31 JPP - Split into its own file
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace BrightIdeasSoftware {
public partial class ObjectListView {
/// <summary>
/// How does a user indicate that they want to edit cells?
/// </summary>
public enum CellEditActivateMode {
/// <summary>
/// This list cannot be edited. F2 does nothing.
/// </summary>
None = 0,
/// <summary>
/// A single click on a <strong>subitem</strong> will edit the value. Single clicking the primary column,
/// selects the row just like normal. The user must press F2 to edit the primary column.
/// </summary>
SingleClick = 1,
/// <summary>
/// Double clicking a subitem or the primary column will edit that cell.
/// F2 will edit the primary column.
/// </summary>
DoubleClick = 2,
/// <summary>
/// Pressing F2 is the only way to edit the cells. Once the primary column is being edited,
/// the other cells in the row can be edited by pressing Tab.
/// </summary>
F2Only = 3,
/// <summary>
/// A single click on a <strong>any</strong> cell will edit the value, even the primary column.
/// </summary>
SingleClickAlways = 4,
}
/// <summary>
/// These values specify how column selection will be presented to the user
/// </summary>
public enum ColumnSelectBehaviour {
/// <summary>
/// No column selection will be presented
/// </summary>
None,
/// <summary>
/// The columns will be show in the main menu
/// </summary>
InlineMenu,
/// <summary>
/// The columns will be shown in a submenu
/// </summary>
Submenu,
/// <summary>
/// A model dialog will be presented to allow the user to choose columns
/// </summary>
ModelDialog,
/*
* NonModelDialog is just a little bit tricky since the OLV can change views while the dialog is showing
* So, just comment this out for the time being.
/// <summary>
/// A non-model dialog will be presented to allow the user to choose columns
/// </summary>
NonModelDialog
*
*/
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,175 @@
/*
* GroupingParameters - All the data that is used to create groups in an ObjectListView
*
* Author: Phillip Piper
* Date: 31-March-2011 5:53 pm
*
* Change log:
* 2011-03-31 JPP - Split into its own file
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
namespace BrightIdeasSoftware {
/// <summary>
/// This class contains all the settings used when groups are created
/// </summary>
public class GroupingParameters {
/// <summary>
/// Create a GroupingParameters
/// </summary>
/// <param name="olv"></param>
/// <param name="groupByColumn"></param>
/// <param name="groupByOrder"></param>
/// <param name="column"></param>
/// <param name="order"></param>
/// <param name="secondaryColumn"></param>
/// <param name="secondaryOrder"></param>
/// <param name="titleFormat"></param>
/// <param name="titleSingularFormat"></param>
/// <param name="sortItemsByPrimaryColumn"></param>
public GroupingParameters(ObjectListView olv, OLVColumn groupByColumn, SortOrder groupByOrder,
OLVColumn column, SortOrder order, OLVColumn secondaryColumn, SortOrder secondaryOrder,
string titleFormat, string titleSingularFormat, bool sortItemsByPrimaryColumn) {
this.ListView = olv;
this.GroupByColumn = groupByColumn;
this.GroupByOrder = groupByOrder;
this.PrimarySort = column;
this.PrimarySortOrder = order;
this.SecondarySort = secondaryColumn;
this.SecondarySortOrder = secondaryOrder;
this.SortItemsByPrimaryColumn = sortItemsByPrimaryColumn;
this.TitleFormat = titleFormat;
this.TitleSingularFormat = titleSingularFormat;
}
/// <summary>
/// Gets or sets the ObjectListView being grouped
/// </summary>
public ObjectListView ListView {
get { return this.listView; }
set { this.listView = value; }
}
private ObjectListView listView;
/// <summary>
/// Gets or sets the column used to create groups
/// </summary>
public OLVColumn GroupByColumn {
get { return this.groupByColumn; }
set { this.groupByColumn = value; }
}
private OLVColumn groupByColumn;
/// <summary>
/// In what order will the groups themselves be sorted?
/// </summary>
public SortOrder GroupByOrder {
get { return this.groupByOrder; }
set { this.groupByOrder = value; }
}
private SortOrder groupByOrder;
/// <summary>
/// If this is set, this comparer will be used to order the groups
/// </summary>
public IComparer<OLVGroup> GroupComparer {
get { return this.groupComparer; }
set { this.groupComparer = value; }
}
private IComparer<OLVGroup> groupComparer;
/// <summary>
/// If this is set, this comparer will be used to order items within each group
/// </summary>
public IComparer<OLVListItem> ItemComparer {
get { return this.itemComparer; }
set { this.itemComparer = value; }
}
private IComparer<OLVListItem> itemComparer;
/// <summary>
/// Gets or sets the column that will be the primary sort
/// </summary>
public OLVColumn PrimarySort {
get { return this.primarySort; }
set { this.primarySort = value; }
}
private OLVColumn primarySort;
/// <summary>
/// Gets or sets the ordering for the primary sort
/// </summary>
public SortOrder PrimarySortOrder {
get { return this.primarySortOrder; }
set { this.primarySortOrder = value; }
}
private SortOrder primarySortOrder;
/// <summary>
/// Gets or sets the column used for secondary sorting
/// </summary>
public OLVColumn SecondarySort {
get { return this.secondarySort; }
set { this.secondarySort = value; }
}
private OLVColumn secondarySort;
/// <summary>
/// Gets or sets the ordering for the secondary sort
/// </summary>
public SortOrder SecondarySortOrder {
get { return this.secondarySortOrder; }
set { this.secondarySortOrder = value; }
}
private SortOrder secondarySortOrder;
/// <summary>
/// Gets or sets the title format used for groups with zero or more than one element
/// </summary>
public string TitleFormat {
get { return this.titleFormat; }
set { this.titleFormat = value; }
}
private string titleFormat;
/// <summary>
/// Gets or sets the title format used for groups with only one element
/// </summary>
public string TitleSingularFormat {
get { return this.titleSingularFormat; }
set { this.titleSingularFormat = value; }
}
private string titleSingularFormat;
/// <summary>
/// Gets or sets whether the items should be sorted by the primary column
/// </summary>
public bool SortItemsByPrimaryColumn {
get { return this.sortItemsByPrimaryColumn; }
set { this.sortItemsByPrimaryColumn = value; }
}
private bool sortItemsByPrimaryColumn;
}
}

View File

@@ -0,0 +1,747 @@
/*
* Groups - Enhancements to the normal ListViewGroup
*
* Author: Phillip Piper
* Date: 22/08/2009 6:03PM
*
* Change log:
* v2.3
* 2009-09-09 JPP - Added Collapsed and Collapsible properties
* 2009-09-01 JPP - Cleaned up code, added more docs
* - Works under VS2005 again
* 2009-08-22 JPP - Initial version
*
* To do:
* - Implement subseting
* - Implement footer items
*
* Copyright (C) 2009-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace BrightIdeasSoftware
{
/// <summary>
/// These values indicate what is the state of the group. These values
/// are taken directly from the SDK and many are not used by ObjectListView.
/// </summary>
[Flags]
public enum GroupState
{
/// <summary>
/// Normal
/// </summary>
LVGS_NORMAL = 0x0,
/// <summary>
/// Collapsed
/// </summary>
LVGS_COLLAPSED = 0x1,
/// <summary>
/// Hidden
/// </summary>
LVGS_HIDDEN = 0x2,
/// <summary>
/// NoHeader
/// </summary>
LVGS_NOHEADER = 0x4,
/// <summary>
/// Can be collapsed
/// </summary>
LVGS_COLLAPSIBLE = 0x8,
/// <summary>
/// Has focus
/// </summary>
LVGS_FOCUSED = 0x10,
/// <summary>
/// Is Selected
/// </summary>
LVGS_SELECTED = 0x20,
/// <summary>
/// Is subsetted
/// </summary>
LVGS_SUBSETED = 0x40,
/// <summary>
/// Subset link has focus
/// </summary>
LVGS_SUBSETLINKFOCUSED = 0x80,
/// <summary>
/// All styles
/// </summary>
LVGS_ALL = 0xFFFF
}
/// <summary>
/// This mask indicates which members of a LVGROUP have valid data. These values
/// are taken directly from the SDK and many are not used by ObjectListView.
/// </summary>
[Flags]
public enum GroupMask
{
/// <summary>
/// No mask
/// </summary>
LVGF_NONE = 0,
/// <summary>
/// Group has header
/// </summary>
LVGF_HEADER = 1,
/// <summary>
/// Group has footer
/// </summary>
LVGF_FOOTER = 2,
/// <summary>
/// Group has state
/// </summary>
LVGF_STATE = 4,
/// <summary>
///
/// </summary>
LVGF_ALIGN = 8,
/// <summary>
///
/// </summary>
LVGF_GROUPID = 0x10,
/// <summary>
/// pszSubtitle is valid
/// </summary>
LVGF_SUBTITLE = 0x00100,
/// <summary>
/// pszTask is valid
/// </summary>
LVGF_TASK = 0x00200,
/// <summary>
/// pszDescriptionTop is valid
/// </summary>
LVGF_DESCRIPTIONTOP = 0x00400,
/// <summary>
/// pszDescriptionBottom is valid
/// </summary>
LVGF_DESCRIPTIONBOTTOM = 0x00800,
/// <summary>
/// iTitleImage is valid
/// </summary>
LVGF_TITLEIMAGE = 0x01000,
/// <summary>
/// iExtendedImage is valid
/// </summary>
LVGF_EXTENDEDIMAGE = 0x02000,
/// <summary>
/// iFirstItem and cItems are valid
/// </summary>
LVGF_ITEMS = 0x04000,
/// <summary>
/// pszSubsetTitle is valid
/// </summary>
LVGF_SUBSET = 0x08000,
/// <summary>
/// readonly, cItems holds count of items in visible subset, iFirstItem is valid
/// </summary>
LVGF_SUBSETITEMS = 0x10000
}
/// <summary>
/// This mask indicates which members of a GROUPMETRICS structure are valid
/// </summary>
[Flags]
public enum GroupMetricsMask
{
/// <summary>
///
/// </summary>
LVGMF_NONE = 0,
/// <summary>
///
/// </summary>
LVGMF_BORDERSIZE = 1,
/// <summary>
///
/// </summary>
LVGMF_BORDERCOLOR = 2,
/// <summary>
///
/// </summary>
LVGMF_TEXTCOLOR = 4
}
/// <summary>
/// Instances of this class enhance the capabilities of a normal ListViewGroup,
/// enabling the functionality that was released in v6 of the common controls.
/// </summary>
/// <remarks>
/// <para>
/// In this implementation (2009-09), these objects are essentially passive.
/// Setting properties does not automatically change the associated group in
/// the listview. Collapsed and Collapsible are two exceptions to this and
/// give immediate results.
/// </para>
/// <para>
/// This really should be a subclass of ListViewGroup, but that class is
/// sealed (why is that?). So this class provides the same interface as a
/// ListViewGroup, plus many other new properties.
/// </para>
/// </remarks>
public class OLVGroup
{
#region Creation
/// <summary>
/// Create an OLVGroup
/// </summary>
public OLVGroup() : this("Default group header") {
}
/// <summary>
/// Create a group with the given title
/// </summary>
/// <param name="header">Title of the group</param>
public OLVGroup(string header) {
this.Header = header;
this.Id = OLVGroup.nextId++;
this.TitleImage = -1;
this.ExtendedImage = -1;
}
private static int nextId;
#endregion
#region Public properties
/// <summary>
/// Gets or sets the bottom description of the group
/// </summary>
/// <remarks>
/// Descriptions only appear when group is centered and there is a title image
/// </remarks>
public string BottomDescription {
get { return this.bottomDescription; }
set { this.bottomDescription = value; }
}
private string bottomDescription;
/// <summary>
/// Gets or sets whether or not this group is collapsed
/// </summary>
public bool Collapsed {
get { return this.GetOneState(GroupState.LVGS_COLLAPSED); }
set { this.SetOneState(value, GroupState.LVGS_COLLAPSED); }
}
/// <summary>
/// Gets or sets whether or not this group can be collapsed
/// </summary>
public bool Collapsible {
get { return this.GetOneState(GroupState.LVGS_COLLAPSIBLE); }
set { this.SetOneState(value, GroupState.LVGS_COLLAPSIBLE); }
}
/// <summary>
/// Gets or sets some representation of the contents of this group
/// </summary>
/// <remarks>This is user defined (like Tag)</remarks>
public IList Contents {
get { return this.contents; }
set { this.contents = value; }
}
private IList contents;
/// <summary>
/// Gets whether this group has been created.
/// </summary>
public bool Created {
get { return this.ListView != null; }
}
/// <summary>
/// Gets or sets the int or string that will select the extended image to be shown against the title
/// </summary>
public object ExtendedImage {
get { return this.extendedImage; }
set { this.extendedImage = value; }
}
private object extendedImage;
/// <summary>
/// Gets or sets the footer of the group
/// </summary>
public string Footer {
get { return this.footer; }
set { this.footer = value; }
}
private string footer;
/// <summary>
/// Gets the internal id of our associated ListViewGroup.
/// </summary>
public int GroupId {
get {
if (this.ListViewGroup == null)
return this.Id;
// Use reflection to get around the access control on the ID property
if (OLVGroup.groupIdPropInfo == null) {
OLVGroup.groupIdPropInfo = typeof(ListViewGroup).GetProperty("ID",
BindingFlags.NonPublic | BindingFlags.Instance);
System.Diagnostics.Debug.Assert(OLVGroup.groupIdPropInfo != null);
}
int? groupId = OLVGroup.groupIdPropInfo.GetValue(this.ListViewGroup, null) as int?;
return groupId.HasValue ? groupId.Value : -1;
}
}
private static PropertyInfo groupIdPropInfo;
/// <summary>
/// Gets or sets the header of the group
/// </summary>
public string Header {
get { return this.header; }
set { this.header = value; }
}
private string header;
/// <summary>
/// Gets or sets the horizontal alignment of the group header
/// </summary>
public HorizontalAlignment HeaderAlignment {
get { return this.headerAlignment; }
set { this.headerAlignment = value; }
}
private HorizontalAlignment headerAlignment;
/// <summary>
/// Gets or sets the internally created id of the group
/// </summary>
public int Id {
get { return this.id; }
set { this.id = value; }
}
private int id;
/// <summary>
/// Gets or sets ListViewItems that are members of this group
/// </summary>
/// <remarks>Listener of the BeforeCreatingGroups event can populate this collection.
/// It is only used on non-virtual lists.</remarks>
public IList<OLVListItem> Items {
get { return this.items; }
set { this.items = value; }
}
private IList<OLVListItem> items = new List<OLVListItem>();
/// <summary>
/// Gets or sets the key that was used to partition objects into this group
/// </summary>
/// <remarks>This is user defined (like Tag)</remarks>
public object Key {
get { return this.key; }
set { this.key = value; }
}
private object key;
/// <summary>
/// Gets the ObjectListView that this group belongs to
/// </summary>
/// <remarks>If this is null, the group has not yet been created.</remarks>
public ObjectListView ListView {
get { return this.listView; }
protected set { this.listView = value; }
}
private ObjectListView listView;
/// <summary>
/// Gets or sets the name of the group
/// </summary>
/// <remarks>As of 2009-09-01, this property is not used.</remarks>
public string Name {
get { return this.name; }
set { this.name = value; }
}
private string name;
/// <summary>
/// Gets or sets whether this group is focused
/// </summary>
public bool Focused
{
get { return this.GetOneState(GroupState.LVGS_FOCUSED); }
set { this.SetOneState(value, GroupState.LVGS_FOCUSED); }
}
/// <summary>
/// Gets or sets whether this group is selected
/// </summary>
public bool Selected
{
get { return this.GetOneState(GroupState.LVGS_SELECTED); }
set { this.SetOneState(value, GroupState.LVGS_SELECTED); }
}
/// <summary>
/// Gets or sets the text that will show that this group is subsetted
/// </summary>
/// <remarks>
/// As of WinSDK v7.0, subsetting of group is officially unimplemented.
/// We can get around this using undocumented interfaces and may do so.
/// </remarks>
public string SubsetTitle {
get { return this.subsetTitle; }
set { this.subsetTitle = value; }
}
private string subsetTitle;
/// <summary>
/// Gets or set the subtitleof the task
/// </summary>
public string Subtitle {
get { return this.subtitle; }
set { this.subtitle = value; }
}
private string subtitle;
/// <summary>
/// Gets or sets the value by which this group will be sorted.
/// </summary>
public IComparable SortValue {
get { return this.sortValue; }
set { this.sortValue = value; }
}
private IComparable sortValue;
/// <summary>
/// Gets or sets the state of the group
/// </summary>
public GroupState State {
get { return this.state; }
set { this.state = value; }
}
private GroupState state;
/// <summary>
/// Gets or sets which bits of State are valid
/// </summary>
public GroupState StateMask {
get { return this.stateMask; }
set { this.stateMask = value; }
}
private GroupState stateMask;
/// <summary>
/// Gets or sets whether this group is showing only a subset of its elements
/// </summary>
/// <remarks>
/// As of WinSDK v7.0, this property officially does nothing.
/// </remarks>
public bool Subseted {
get { return this.GetOneState(GroupState.LVGS_SUBSETED); }
set { this.SetOneState(value, GroupState.LVGS_SUBSETED); }
}
/// <summary>
/// Gets or sets the user-defined data attached to this group
/// </summary>
public object Tag {
get { return this.tag; }
set { this.tag = value; }
}
private object tag;
/// <summary>
/// Gets or sets the task of this group
/// </summary>
/// <remarks>This task is the clickable text that appears on the right margin
/// of the group header.</remarks>
public string Task {
get { return this.task; }
set { this.task = value; }
}
private string task;
/// <summary>
/// Gets or sets the int or string that will select the image to be shown against the title
/// </summary>
public object TitleImage {
get { return this.titleImage; }
set { this.titleImage = value; }
}
private object titleImage;
/// <summary>
/// Gets or sets the top description of the group
/// </summary>
/// <remarks>
/// Descriptions only appear when group is centered and there is a title image
/// </remarks>
public string TopDescription {
get { return this.topDescription; }
set { this.topDescription = value; }
}
private string topDescription;
/// <summary>
/// Gets or sets the number of items that are within this group.
/// </summary>
/// <remarks>This should only be used for virtual groups.</remarks>
public int VirtualItemCount {
get { return this.virtualItemCount; }
set { this.virtualItemCount = value; }
}
private int virtualItemCount;
#endregion
#region Protected properties
/// <summary>
/// Gets or sets the ListViewGroup that is shadowed by this group.
/// </summary>
/// <remarks>For virtual groups, this will always be null.</remarks>
protected ListViewGroup ListViewGroup {
get { return this.listViewGroup; }
set { this.listViewGroup = value; }
}
private ListViewGroup listViewGroup;
#endregion
#region Calculations/Conversions
/// <summary>
/// Calculate the index into the group image list of the given image selector
/// </summary>
/// <param name="imageSelector"></param>
/// <returns></returns>
public int GetImageIndex(object imageSelector) {
if (imageSelector == null || this.ListView == null || this.ListView.GroupImageList == null)
return -1;
if (imageSelector is Int32)
return (int)imageSelector;
String imageSelectorAsString = imageSelector as String;
if (imageSelectorAsString != null)
return this.ListView.GroupImageList.Images.IndexOfKey(imageSelectorAsString);
return -1;
}
/// <summary>
/// Convert this object to a string representation
/// </summary>
/// <returns></returns>
public override string ToString() {
return this.Header;
}
#endregion
#region Commands
/// <summary>
/// Insert a native group into the underlying Windows control,
/// *without* using a ListViewGroup
/// </summary>
/// <param name="olv"></param>
/// <remarks>This is used when creating virtual groups</remarks>
public void InsertGroupNewStyle(ObjectListView olv) {
this.ListView = olv;
NativeMethods.InsertGroup(olv, this.AsNativeGroup(true));
}
/// <summary>
/// Insert a native group into the underlying control via a ListViewGroup
/// </summary>
/// <param name="olv"></param>
public void InsertGroupOldStyle(ObjectListView olv) {
this.ListView = olv;
// Create/update the associated ListViewGroup
if (this.ListViewGroup == null)
this.ListViewGroup = new ListViewGroup();
this.ListViewGroup.Header = this.Header;
this.ListViewGroup.HeaderAlignment = this.HeaderAlignment;
this.ListViewGroup.Name = this.Name;
// Remember which OLVGroup created the ListViewGroup
this.ListViewGroup.Tag = this;
// Add the group to the control
olv.Groups.Add(this.ListViewGroup);
// Add any extra information
NativeMethods.SetGroupInfo(olv, this.GroupId, this.AsNativeGroup(false));
}
/// <summary>
/// Change the members of the group to match the current contents of Items,
/// using a ListViewGroup
/// </summary>
public void SetItemsOldStyle() {
List<OLVListItem> list = this.Items as List<OLVListItem>;
if (list == null) {
foreach (OLVListItem item in this.Items) {
this.ListViewGroup.Items.Add(item);
}
} else {
this.ListViewGroup.Items.AddRange(list.ToArray());
}
}
#endregion
#region Implementation
/// <summary>
/// Create a native LVGROUP structure that matches this group
/// </summary>
internal NativeMethods.LVGROUP2 AsNativeGroup(bool withId) {
NativeMethods.LVGROUP2 group = new NativeMethods.LVGROUP2();
group.cbSize = (uint)Marshal.SizeOf(typeof(NativeMethods.LVGROUP2));
group.mask = (uint)(GroupMask.LVGF_HEADER ^ GroupMask.LVGF_ALIGN ^ GroupMask.LVGF_STATE);
group.pszHeader = this.Header;
group.uAlign = (uint)this.HeaderAlignment;
group.stateMask = (uint)this.StateMask;
group.state = (uint)this.State;
if (withId) {
group.iGroupId = this.GroupId;
group.mask ^= (uint)GroupMask.LVGF_GROUPID;
}
if (!String.IsNullOrEmpty(this.Footer)) {
group.pszFooter = this.Footer;
group.mask ^= (uint)GroupMask.LVGF_FOOTER;
}
if (!String.IsNullOrEmpty(this.Subtitle)) {
group.pszSubtitle = this.Subtitle;
group.mask ^= (uint)GroupMask.LVGF_SUBTITLE;
}
if (!String.IsNullOrEmpty(this.Task)) {
group.pszTask = this.Task;
group.mask ^= (uint)GroupMask.LVGF_TASK;
}
if (!String.IsNullOrEmpty(this.TopDescription)) {
group.pszDescriptionTop = this.TopDescription;
group.mask ^= (uint)GroupMask.LVGF_DESCRIPTIONTOP;
}
if (!String.IsNullOrEmpty(this.BottomDescription)) {
group.pszDescriptionBottom = this.BottomDescription;
group.mask ^= (uint)GroupMask.LVGF_DESCRIPTIONBOTTOM;
}
int imageIndex = this.GetImageIndex(this.TitleImage);
if (imageIndex >= 0) {
group.iTitleImage = imageIndex;
group.mask ^= (uint)GroupMask.LVGF_TITLEIMAGE;
}
imageIndex = this.GetImageIndex(this.ExtendedImage);
if (imageIndex >= 0) {
group.iExtendedImage = imageIndex;
group.mask ^= (uint)GroupMask.LVGF_EXTENDEDIMAGE;
}
if (!String.IsNullOrEmpty(this.SubsetTitle)) {
group.pszSubsetTitle = this.SubsetTitle;
group.mask ^= (uint)GroupMask.LVGF_SUBSET;
}
if (this.VirtualItemCount > 0) {
group.cItems = this.VirtualItemCount;
group.mask ^= (uint)GroupMask.LVGF_ITEMS;
}
return group;
}
private bool GetOneState(GroupState mask) {
if (this.Created)
this.State = this.GetState();
return (this.State & mask) == mask;
}
/// <summary>
/// Get the current state of this group from the underlying control
/// </summary>
protected GroupState GetState() {
return NativeMethods.GetGroupState(this.ListView, this.GroupId, GroupState.LVGS_ALL);
}
/// <summary>
/// Get the current state of this group from the underlying control
/// </summary>
protected int SetState(GroupState newState, GroupState mask) {
NativeMethods.LVGROUP2 group = new NativeMethods.LVGROUP2();
group.cbSize = ((uint)Marshal.SizeOf(typeof(NativeMethods.LVGROUP2)));
group.mask = (uint)GroupMask.LVGF_STATE;
group.state = (uint)newState;
group.stateMask = (uint)mask;
return NativeMethods.SetGroupInfo(this.ListView, this.GroupId, group);
}
private void SetOneState(bool value, GroupState mask)
{
this.StateMask ^= mask;
if (value)
this.State ^= mask;
else
this.State &= ~mask;
if (this.Created)
this.SetState(this.State, mask);
}
#endregion
}
}

View File

@@ -0,0 +1,559 @@
/*
* Munger - An Interface pattern on getting and setting values from object through Reflection
*
* Author: Phillip Piper
* Date: 28/11/2008 17:15
*
* Change log:
* v2.5.1
* 2012-05-01 JPP - Added IgnoreMissingAspects property
* v2.5
* 2011-05-20 JPP - Accessing through an indexer when the target had both a integer and
* a string indexer didn't work reliably.
* v2.4.1
* 2010-08-10 JPP - Refactored into Munger/SimpleMunger. 3x faster!
* v2.3
* 2009-02-15 JPP - Made Munger a public class
* 2009-01-20 JPP - Made the Munger capable of handling indexed access.
* Incidentally, this removed the ugliness that the last change introduced.
* 2009-01-18 JPP - Handle target objects from a DataListView (normally DataRowViews)
* v2.0
* 2008-11-28 JPP Initial version
*
* TO DO:
*
* Copyright (C) 2006-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Reflection;
namespace BrightIdeasSoftware
{
/// <summary>
/// An instance of Munger gets a value from or puts a value into a target object. The property
/// to be peeked (or poked) is determined from a string. The peeking or poking is done using reflection.
/// </summary>
/// <remarks>
/// Name of the aspect to be peeked can be a field, property or parameterless method. The name of an
/// aspect to poke can be a field, writable property or single parameter method.
/// <para>
/// Aspect names can be dotted to chain a series of references.
/// </para>
/// <example>Order.Customer.HomeAddress.State</example>
/// </remarks>
public class Munger
{
#region Life and death
/// <summary>
/// Create a do nothing Munger
/// </summary>
public Munger()
{
}
/// <summary>
/// Create a Munger that works on the given aspect name
/// </summary>
/// <param name="aspectName">The name of the </param>
public Munger(String aspectName)
{
this.AspectName = aspectName;
}
#endregion
#region Static utility methods
/// <summary>
/// A helper method to put the given value into the given aspect of the given object.
/// </summary>
/// <remarks>This method catches and silently ignores any errors that occur
/// while modifying the target object</remarks>
/// <param name="target">The object to be modified</param>
/// <param name="propertyName">The name of the property/field to be modified</param>
/// <param name="value">The value to be assigned</param>
/// <returns>Did the modification work?</returns>
public static bool PutProperty(object target, string propertyName, object value) {
try {
Munger munger = new Munger(propertyName);
return munger.PutValue(target, value);
}
catch (MungerException) {
// Not a lot we can do about this. Something went wrong in the bowels
// of the property. Let's take the ostrich approach and just ignore it :-)
// Normally, we would never just silently ignore an exception.
// However, in this case, this is a utility method that explicitly
// contracts to catch and ignore errors. If this is not acceptible,
// the programmer should not use this method.
}
return false;
}
/// <summary>
/// Gets or sets whether Mungers will silently ignore missing aspect errors.
/// </summary>
/// <remarks>
/// <para>
/// By default, if a Munger is asked to fetch a field/property/method
/// that does not exist from a model, it returns an error message, since that
/// condition is normally a programming error. There are some use cases where
/// this is not an error, and the munger should simply keep quiet.
/// </para>
/// <para>By default this is true during release builds.</para>
/// </remarks>
public static bool IgnoreMissingAspects {
get { return ignoreMissingAspects; }
set { ignoreMissingAspects = value; }
}
private static bool ignoreMissingAspects
#if !DEBUG
= true
#endif
;
#endregion
#region Public properties
/// <summary>
/// The name of the aspect that is to be peeked or poked.
/// </summary>
/// <remarks>
/// <para>
/// This name can be a field, property or parameter-less method.
/// </para>
/// <para>
/// The name can be dotted, which chains references. If any link in the chain returns
/// null, the entire chain is considered to return null.
/// </para>
/// </remarks>
/// <example>"DateOfBirth"</example>
/// <example>"Owner.HomeAddress.Postcode"</example>
public string AspectName
{
get { return aspectName; }
set {
aspectName = value;
// Clear any cache
aspectParts = null;
}
}
private string aspectName;
#endregion
#region Public interface
/// <summary>
/// Extract the value indicated by our AspectName from the given target.
/// </summary>
/// <remarks>If the aspect name is null or empty, this will return null.</remarks>
/// <param name="target">The object that will be peeked</param>
/// <returns>The value read from the target</returns>
public Object GetValue(Object target) {
if (this.Parts.Count == 0)
return null;
try {
return this.EvaluateParts(target, this.Parts);
} catch (MungerException ex) {
if (Munger.IgnoreMissingAspects)
return null;
return String.Format("'{0}' is not a parameter-less method, property or field of type '{1}'",
ex.Munger.AspectName, ex.Target.GetType());
}
}
/// <summary>
/// Extract the value indicated by our AspectName from the given target, raising exceptions
/// if the munger fails.
/// </summary>
/// <remarks>If the aspect name is null or empty, this will return null.</remarks>
/// <param name="target">The object that will be peeked</param>
/// <returns>The value read from the target</returns>
public Object GetValueEx(Object target) {
if (this.Parts.Count == 0)
return null;
return this.EvaluateParts(target, this.Parts);
}
/// <summary>
/// Poke the given value into the given target indicated by our AspectName.
/// </summary>
/// <remarks>
/// <para>
/// If the AspectName is a dotted path, all the selectors bar the last
/// are used to find the object that should be updated, and the last
/// selector is used as the property to update on that object.
/// </para>
/// <para>
/// So, if 'target' is a Person and the AspectName is "HomeAddress.Postcode",
/// this method will first fetch "HomeAddress" property, and then try to set the
/// "Postcode" property on the home address object.
/// </para>
/// </remarks>
/// <param name="target">The object that will be poked</param>
/// <param name="value">The value that will be poked into the target</param>
/// <returns>bool indicating whether the put worked</returns>
public bool PutValue(Object target, Object value)
{
if (this.Parts.Count == 0)
return false;
SimpleMunger lastPart = this.Parts[this.Parts.Count - 1];
if (this.Parts.Count > 1) {
List<SimpleMunger> parts = new List<SimpleMunger>(this.Parts);
parts.RemoveAt(parts.Count - 1);
try {
target = this.EvaluateParts(target, parts);
} catch (MungerException ex) {
this.ReportPutValueException(ex);
return false;
}
}
if (target != null) {
try {
return lastPart.PutValue(target, value);
} catch (MungerException ex) {
this.ReportPutValueException(ex);
}
}
return false;
}
#endregion
#region Implementation
/// <summary>
/// Gets the list of SimpleMungers that match our AspectName
/// </summary>
private IList<SimpleMunger> Parts {
get {
if (aspectParts == null)
aspectParts = BuildParts(this.AspectName);
return aspectParts;
}
}
private IList<SimpleMunger> aspectParts;
/// <summary>
/// Convert a possibly dotted AspectName into a list of SimpleMungers
/// </summary>
/// <param name="aspect"></param>
/// <returns></returns>
private IList<SimpleMunger> BuildParts(string aspect) {
List<SimpleMunger> parts = new List<SimpleMunger>();
if (!String.IsNullOrEmpty(aspect)) {
foreach (string part in aspect.Split('.')) {
parts.Add(new SimpleMunger(part.Trim()));
}
}
return parts;
}
/// <summary>
/// Evaluate the given chain of SimpleMungers against an initial target.
/// </summary>
/// <param name="target"></param>
/// <param name="parts"></param>
/// <returns></returns>
private object EvaluateParts(object target, IList<SimpleMunger> parts) {
foreach (SimpleMunger part in parts) {
if (target == null)
break;
target = part.GetValue(target);
}
return target;
}
private void ReportPutValueException(MungerException ex) {
//TODO: How should we report this error?
System.Diagnostics.Debug.WriteLine("PutValue failed");
System.Diagnostics.Debug.WriteLine(String.Format("- Culprit aspect: {0}", ex.Munger.AspectName));
System.Diagnostics.Debug.WriteLine(String.Format("- Target: {0} of type {1}", ex.Target, ex.Target.GetType()));
System.Diagnostics.Debug.WriteLine(String.Format("- Inner exception: {0}", ex.InnerException));
}
#endregion
}
/// <summary>
/// A SimpleMunger deals with a single property/field/method on its target.
/// </summary>
/// <remarks>
/// Munger uses a chain of these resolve a dotted aspect name.
/// </remarks>
/// <remarks>
/// Create a SimpleMunger
/// </remarks>
/// <param name="aspectName"></param>
public class SimpleMunger(String aspectName)
{
#region Life and death
#endregion
#region Public properties
/// <summary>
/// The name of the aspect that is to be peeked or poked.
/// </summary>
/// <remarks>
/// <para>
/// This name can be a field, property or method.
/// When using a method to get a value, the method must be parameter-less.
/// When using a method to set a value, the method must accept 1 parameter.
/// </para>
/// <para>
/// It cannot be a dotted name.
/// </para>
/// </remarks>
public string AspectName {
get { return aspectName; }
}
private readonly string aspectName = aspectName;
#endregion
#region Public interface
/// <summary>
/// Get a value from the given target
/// </summary>
/// <param name="target"></param>
/// <returns></returns>
public Object GetValue(Object target) {
if (target == null)
return null;
this.ResolveName(target, this.AspectName, 0);
try {
if (this.resolvedPropertyInfo != null)
return this.resolvedPropertyInfo.GetValue(target, null);
if (this.resolvedMethodInfo != null)
return this.resolvedMethodInfo.Invoke(target, null);
if (this.resolvedFieldInfo != null)
return this.resolvedFieldInfo.GetValue(target);
// If that didn't work, try to use the indexer property.
// This covers things like dictionaries and DataRows.
if (this.indexerPropertyInfo != null)
return this.indexerPropertyInfo.GetValue(target, new object[] { this.AspectName });
} catch (Exception ex) {
// Lots of things can do wrong in these invocations
throw new MungerException(this, target, ex);
}
// If we get to here, we couldn't find a match for the aspect
throw new MungerException(this, target, new MissingMethodException());
}
/// <summary>
/// Poke the given value into the given target indicated by our AspectName.
/// </summary>
/// <param name="target">The object that will be poked</param>
/// <param name="value">The value that will be poked into the target</param>
/// <returns>bool indicating if the put worked</returns>
public bool PutValue(object target, object value) {
if (target == null)
return false;
this.ResolveName(target, this.AspectName, 1);
try {
if (this.resolvedPropertyInfo != null) {
this.resolvedPropertyInfo.SetValue(target, value, null);
return true;
}
if (this.resolvedMethodInfo != null) {
this.resolvedMethodInfo.Invoke(target, new object[] { value });
return true;
}
if (this.resolvedFieldInfo != null) {
this.resolvedFieldInfo.SetValue(target, value);
return true;
}
// If that didn't work, try to use the indexer property.
// This covers things like dictionaries and DataRows.
if (this.indexerPropertyInfo != null) {
this.indexerPropertyInfo.SetValue(target, value, new object[] { this.AspectName });
return true;
}
} catch (Exception ex) {
// Lots of things can do wrong in these invocations
throw new MungerException(this, target, ex);
}
return false;
}
#endregion
#region Implementation
private void ResolveName(object target, string name, int numberMethodParameters) {
if (cachedTargetType == target.GetType() && cachedName == name && cachedNumberParameters == numberMethodParameters)
return;
cachedTargetType = target.GetType();
cachedName = name;
cachedNumberParameters = numberMethodParameters;
resolvedFieldInfo = null;
resolvedPropertyInfo = null;
resolvedMethodInfo = null;
indexerPropertyInfo = null;
const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance /*| BindingFlags.NonPublic*/;
foreach (PropertyInfo pinfo in target.GetType().GetProperties(flags)) {
if (pinfo.Name == name) {
resolvedPropertyInfo = pinfo;
return;
}
// See if we can find an string indexer property while we are here.
// We also need to allow for old style <object> keyed collections.
if (indexerPropertyInfo == null && pinfo.Name == "Item") {
ParameterInfo[] par = pinfo.GetGetMethod().GetParameters();
if (par.Length > 0) {
Type parameterType = par[0].ParameterType;
if (parameterType == typeof(string) || parameterType == typeof(object))
indexerPropertyInfo = pinfo;
}
}
}
foreach (FieldInfo info in target.GetType().GetFields(flags)) {
if (info.Name == name) {
resolvedFieldInfo = info;
return;
}
}
foreach (MethodInfo info in target.GetType().GetMethods(flags)) {
if (info.Name == name && info.GetParameters().Length == numberMethodParameters) {
resolvedMethodInfo = info;
return;
}
}
}
private Type cachedTargetType;
private string cachedName;
private int cachedNumberParameters;
private FieldInfo resolvedFieldInfo;
private PropertyInfo resolvedPropertyInfo;
private MethodInfo resolvedMethodInfo;
private PropertyInfo indexerPropertyInfo;
#endregion
}
/// <summary>
/// These exceptions are raised when a munger finds something it cannot process
/// </summary>
/// <remarks>
/// Create a MungerException
/// </remarks>
/// <param name="munger"></param>
/// <param name="target"></param>
/// <param name="ex"></param>
public class MungerException(SimpleMunger munger, object target, Exception ex) : ApplicationException("Munger failed", ex)
{
/// <summary>
/// Get the munger that raised the exception
/// </summary>
public SimpleMunger Munger {
get { return munger; }
}
private readonly SimpleMunger munger = munger;
/// <summary>
/// Gets the target that threw the exception
/// </summary>
public object Target {
get { return target; }
}
private readonly object target = target;
}
/*
* We don't currently need this
* 2010-08-06
*
internal class SimpleBinder : Binder
{
public override FieldInfo BindToField(BindingFlags bindingAttr, FieldInfo[] match, object value, System.Globalization.CultureInfo culture) {
//return Type.DefaultBinder.BindToField(
throw new NotImplementedException();
}
public override object ChangeType(object value, Type type, System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
}
public override MethodBase BindToMethod(BindingFlags bindingAttr, MethodBase[] match, ref object[] args, ParameterModifier[] modifiers, System.Globalization.CultureInfo culture, string[] names, out object state) {
throw new NotImplementedException();
}
public override void ReorderArgumentArray(ref object[] args, object state) {
throw new NotImplementedException();
}
public override MethodBase SelectMethod(BindingFlags bindingAttr, MethodBase[] match, Type[] types, ParameterModifier[] modifiers) {
throw new NotImplementedException();
}
public override PropertyInfo SelectProperty(BindingFlags bindingAttr, PropertyInfo[] match, Type returnType, Type[] indexes, ParameterModifier[] modifiers) {
if (match == null)
throw new ArgumentNullException("match");
if (match.Length == 0)
return null;
return match[0];
}
}
*/
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,87 @@
/*
* NullableDictionary - A simple Dictionary that can handle null as a key
*
* Author: Phillip Piper
* Date: 31-March-2011 5:53 pm
*
* Change log:
* 2011-03-31 JPP - Split into its own file
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
namespace BrightIdeasSoftware {
/// <summary>
/// A simple-minded implementation of a Dictionary that can handle null as a key.
/// </summary>
/// <typeparam name="TKey">The type of the dictionary key</typeparam>
/// <typeparam name="TValue">The type of the values to be stored</typeparam>
/// <remarks>This is not a full implementation and is only meant to handle
/// collecting groups by their keys, since groups can have null as a key value.</remarks>
internal class NullableDictionary<TKey, TValue> : Dictionary<TKey, TValue> {
private bool hasNullKey;
private TValue nullValue;
new public TValue this[TKey key] {
get {
if (key != null)
return base[key];
if (this.hasNullKey)
return this.nullValue;
throw new KeyNotFoundException();
}
set {
if (key == null) {
this.hasNullKey = true;
this.nullValue = value;
} else
base[key] = value;
}
}
new public bool ContainsKey(TKey key) {
return key == null ? this.hasNullKey : base.ContainsKey(key);
}
new public IList Keys {
get {
ArrayList list = new ArrayList(base.Keys);
if (this.hasNullKey)
list.Add(null);
return list;
}
}
new public IList<TValue> Values {
get {
List<TValue> list = new List<TValue>(base.Values);
if (this.hasNullKey)
list.Add(this.nullValue);
return list;
}
}
}
}

View File

@@ -0,0 +1,321 @@
/*
* OLVListItem - A row in an ObjectListView
*
* Author: Phillip Piper
* Date: 31-March-2011 5:53 pm
*
* Change log:
* 2015-08-22 JPP - Added OLVListItem.SelectedBackColor and SelectedForeColor
* 2015-06-09 JPP - Added HasAnyHyperlinks property
* v2.8
* 2014-09-27 JPP - Remove faulty caching of CheckState
* 2014-05-06 JPP - Added OLVListItem.Enabled flag
* vOld
* 2011-03-31 JPP - Split into its own file
*
* Copyright (C) 2011-2015 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
namespace BrightIdeasSoftware {
/// <summary>
/// OLVListItems are specialized ListViewItems that know which row object they came from,
/// and the row index at which they are displayed, even when in group view mode. They
/// also know the image they should draw against themselves
/// </summary>
public class OLVListItem : ListViewItem {
#region Constructors
/// <summary>
/// Create a OLVListItem for the given row object
/// </summary>
public OLVListItem(object rowObject) {
this.rowObject = rowObject;
}
/// <summary>
/// Create a OLVListItem for the given row object, represented by the given string and image
/// </summary>
public OLVListItem(object rowObject, string text, Object image)
: base(text, -1) {
this.rowObject = rowObject;
this.imageSelector = image;
}
#endregion.
#region Properties
/// <summary>
/// Gets the bounding rectangle of the item, including all subitems
/// </summary>
new public Rectangle Bounds {
get {
try {
return base.Bounds;
}
catch (System.ArgumentException) {
// If the item is part of a collapsed group, Bounds will throw an exception
return Rectangle.Empty;
}
}
}
/// <summary>
/// Gets or sets how many pixels will be left blank around each cell of this item
/// </summary>
/// <remarks>This setting only takes effect when the control is owner drawn.</remarks>
public Rectangle? CellPadding {
get { return this.cellPadding; }
set { this.cellPadding = value; }
}
private Rectangle? cellPadding;
/// <summary>
/// Gets or sets how the cells of this item will be vertically aligned
/// </summary>
/// <remarks>This setting only takes effect when the control is owner drawn.</remarks>
public StringAlignment? CellVerticalAlignment {
get { return this.cellVerticalAlignment; }
set { this.cellVerticalAlignment = value; }
}
private StringAlignment? cellVerticalAlignment;
/// <summary>
/// Gets or sets the checkedness of this item.
/// </summary>
/// <remarks>
/// Virtual lists don't handle checkboxes well, so we have to intercept attempts to change them
/// through the items, and change them into something that will work.
/// Unfortunately, this won't work if this property is set through the base class, since
/// the property is not declared as virtual.
/// </remarks>
new public bool Checked {
get {
return base.Checked;
}
set {
if (this.Checked != value) {
if (value)
((ObjectListView)this.ListView).CheckObject(this.RowObject);
else
((ObjectListView)this.ListView).UncheckObject(this.RowObject);
}
}
}
/// <summary>
/// Enable tri-state checkbox.
/// </summary>
/// <remarks>.NET's Checked property was not built to handle tri-state checkboxes,
/// and will return True for both Checked and Indeterminate states.</remarks>
public CheckState CheckState {
get {
switch (this.StateImageIndex) {
case 0:
return System.Windows.Forms.CheckState.Unchecked;
case 1:
return System.Windows.Forms.CheckState.Checked;
case 2:
return System.Windows.Forms.CheckState.Indeterminate;
default:
return System.Windows.Forms.CheckState.Unchecked;
}
}
set {
switch (value) {
case System.Windows.Forms.CheckState.Unchecked:
this.StateImageIndex = 0;
break;
case System.Windows.Forms.CheckState.Checked:
this.StateImageIndex = 1;
break;
case System.Windows.Forms.CheckState.Indeterminate:
this.StateImageIndex = 2;
break;
}
}
}
/// <summary>
/// Gets if this item has any decorations set for it.
/// </summary>
public bool HasDecoration {
get {
return this.decorations != null && this.decorations.Count > 0;
}
}
/// <summary>
/// Gets or sets the decoration that will be drawn over this item
/// </summary>
/// <remarks>Setting this replaces all other decorations</remarks>
public IDecoration Decoration {
get {
if (this.HasDecoration)
return this.Decorations[0];
else
return null;
}
set {
this.Decorations.Clear();
if (value != null)
this.Decorations.Add(value);
}
}
/// <summary>
/// Gets the collection of decorations that will be drawn over this item
/// </summary>
public IList<IDecoration> Decorations {
get {
if (this.decorations == null)
this.decorations = new List<IDecoration>();
return this.decorations;
}
}
private IList<IDecoration> decorations;
/// <summary>
/// Gets whether or not this row can be selected and activated
/// </summary>
public bool Enabled
{
get { return this.enabled; }
internal set { this.enabled = value; }
}
private bool enabled;
/// <summary>
/// Gets whether any cell on this item is showing a hyperlink
/// </summary>
public bool HasAnyHyperlinks {
get {
foreach (OLVListSubItem subItem in this.SubItems) {
if (!String.IsNullOrEmpty(subItem.Url))
return true;
}
return false;
}
}
/// <summary>
/// Get or set the image that should be shown against this item
/// </summary>
/// <remarks><para>This can be an Image, a string or an int. A string or an int will
/// be used as an index into the small image list.</para></remarks>
public Object ImageSelector {
get { return imageSelector; }
set {
imageSelector = value;
if (value is Int32)
this.ImageIndex = (Int32)value;
else if (value is String)
this.ImageKey = (String)value;
else
this.ImageIndex = -1;
}
}
private Object imageSelector;
/// <summary>
/// Gets or sets the the model object that is source of the data for this list item.
/// </summary>
public object RowObject {
get { return rowObject; }
set { rowObject = value; }
}
private object rowObject;
/// <summary>
/// Gets or sets the color that will be used for this row's background when it is selected and
/// the control is focused.
/// </summary>
/// <remarks>
/// <para>To work reliably, this property must be set during a FormatRow event.</para>
/// <para>
/// If this is not set, the normal selection BackColor will be used.
/// </para>
/// </remarks>
public Color? SelectedBackColor {
get { return this.selectedBackColor; }
set { this.selectedBackColor = value; }
}
private Color? selectedBackColor;
/// <summary>
/// Gets or sets the color that will be used for this row's foreground when it is selected and
/// the control is focused.
/// </summary>
/// <remarks>
/// <para>To work reliably, this property must be set during a FormatRow event.</para>
/// <para>
/// If this is not set, the normal selection ForeColor will be used.
/// </para>
/// </remarks>
public Color? SelectedForeColor
{
get { return this.selectedForeColor; }
set { this.selectedForeColor = value; }
}
private Color? selectedForeColor;
#endregion
#region Accessing
/// <summary>
/// Return the sub item at the given index
/// </summary>
/// <param name="index">Index of the subitem to be returned</param>
/// <returns>An OLVListSubItem</returns>
public virtual OLVListSubItem GetSubItem(int index) {
if (index >= 0 && index < this.SubItems.Count)
return (OLVListSubItem)this.SubItems[index];
return null;
}
/// <summary>
/// Return bounds of the given subitem
/// </summary>
/// <remarks>This correctly calculates the bounds even for column 0.</remarks>
public virtual Rectangle GetSubItemBounds(int subItemIndex) {
if (subItemIndex == 0) {
Rectangle r = this.Bounds;
Point sides = NativeMethods.GetScrolledColumnSides(this.ListView, subItemIndex);
r.X = sides.X + 1;
r.Width = sides.Y - sides.X;
return r;
}
OLVListSubItem subItem = this.GetSubItem(subItemIndex);
return subItem == null ? new Rectangle() : subItem.Bounds;
}
#endregion
}
}

View File

@@ -0,0 +1,173 @@
/*
* OLVListSubItem - A single cell in an ObjectListView
*
* Author: Phillip Piper
* Date: 31-March-2011 5:53 pm
*
* Change log:
* 2011-03-31 JPP - Split into its own file
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;
namespace BrightIdeasSoftware {
/// <summary>
/// A ListViewSubItem that knows which image should be drawn against it.
/// </summary>
[Browsable(false)]
public class OLVListSubItem : ListViewItem.ListViewSubItem {
#region Constructors
/// <summary>
/// Create a OLVListSubItem
/// </summary>
public OLVListSubItem() {
}
/// <summary>
/// Create a OLVListSubItem that shows the given string and image
/// </summary>
public OLVListSubItem(object modelValue, string text, Object image) {
this.ModelValue = modelValue;
this.Text = text;
this.ImageSelector = image;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets how many pixels will be left blank around this cell
/// </summary>
/// <remarks>This setting only takes effect when the control is owner drawn.</remarks>
public Rectangle? CellPadding {
get { return this.cellPadding; }
set { this.cellPadding = value; }
}
private Rectangle? cellPadding;
/// <summary>
/// Gets or sets how this cell will be vertically aligned
/// </summary>
/// <remarks>This setting only takes effect when the control is owner drawn.</remarks>
public StringAlignment? CellVerticalAlignment {
get { return this.cellVerticalAlignment; }
set { this.cellVerticalAlignment = value; }
}
private StringAlignment? cellVerticalAlignment;
/// <summary>
/// Gets or sets the model value is being displayed by this subitem.
/// </summary>
public object ModelValue
{
get { return modelValue; }
private set { modelValue = value; }
}
private object modelValue;
/// <summary>
/// Gets if this subitem has any decorations set for it.
/// </summary>
public bool HasDecoration {
get {
return this.decorations != null && this.decorations.Count > 0;
}
}
/// <summary>
/// Gets or sets the decoration that will be drawn over this item
/// </summary>
/// <remarks>Setting this replaces all other decorations</remarks>
public IDecoration Decoration {
get {
return this.HasDecoration ? this.Decorations[0] : null;
}
set {
this.Decorations.Clear();
if (value != null)
this.Decorations.Add(value);
}
}
/// <summary>
/// Gets the collection of decorations that will be drawn over this item
/// </summary>
public IList<IDecoration> Decorations {
get {
if (this.decorations == null)
this.decorations = new List<IDecoration>();
return this.decorations;
}
}
private IList<IDecoration> decorations;
/// <summary>
/// Get or set the image that should be shown against this item
/// </summary>
/// <remarks><para>This can be an Image, a string or an int. A string or an int will
/// be used as an index into the small image list.</para></remarks>
public Object ImageSelector {
get { return imageSelector; }
set { imageSelector = value; }
}
private Object imageSelector;
/// <summary>
/// Gets or sets the url that should be invoked when this subitem is clicked
/// </summary>
public string Url
{
get { return this.url; }
set { this.url = value; }
}
private string url;
/// <summary>
/// Gets or sets whether this cell is selected
/// </summary>
public bool Selected
{
get { return this.selected; }
set { this.selected = value; }
}
private bool selected;
#endregion
#region Implementation Properties
/// <summary>
/// Return the state of the animatation of the image on this subitem.
/// Null means there is either no image, or it is not an animation
/// </summary>
internal ImageRenderer.AnimationState AnimationState;
#endregion
}
}

View File

@@ -0,0 +1,388 @@
/*
* OlvListViewHitTestInfo - All information gathered during a OlvHitTest() operation
*
* Author: Phillip Piper
* Date: 31-March-2011 5:53 pm
*
* Change log:
* 2011-03-31 JPP - Split into its own file
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
namespace BrightIdeasSoftware {
/// <summary>
/// An indication of where a hit was within ObjectListView cell
/// </summary>
public enum HitTestLocation {
/// <summary>
/// Nowhere
/// </summary>
Nothing,
/// <summary>
/// On the text
/// </summary>
Text,
/// <summary>
/// On the image
/// </summary>
Image,
/// <summary>
/// On the checkbox
/// </summary>
CheckBox,
/// <summary>
/// On the expand button (TreeListView)
/// </summary>
ExpandButton,
/// <summary>
/// in a button (cell must have ButtonRenderer)
/// </summary>
Button,
/// <summary>
/// in the cell but not in any more specific location
/// </summary>
InCell,
/// <summary>
/// UserDefined location1 (used for custom renderers)
/// </summary>
UserDefined,
/// <summary>
/// On the expand/collapse widget of the group
/// </summary>
GroupExpander,
/// <summary>
/// Somewhere on a group
/// </summary>
Group,
/// <summary>
/// Somewhere in a column header
/// </summary>
Header,
/// <summary>
/// Somewhere in a column header checkbox
/// </summary>
HeaderCheckBox,
/// <summary>
/// Somewhere in a header divider
/// </summary>
HeaderDivider,
}
/// <summary>
/// A collection of ListViewHitTest constants
/// </summary>
[Flags]
public enum HitTestLocationEx {
/// <summary>
///
/// </summary>
LVHT_NOWHERE = 0x00000001,
/// <summary>
///
/// </summary>
LVHT_ONITEMICON = 0x00000002,
/// <summary>
///
/// </summary>
LVHT_ONITEMLABEL = 0x00000004,
/// <summary>
///
/// </summary>
LVHT_ONITEMSTATEICON = 0x00000008,
/// <summary>
///
/// </summary>
LVHT_ONITEM = (LVHT_ONITEMICON | LVHT_ONITEMLABEL | LVHT_ONITEMSTATEICON),
/// <summary>
///
/// </summary>
LVHT_ABOVE = 0x00000008,
/// <summary>
///
/// </summary>
LVHT_BELOW = 0x00000010,
/// <summary>
///
/// </summary>
LVHT_TORIGHT = 0x00000020,
/// <summary>
///
/// </summary>
LVHT_TOLEFT = 0x00000040,
/// <summary>
///
/// </summary>
LVHT_EX_GROUP_HEADER = 0x10000000,
/// <summary>
///
/// </summary>
LVHT_EX_GROUP_FOOTER = 0x20000000,
/// <summary>
///
/// </summary>
LVHT_EX_GROUP_COLLAPSE = 0x40000000,
/// <summary>
///
/// </summary>
LVHT_EX_GROUP_BACKGROUND = -2147483648, // 0x80000000
/// <summary>
///
/// </summary>
LVHT_EX_GROUP_STATEICON = 0x01000000,
/// <summary>
///
/// </summary>
LVHT_EX_GROUP_SUBSETLINK = 0x02000000,
/// <summary>
///
/// </summary>
LVHT_EX_GROUP = (LVHT_EX_GROUP_BACKGROUND | LVHT_EX_GROUP_COLLAPSE | LVHT_EX_GROUP_FOOTER | LVHT_EX_GROUP_HEADER | LVHT_EX_GROUP_STATEICON | LVHT_EX_GROUP_SUBSETLINK),
/// <summary>
///
/// </summary>
LVHT_EX_GROUP_MINUS_FOOTER_AND_BKGRD = (LVHT_EX_GROUP_COLLAPSE | LVHT_EX_GROUP_HEADER | LVHT_EX_GROUP_STATEICON | LVHT_EX_GROUP_SUBSETLINK),
/// <summary>
///
/// </summary>
LVHT_EX_ONCONTENTS = 0x04000000, // On item AND not on the background
/// <summary>
///
/// </summary>
LVHT_EX_FOOTER = 0x08000000,
}
/// <summary>
/// Instances of this class encapsulate the information gathered during a OlvHitTest()
/// operation.
/// </summary>
/// <remarks>Custom renderers can use HitTestLocation.UserDefined and the UserData
/// object to store more specific locations for use during event handlers.</remarks>
public class OlvListViewHitTestInfo {
/// <summary>
/// Create a OlvListViewHitTestInfo
/// </summary>
public OlvListViewHitTestInfo(OLVListItem olvListItem, OLVListSubItem subItem, int flags, OLVGroup group, int iColumn)
{
this.item = olvListItem;
this.subItem = subItem;
this.location = ConvertNativeFlagsToDotNetLocation(olvListItem, flags);
this.HitTestLocationEx = (HitTestLocationEx)flags;
this.Group = group;
this.ColumnIndex = iColumn;
this.ListView = olvListItem == null ? null : (ObjectListView)olvListItem.ListView;
switch (location) {
case ListViewHitTestLocations.StateImage:
this.HitTestLocation = HitTestLocation.CheckBox;
break;
case ListViewHitTestLocations.Image:
this.HitTestLocation = HitTestLocation.Image;
break;
case ListViewHitTestLocations.Label:
this.HitTestLocation = HitTestLocation.Text;
break;
default:
if ((this.HitTestLocationEx & HitTestLocationEx.LVHT_EX_GROUP_COLLAPSE) == HitTestLocationEx.LVHT_EX_GROUP_COLLAPSE)
this.HitTestLocation = HitTestLocation.GroupExpander;
else if ((this.HitTestLocationEx & HitTestLocationEx.LVHT_EX_GROUP_MINUS_FOOTER_AND_BKGRD) != 0)
this.HitTestLocation = HitTestLocation.Group;
else
this.HitTestLocation = HitTestLocation.Nothing;
break;
}
}
/// <summary>
/// Create a OlvListViewHitTestInfo when the header was hit
/// </summary>
public OlvListViewHitTestInfo(ObjectListView olv, int iColumn, bool isOverCheckBox, int iDivider) {
this.ListView = olv;
this.ColumnIndex = iColumn;
this.HeaderDividerIndex = iDivider;
this.HitTestLocation = isOverCheckBox ? HitTestLocation.HeaderCheckBox : (iDivider < 0 ? HitTestLocation.Header : HitTestLocation.HeaderDivider);
}
private static ListViewHitTestLocations ConvertNativeFlagsToDotNetLocation(OLVListItem hitItem, int flags)
{
// Untangle base .NET behaviour.
// In Windows SDK, the value 8 can have two meanings here: LVHT_ONITEMSTATEICON or LVHT_ABOVE.
// .NET changes these to be:
// - LVHT_ABOVE becomes ListViewHitTestLocations.AboveClientArea (which is 0x100).
// - LVHT_ONITEMSTATEICON becomes ListViewHitTestLocations.StateImage (which is 0x200).
// So, if we see the 8 bit set in flags, we change that to either a state image hit
// (if we hit an item) or to AboveClientAream if nothing was hit.
if ((8 & flags) == 8)
return (ListViewHitTestLocations)(0xf7 & flags | (hitItem == null ? 0x100 : 0x200));
// Mask off the LVHT_EX_XXXX values since ListViewHitTestLocations doesn't have them
return (ListViewHitTestLocations)(flags & 0xffff);
}
#region Public fields
/// <summary>
/// Where is the hit location?
/// </summary>
public HitTestLocation HitTestLocation;
/// <summary>
/// Where is the hit location?
/// </summary>
public HitTestLocationEx HitTestLocationEx;
/// <summary>
/// Which group was hit?
/// </summary>
public OLVGroup Group;
/// <summary>
/// Custom renderers can use this information to supply more details about the hit location
/// </summary>
public Object UserData;
#endregion
#region Public read-only properties
/// <summary>
/// Gets the item that was hit
/// </summary>
public OLVListItem Item {
get { return item; }
internal set { item = value; }
}
private OLVListItem item;
/// <summary>
/// Gets the subitem that was hit
/// </summary>
public OLVListSubItem SubItem {
get { return subItem; }
internal set { subItem = value; }
}
private OLVListSubItem subItem;
/// <summary>
/// Gets the part of the subitem that was hit
/// </summary>
public ListViewHitTestLocations Location {
get { return location; }
internal set { location = value; }
}
private ListViewHitTestLocations location;
/// <summary>
/// Gets the ObjectListView that was tested
/// </summary>
public ObjectListView ListView {
get { return listView; }
internal set { listView = value; }
}
private ObjectListView listView;
/// <summary>
/// Gets the model object that was hit
/// </summary>
public Object RowObject {
get {
return this.Item == null ? null : this.Item.RowObject;
}
}
/// <summary>
/// Gets the index of the row under the hit point or -1
/// </summary>
public int RowIndex {
get { return this.Item == null ? -1 : this.Item.Index; }
}
/// <summary>
/// Gets the index of the column under the hit point
/// </summary>
public int ColumnIndex {
get { return columnIndex; }
internal set { columnIndex = value; }
}
private int columnIndex;
/// <summary>
/// Gets the index of the header divider
/// </summary>
public int HeaderDividerIndex {
get { return headerDividerIndex; }
internal set { headerDividerIndex = value; }
}
private int headerDividerIndex = -1;
/// <summary>
/// Gets the column that was hit
/// </summary>
public OLVColumn Column {
get {
int index = this.ColumnIndex;
return index < 0 || this.ListView == null ? null : this.ListView.GetColumn(index);
}
}
#endregion
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>
/// A string that represents the current object.
/// </returns>
/// <filterpriority>2</filterpriority>
public override string ToString()
{
return string.Format("HitTestLocation: {0}, HitTestLocationEx: {1}, Item: {2}, SubItem: {3}, Location: {4}, Group: {5}, ColumnIndex: {6}",
this.HitTestLocation, this.HitTestLocationEx, this.item, this.subItem, this.location, this.Group, this.ColumnIndex);
}
internal class HeaderHitTestInfo
{
public int ColumnIndex;
public bool IsOverCheckBox;
public int OverDividerIndex;
}
}
}

View File

@@ -0,0 +1,262 @@
using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
namespace BrightIdeasSoftware
{
/// <summary>
/// A TreeDataSourceAdapter knows how to build a tree structure from a binding list.
/// </summary>
/// <remarks>To build a tree</remarks>
public class TreeDataSourceAdapter : DataSourceAdapter
{
#region Life and death
/// <summary>
/// Create a data source adaptor that knows how to build a tree structure
/// </summary>
/// <param name="tlv"></param>
public TreeDataSourceAdapter(DataTreeListView tlv)
: base(tlv) {
this.treeListView = tlv;
this.treeListView.CanExpandGetter = delegate(object model) { return this.CalculateHasChildren(model); };
this.treeListView.ChildrenGetter = delegate(object model) { return this.CalculateChildren(model); };
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the name of the property/column that uniquely identifies each row.
/// </summary>
/// <remarks>
/// <para>
/// The value contained by this column must be unique across all rows
/// in the data source. Odd and unpredictable things will happen if two
/// rows have the same id.
/// </para>
/// <para>Null cannot be a valid key value.</para>
/// </remarks>
public virtual string KeyAspectName {
get { return keyAspectName; }
set {
if (keyAspectName == value)
return;
keyAspectName = value;
this.keyMunger = new Munger(this.KeyAspectName);
this.InitializeDataSource();
}
}
private string keyAspectName;
/// <summary>
/// Gets or sets the name of the property/column that contains the key of
/// the parent of a row.
/// </summary>
/// <remarks>
/// <para>
/// The test condition for deciding if one row is the parent of another is functionally
/// equivilent to this:
/// <code>
/// Object.Equals(candidateParentRow[this.KeyAspectName], row[this.ParentKeyAspectName])
/// </code>
/// </para>
/// <para>Unlike key value, parent keys can be null but a null parent key can only be used
/// to identify root objects.</para>
/// </remarks>
public virtual string ParentKeyAspectName {
get { return parentKeyAspectName; }
set {
if (parentKeyAspectName == value)
return;
parentKeyAspectName = value;
this.parentKeyMunger = new Munger(this.ParentKeyAspectName);
this.InitializeDataSource();
}
}
private string parentKeyAspectName;
/// <summary>
/// Gets or sets the value that identifies a row as a root object.
/// When the ParentKey of a row equals the RootKeyValue, that row will
/// be treated as root of the TreeListView.
/// </summary>
/// <remarks>
/// <para>
/// The test condition for deciding a root object is functionally
/// equivilent to this:
/// <code>
/// Object.Equals(candidateRow[this.ParentKeyAspectName], this.RootKeyValue)
/// </code>
/// </para>
/// <para>The RootKeyValue can be null.</para>
/// </remarks>
public virtual object RootKeyValue {
get { return rootKeyValue; }
set {
if (Equals(rootKeyValue, value))
return;
rootKeyValue = value;
this.InitializeDataSource();
}
}
private object rootKeyValue;
/// <summary>
/// Gets or sets whether or not the key columns (id and parent id) should
/// be shown to the user.
/// </summary>
/// <remarks>This must be set before the DataSource is set. It has no effect
/// afterwards.</remarks>
public virtual bool ShowKeyColumns {
get { return showKeyColumns; }
set { showKeyColumns = value; }
}
private bool showKeyColumns = true;
#endregion
#region Implementation properties
/// <summary>
/// Gets the DataTreeListView that is being managed
/// </summary>
protected DataTreeListView TreeListView {
get { return treeListView; }
}
private readonly DataTreeListView treeListView;
#endregion
#region Implementation
/// <summary>
///
/// </summary>
protected override void InitializeDataSource() {
base.InitializeDataSource();
this.TreeListView.RebuildAll(true);
}
/// <summary>
///
/// </summary>
protected override void SetListContents() {
this.TreeListView.Roots = this.CalculateRoots();
}
/// <summary>
///
/// </summary>
/// <param name="property"></param>
/// <returns></returns>
protected override bool ShouldCreateColumn(PropertyDescriptor property) {
// If the property is a key column, and we aren't supposed to show keys, don't show it
if (!this.ShowKeyColumns && (property.Name == this.KeyAspectName || property.Name == this.ParentKeyAspectName))
return false;
return base.ShouldCreateColumn(property);
}
/// <summary>
///
/// </summary>
/// <param name="e"></param>
protected override void HandleListChangedItemChanged(System.ComponentModel.ListChangedEventArgs e) {
// If the id or the parent id of a row changes, we just rebuild everything.
// We can't do anything more specific. We don't know what the previous values, so we can't
// tell the previous parent to refresh itself. If the id itself has changed, things that used
// to be children will no longer be children. Just rebuild everything.
// It seems PropertyDescriptor is only filled in .NET 4 :(
if (e.PropertyDescriptor != null &&
(e.PropertyDescriptor.Name == this.KeyAspectName ||
e.PropertyDescriptor.Name == this.ParentKeyAspectName))
this.InitializeDataSource();
else
base.HandleListChangedItemChanged(e);
}
/// <summary>
///
/// </summary>
/// <param name="index"></param>
protected override void ChangePosition(int index) {
// We can't use our base method directly, since the normal position management
// doesn't know about our tree structure. They treat our dataset as a flat list
// but we have a collapsable structure. This means that the 5'th row to them
// may not even be visible to us
// To display the n'th row, we have to make sure that all its ancestors
// are expanded. Then we will be able to select it.
object model = this.CurrencyManager.List[index];
object parent = this.CalculateParent(model);
while (parent != null && !this.TreeListView.IsExpanded(parent)) {
this.TreeListView.Expand(parent);
parent = this.CalculateParent(parent);
}
base.ChangePosition(index);
}
private IEnumerable CalculateRoots() {
foreach (object x in this.CurrencyManager.List) {
object parentKey = this.GetParentValue(x);
if (Object.Equals(this.RootKeyValue, parentKey))
yield return x;
}
}
private bool CalculateHasChildren(object model) {
object keyValue = this.GetKeyValue(model);
if (keyValue == null)
return false;
foreach (object x in this.CurrencyManager.List) {
object parentKey = this.GetParentValue(x);
if (Object.Equals(keyValue, parentKey))
return true;
}
return false;
}
private IEnumerable CalculateChildren(object model) {
object keyValue = this.GetKeyValue(model);
if (keyValue != null) {
foreach (object x in this.CurrencyManager.List) {
object parentKey = this.GetParentValue(x);
if (Object.Equals(keyValue, parentKey))
yield return x;
}
}
}
private object CalculateParent(object model) {
object parentValue = this.GetParentValue(model);
if (parentValue == null)
return null;
foreach (object x in this.CurrencyManager.List) {
object key = this.GetKeyValue(x);
if (Object.Equals(parentValue, key))
return x;
}
return null;
}
private object GetKeyValue(object model) {
return this.keyMunger == null ? null : this.keyMunger.GetValue(model);
}
private object GetParentValue(object model) {
return this.parentKeyMunger == null ? null : this.parentKeyMunger.GetValue(model);
}
#endregion
private Munger keyMunger;
private Munger parentKeyMunger;
}
}

View File

@@ -0,0 +1,352 @@
/*
* Virtual groups - Classes and interfaces needed to implement virtual groups
*
* Author: Phillip Piper
* Date: 28/08/2009 11:10am
*
* Change log:
* 2011-02-21 JPP - Correctly honor group comparer and collapsible groups settings
* v2.3
* 2009-08-28 JPP - Initial version
*
* To do:
*
* Copyright (C) 2009-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace BrightIdeasSoftware
{
/// <summary>
/// A IVirtualGroups is the interface that a virtual list must implement to support virtual groups
/// </summary>
public interface IVirtualGroups
{
/// <summary>
/// Return the list of groups that should be shown according to the given parameters
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
IList<OLVGroup> GetGroups(GroupingParameters parameters);
/// <summary>
/// Return the index of the item that appears at the given position within the given group.
/// </summary>
/// <param name="group"></param>
/// <param name="indexWithinGroup"></param>
/// <returns></returns>
int GetGroupMember(OLVGroup group, int indexWithinGroup);
/// <summary>
/// Return the index of the group to which the given item belongs
/// </summary>
/// <param name="itemIndex"></param>
/// <returns></returns>
int GetGroup(int itemIndex);
/// <summary>
/// Return the index at which the given item is shown in the given group
/// </summary>
/// <param name="group"></param>
/// <param name="itemIndex"></param>
/// <returns></returns>
int GetIndexWithinGroup(OLVGroup group, int itemIndex);
/// <summary>
/// A hint that the given range of items are going to be required
/// </summary>
/// <param name="fromGroupIndex"></param>
/// <param name="fromIndex"></param>
/// <param name="toGroupIndex"></param>
/// <param name="toIndex"></param>
void CacheHint(int fromGroupIndex, int fromIndex, int toGroupIndex, int toIndex);
}
/// <summary>
/// This is a safe, do nothing implementation of a grouping strategy
/// </summary>
public class AbstractVirtualGroups : IVirtualGroups
{
/// <summary>
/// Return the list of groups that should be shown according to the given parameters
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
public virtual IList<OLVGroup> GetGroups(GroupingParameters parameters) {
return new List<OLVGroup>();
}
/// <summary>
/// Return the index of the item that appears at the given position within the given group.
/// </summary>
/// <param name="group"></param>
/// <param name="indexWithinGroup"></param>
/// <returns></returns>
public virtual int GetGroupMember(OLVGroup group, int indexWithinGroup) {
return -1;
}
/// <summary>
/// Return the index of the group to which the given item belongs
/// </summary>
/// <param name="itemIndex"></param>
/// <returns></returns>
public virtual int GetGroup(int itemIndex) {
return -1;
}
/// <summary>
/// Return the index at which the given item is shown in the given group
/// </summary>
/// <param name="group"></param>
/// <param name="itemIndex"></param>
/// <returns></returns>
public virtual int GetIndexWithinGroup(OLVGroup group, int itemIndex) {
return -1;
}
/// <summary>
/// A hint that the given range of items are going to be required
/// </summary>
/// <param name="fromGroupIndex"></param>
/// <param name="fromIndex"></param>
/// <param name="toGroupIndex"></param>
/// <param name="toIndex"></param>
public virtual void CacheHint(int fromGroupIndex, int fromIndex, int toGroupIndex, int toIndex) {
}
}
/// <summary>
/// Provides grouping functionality to a FastObjectListView
/// </summary>
public class FastListGroupingStrategy : AbstractVirtualGroups
{
/// <summary>
/// Create groups for FastListView
/// </summary>
/// <param name="parmameters"></param>
/// <returns></returns>
public override IList<OLVGroup> GetGroups(GroupingParameters parmameters) {
// There is a lot of overlap between this method and ObjectListView.MakeGroups()
// Any changes made here may need to be reflected there
// This strategy can only be used on FastObjectListViews
FastObjectListView folv = (FastObjectListView)parmameters.ListView;
// Separate the list view items into groups, using the group key as the descrimanent
int objectCount = 0;
NullableDictionary<object, List<object>> map = new NullableDictionary<object, List<object>>();
foreach (object model in folv.FilteredObjects) {
object key = parmameters.GroupByColumn.GetGroupKey(model);
if (!map.ContainsKey(key))
map[key] = new List<object>();
map[key].Add(model);
objectCount++;
}
// Sort the items within each group
// TODO: Give parameters a ModelComparer property
OLVColumn primarySortColumn = parmameters.SortItemsByPrimaryColumn ? parmameters.ListView.GetColumn(0) : parmameters.PrimarySort;
ModelObjectComparer sorter = new ModelObjectComparer(primarySortColumn, parmameters.PrimarySortOrder,
parmameters.SecondarySort, parmameters.SecondarySortOrder);
foreach (object key in map.Keys) {
map[key].Sort(sorter);
}
// Make a list of the required groups
List<OLVGroup> groups = new List<OLVGroup>();
foreach (object key in map.Keys) {
string title = parmameters.GroupByColumn.ConvertGroupKeyToTitle(key);
if (!String.IsNullOrEmpty(parmameters.TitleFormat)) {
int count = map[key].Count;
string format = (count == 1 ? parmameters.TitleSingularFormat : parmameters.TitleFormat);
try {
title = String.Format(format, title, count);
} catch (FormatException) {
title = "Invalid group format: " + format;
}
}
OLVGroup lvg = new OLVGroup(title);
lvg.Collapsible = folv.HasCollapsibleGroups;
lvg.Key = key;
lvg.SortValue = key as IComparable;
lvg.Contents = map[key].ConvertAll<int>(delegate(object x) { return folv.IndexOf(x); });
lvg.VirtualItemCount = map[key].Count;
if (parmameters.GroupByColumn.GroupFormatter != null)
parmameters.GroupByColumn.GroupFormatter(lvg, parmameters);
groups.Add(lvg);
}
// Sort the groups
if (parmameters.GroupByOrder != SortOrder.None)
groups.Sort(parmameters.GroupComparer ?? new OLVGroupComparer(parmameters.GroupByOrder));
// Build an array that remembers which group each item belongs to.
this.indexToGroupMap = new List<int>(objectCount);
this.indexToGroupMap.AddRange(new int[objectCount]);
for (int i = 0; i < groups.Count; i++) {
OLVGroup group = groups[i];
List<int> members = (List<int>)group.Contents;
foreach (int j in members)
this.indexToGroupMap[j] = i;
}
return groups;
}
private List<int> indexToGroupMap;
/// <summary>
///
/// </summary>
/// <param name="group"></param>
/// <param name="indexWithinGroup"></param>
/// <returns></returns>
public override int GetGroupMember(OLVGroup group, int indexWithinGroup) {
return (int)group.Contents[indexWithinGroup];
}
/// <summary>
///
/// </summary>
/// <param name="itemIndex"></param>
/// <returns></returns>
public override int GetGroup(int itemIndex) {
return this.indexToGroupMap[itemIndex];
}
/// <summary>
///
/// </summary>
/// <param name="group"></param>
/// <param name="itemIndex"></param>
/// <returns></returns>
public override int GetIndexWithinGroup(OLVGroup group, int itemIndex) {
return group.Contents.IndexOf(itemIndex);
}
}
/// <summary>
/// This is the COM interface that a ListView must be given in order for groups in virtual lists to work.
/// </summary>
/// <remarks>
/// This interface is NOT documented by MS. It was found on Greg Chapell's site. This means that there is
/// no guarantee that it will work on future versions of Windows, nor continue to work on current ones.
/// </remarks>
[ComImport(),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("44C09D56-8D3B-419D-A462-7B956B105B47")]
internal interface IOwnerDataCallback
{
/// <summary>
/// Not sure what this does
/// </summary>
/// <param name="i"></param>
/// <param name="pt"></param>
void GetItemPosition(int i, out NativeMethods.POINT pt);
/// <summary>
/// Not sure what this does
/// </summary>
/// <param name="t"></param>
/// <param name="pt"></param>
void SetItemPosition(int t, NativeMethods.POINT pt);
/// <summary>
/// Get the index of the item that occurs at the n'th position of the indicated group.
/// </summary>
/// <param name="groupIndex">Index of the group</param>
/// <param name="n">Index within the group</param>
/// <param name="itemIndex">Index of the item within the whole list</param>
void GetItemInGroup(int groupIndex, int n, out int itemIndex);
/// <summary>
/// Get the index of the group to which the given item belongs
/// </summary>
/// <param name="itemIndex">Index of the item within the whole list</param>
/// <param name="occurrenceCount">Which occurences of the item is wanted</param>
/// <param name="groupIndex">Index of the group</param>
void GetItemGroup(int itemIndex, int occurrenceCount, out int groupIndex);
/// <summary>
/// Get the number of groups that contain the given item
/// </summary>
/// <param name="itemIndex">Index of the item within the whole list</param>
/// <param name="occurrenceCount">How many groups does it occur within</param>
void GetItemGroupCount(int itemIndex, out int occurrenceCount);
/// <summary>
/// A hint to prepare any cache for the given range of requests
/// </summary>
/// <param name="i"></param>
/// <param name="j"></param>
void OnCacheHint(NativeMethods.LVITEMINDEX i, NativeMethods.LVITEMINDEX j);
}
/// <summary>
/// A default implementation of the IOwnerDataCallback interface
/// </summary>
[Guid("6FC61F50-80E8-49b4-B200-3F38D3865ABD")]
internal class OwnerDataCallbackImpl(VirtualObjectListView olv) : IOwnerDataCallback
{
VirtualObjectListView olv = olv;
#region IOwnerDataCallback Members
public void GetItemPosition(int i, out NativeMethods.POINT pt) {
//System.Diagnostics.Debug.WriteLine("GetItemPosition");
throw new NotSupportedException();
}
public void SetItemPosition(int t, NativeMethods.POINT pt) {
//System.Diagnostics.Debug.WriteLine("SetItemPosition");
throw new NotSupportedException();
}
public void GetItemInGroup(int groupIndex, int n, out int itemIndex) {
//System.Diagnostics.Debug.WriteLine(String.Format("-> GetItemInGroup({0}, {1})", groupIndex, n));
itemIndex = this.olv.GroupingStrategy.GetGroupMember(this.olv.OLVGroups[groupIndex], n);
//System.Diagnostics.Debug.WriteLine(String.Format("<- {0}", itemIndex));
}
public void GetItemGroup(int itemIndex, int occurrenceCount, out int groupIndex) {
//System.Diagnostics.Debug.WriteLine(String.Format("GetItemGroup({0}, {1})", itemIndex, occurrenceCount));
groupIndex = this.olv.GroupingStrategy.GetGroup(itemIndex);
//System.Diagnostics.Debug.WriteLine(String.Format("<- {0}", groupIndex));
}
public void GetItemGroupCount(int itemIndex, out int occurrenceCount) {
//System.Diagnostics.Debug.WriteLine(String.Format("GetItemGroupCount({0})", itemIndex));
occurrenceCount = 1;
}
public void OnCacheHint(NativeMethods.LVITEMINDEX from, NativeMethods.LVITEMINDEX to) {
//System.Diagnostics.Debug.WriteLine(String.Format("OnCacheHint({0}, {1}, {2}, {3})", from.iGroup, from.iItem, to.iGroup, to.iItem));
this.olv.GroupingStrategy.CacheHint(from.iGroup, from.iItem, to.iGroup, to.iItem);
}
#endregion
}
}

View File

@@ -0,0 +1,343 @@
/*
* VirtualListDataSource - Encapsulate how data is provided to a virtual list
*
* Author: Phillip Piper
* Date: 28/08/2009 11:10am
*
* Change log:
* v2.4
* 2010-04-01 JPP - Added IFilterableDataSource
* v2.3
* 2009-08-28 JPP - Initial version (Separated from VirtualObjectListView.cs)
*
* To do:
*
* Copyright (C) 2009-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.Windows.Forms;
namespace BrightIdeasSoftware
{
/// <summary>
/// A VirtualListDataSource is a complete manner to provide functionality to a virtual list.
/// An object that implements this interface provides a VirtualObjectListView with all the
/// information it needs to be fully functional.
/// </summary>
/// <remarks>Implementors must provide functioning implementations of at least GetObjectCount()
/// and GetNthObject(), otherwise nothing will appear in the list.</remarks>
public interface IVirtualListDataSource
{
/// <summary>
/// Return the object that should be displayed at the n'th row.
/// </summary>
/// <param name="n">The index of the row whose object is to be returned.</param>
/// <returns>The model object at the n'th row, or null if the fetching was unsuccessful.</returns>
Object GetNthObject(int n);
/// <summary>
/// Return the number of rows that should be visible in the virtual list
/// </summary>
/// <returns>The number of rows the list view should have.</returns>
int GetObjectCount();
/// <summary>
/// Get the index of the row that is showing the given model object
/// </summary>
/// <param name="model">The model object sought</param>
/// <returns>The index of the row showing the model, or -1 if the object could not be found.</returns>
int GetObjectIndex(Object model);
/// <summary>
/// The ListView is about to request the given range of items. Do
/// whatever caching seems appropriate.
/// </summary>
/// <param name="first"></param>
/// <param name="last"></param>
void PrepareCache(int first, int last);
/// <summary>
/// Find the first row that "matches" the given text in the given range.
/// </summary>
/// <param name="value">The text typed by the user</param>
/// <param name="first">Start searching from this index. This may be greater than the 'to' parameter,
/// in which case the search should descend</param>
/// <param name="last">Do not search beyond this index. This may be less than the 'from' parameter.</param>
/// <param name="column">The column that should be considered when looking for a match.</param>
/// <returns>Return the index of row that was matched, or -1 if no match was found</returns>
int SearchText(string value, int first, int last, OLVColumn column);
/// <summary>
/// Sort the model objects in the data source.
/// </summary>
/// <param name="column"></param>
/// <param name="order"></param>
void Sort(OLVColumn column, SortOrder order);
//-----------------------------------------------------------------------------------
// Modification commands
// THINK: Should we split these four into a separate interface?
/// <summary>
/// Add the given collection of model objects to this control.
/// </summary>
/// <param name="modelObjects">A collection of model objects</param>
void AddObjects(ICollection modelObjects);
/// <summary>
/// Insert the given collection of model objects to this control at the position
/// </summary>
/// <param name="index">Index where the collection will be added</param>
/// <param name="modelObjects">A collection of model objects</param>
void InsertObjects(int index, ICollection modelObjects);
/// <summary>
/// Remove all of the given objects from the control
/// </summary>
/// <param name="modelObjects">Collection of objects to be removed</param>
void RemoveObjects(ICollection modelObjects);
/// <summary>
/// Set the collection of objects that this control will show.
/// </summary>
/// <param name="collection"></param>
void SetObjects(IEnumerable collection);
/// <summary>
/// Update/replace the nth object with the given object
/// </summary>
/// <param name="index"></param>
/// <param name="modelObject"></param>
void UpdateObject(int index, object modelObject);
}
/// <summary>
/// This extension allow virtual lists to filter their contents
/// </summary>
public interface IFilterableDataSource
{
/// <summary>
/// All subsequent retrievals on this data source should be filtered
/// through the given filters. null means no filtering of that kind.
/// </summary>
/// <param name="modelFilter"></param>
/// <param name="listFilter"></param>
void ApplyFilters(IModelFilter modelFilter, IListFilter listFilter);
}
/// <summary>
/// A do-nothing implementation of the VirtualListDataSource interface.
/// </summary>
/// <remarks>
/// Creates an AbstractVirtualListDataSource
/// </remarks>
/// <param name="listView"></param>
public class AbstractVirtualListDataSource(VirtualObjectListView listView) : IVirtualListDataSource, IFilterableDataSource
{
/// <summary>
/// The list view that this data source is giving information to.
/// </summary>
protected VirtualObjectListView listView = listView;
/// <summary>
///
/// </summary>
/// <param name="n"></param>
/// <returns></returns>
public virtual object GetNthObject(int n) {
return null;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public virtual int GetObjectCount() {
return -1;
}
/// <summary>
///
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public virtual int GetObjectIndex(object model) {
return -1;
}
/// <summary>
///
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
public virtual void PrepareCache(int from, int to) {
}
/// <summary>
///
/// </summary>
/// <param name="value"></param>
/// <param name="first"></param>
/// <param name="last"></param>
/// <param name="column"></param>
/// <returns></returns>
public virtual int SearchText(string value, int first, int last, OLVColumn column) {
return -1;
}
/// <summary>
///
/// </summary>
/// <param name="column"></param>
/// <param name="order"></param>
public virtual void Sort(OLVColumn column, SortOrder order) {
}
/// <summary>
///
/// </summary>
/// <param name="modelObjects"></param>
public virtual void AddObjects(ICollection modelObjects) {
}
/// <summary>
///
/// </summary>
/// <param name="index"></param>
/// <param name="modelObjects"></param>
public virtual void InsertObjects(int index, ICollection modelObjects) {
}
/// <summary>
///
/// </summary>
/// <param name="modelObjects"></param>
public virtual void RemoveObjects(ICollection modelObjects) {
}
/// <summary>
///
/// </summary>
/// <param name="collection"></param>
public virtual void SetObjects(IEnumerable collection) {
}
/// <summary>
/// Update/replace the nth object with the given object
/// </summary>
/// <param name="index"></param>
/// <param name="modelObject"></param>
public virtual void UpdateObject(int index, object modelObject) {
}
/// <summary>
/// This is a useful default implementation of SearchText method, intended to be called
/// by implementors of IVirtualListDataSource.
/// </summary>
/// <param name="value"></param>
/// <param name="first"></param>
/// <param name="last"></param>
/// <param name="column"></param>
/// <param name="source"></param>
/// <returns></returns>
static public int DefaultSearchText(string value, int first, int last, OLVColumn column, IVirtualListDataSource source) {
if (first <= last) {
for (int i = first; i <= last; i++) {
string data = column.GetStringValue(source.GetNthObject(i));
if (data.StartsWith(value, StringComparison.CurrentCultureIgnoreCase))
return i;
}
} else {
for (int i = first; i >= last; i--) {
string data = column.GetStringValue(source.GetNthObject(i));
if (data.StartsWith(value, StringComparison.CurrentCultureIgnoreCase))
return i;
}
}
return -1;
}
#region IFilterableDataSource Members
/// <summary>
///
/// </summary>
/// <param name="modelFilter"></param>
/// <param name="listFilter"></param>
virtual public void ApplyFilters(IModelFilter modelFilter, IListFilter listFilter) {
}
#endregion
}
/// <summary>
/// This class mimics the behavior of VirtualObjectListView v1.x.
/// </summary>
/// <remarks>
/// Creates a VirtualListVersion1DataSource
/// </remarks>
/// <param name="listView"></param>
public class VirtualListVersion1DataSource(VirtualObjectListView listView) : AbstractVirtualListDataSource(listView)
{
#region Public properties
/// <summary>
/// How will the n'th object of the data source be fetched?
/// </summary>
public RowGetterDelegate RowGetter {
get { return rowGetter; }
set { rowGetter = value; }
}
private RowGetterDelegate rowGetter;
#endregion
#region IVirtualListDataSource implementation
/// <summary>
///
/// </summary>
/// <param name="n"></param>
/// <returns></returns>
public override object GetNthObject(int n) {
if (this.RowGetter == null)
return null;
else
return this.RowGetter(n);
}
/// <summary>
///
/// </summary>
/// <param name="value"></param>
/// <param name="first"></param>
/// <param name="last"></param>
/// <param name="column"></param>
/// <returns></returns>
public override int SearchText(string value, int first, int last, OLVColumn column) {
return DefaultSearchText(value, first, last, column, this);
}
#endregion
}
}

1911
ObjectListView/OLVColumn.cs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,536 @@
/*
* DesignSupport - Design time support for the various classes within ObjectListView
*
* Author: Phillip Piper
* Date: 12/08/2009 8:36 PM
*
* Change log:
* 2012-08-27 JPP - Fall back to more specific type name for the ListViewDesigner if
* the first GetType() fails.
* v2.5.1
* 2012-04-26 JPP - Filter group events from TreeListView since it can't have groups
* 2011-06-06 JPP - Vastly improved ObjectListViewDesigner, based off information in
* "'Inheriting' from an Internal WinForms Designer" on CodeProject.
* v2.3
* 2009-08-12 JPP - Initial version
*
* To do:
*
* Copyright (C) 2009-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
using System.Windows.Forms.Design;
namespace BrightIdeasSoftware.Design
{
/// <summary>
/// Designer for <see cref="ObjectListView"/> and its subclasses.
/// </summary>
/// <remarks>
/// <para>
/// This designer removes properties and events that are available on ListView but that are not
/// useful on ObjectListView.
/// </para>
/// <para>
/// We can't inherit from System.Windows.Forms.Design.ListViewDesigner, since it is marked internal.
/// So, this class uses reflection to create a ListViewDesigner and then forwards messages to that designer.
/// </para>
/// </remarks>
public class ObjectListViewDesigner : ControlDesigner
{
#region Initialize & Dispose
/// <summary>
/// Initializes the designer with the specified component.
/// </summary>
/// <param name="component">The <see cref="T:System.ComponentModel.IComponent"/> to associate the designer with. This component must always be an instance of, or derive from, <see cref="T:System.Windows.Forms.Control"/>. </param>
public override void Initialize(IComponent component) {
// Debug.WriteLine("ObjectListViewDesigner.Initialize");
// Use reflection to bypass the "internal" marker on ListViewDesigner
// If we can't get the unversioned designer, look specifically for .NET 4.0 version of it.
Type tListViewDesigner = Type.GetType("System.Windows.Forms.Design.ListViewDesigner, System.Design") ??
Type.GetType("System.Windows.Forms.Design.ListViewDesigner, System.Design, " +
"Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
if (tListViewDesigner == null) throw new ArgumentException("Could not load ListViewDesigner");
this.listViewDesigner = (ControlDesigner)Activator.CreateInstance(tListViewDesigner, BindingFlags.Instance | BindingFlags.Public, null, null, null);
this.designerFilter = this.listViewDesigner;
// Fetch the methods from the ListViewDesigner that we know we want to use
this.listViewDesignGetHitTest = tListViewDesigner.GetMethod("GetHitTest", BindingFlags.Instance | BindingFlags.NonPublic);
this.listViewDesignWndProc = tListViewDesigner.GetMethod("WndProc", BindingFlags.Instance | BindingFlags.NonPublic);
Debug.Assert(this.listViewDesignGetHitTest != null, "Required method (GetHitTest) not found on ListViewDesigner");
Debug.Assert(this.listViewDesignWndProc != null, "Required method (WndProc) not found on ListViewDesigner");
// Tell the Designer to use properties of default designer as well as the properties of this class (do before base.Initialize)
TypeDescriptor.CreateAssociation(component, this.listViewDesigner);
IServiceContainer site = (IServiceContainer)component.Site;
if (site != null && GetService(typeof(DesignerCommandSet)) == null) {
site.AddService(typeof(DesignerCommandSet), new CDDesignerCommandSet(this));
} else {
Debug.Fail("site != null && GetService(typeof (DesignerCommandSet)) == null");
}
this.listViewDesigner.Initialize(component);
base.Initialize(component);
RemoveDuplicateDockingActionList();
}
/// <summary>
/// Initializes a newly created component.
/// </summary>
/// <param name="defaultValues">A name/value dictionary of default values to apply to properties. May be null if no default values are specified.</param>
public override void InitializeNewComponent(IDictionary defaultValues) {
// Debug.WriteLine("ObjectListViewDesigner.InitializeNewComponent");
base.InitializeNewComponent(defaultValues);
this.listViewDesigner.InitializeNewComponent(defaultValues);
}
/// <summary>
/// Releases the unmanaged resources used by the <see cref="T:System.Windows.Forms.Design.ControlDesigner"/> and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources. </param>
protected override void Dispose(bool disposing) {
// Debug.WriteLine("ObjectListViewDesigner.Dispose");
if (disposing) {
if (this.listViewDesigner != null) {
this.listViewDesigner.Dispose();
// Normally we would now null out the designer, but this designer
// still has methods called AFTER it is disposed.
}
}
base.Dispose(disposing);
}
/// <summary>
/// Removes the duplicate DockingActionList added by this designer to the <see cref="DesignerActionService"/>.
/// </summary>
/// <remarks>
/// <see cref="ControlDesigner.Initialize"/> adds an internal DockingActionList : 'Dock/Undock in Parent Container'.
/// But the default designer has already added that action list. So we need to remove one.
/// </remarks>
private void RemoveDuplicateDockingActionList() {
// This is a true hack -- in a class that is basically a huge hack itself.
// Reach into the bowel of our base class, get a private field, and use that fields value to
// remove an action from the designer.
// In ControlDesigner, there is "private DockingActionList dockingAction;"
// Don't you just love Reflector?!
FieldInfo fi = typeof(ControlDesigner).GetField("dockingAction", BindingFlags.Instance | BindingFlags.NonPublic);
if (fi != null) {
DesignerActionList dockingAction = (DesignerActionList)fi.GetValue(this);
if (dockingAction != null) {
DesignerActionService service = (DesignerActionService)GetService(typeof(DesignerActionService));
if (service != null) {
service.Remove(this.Control, dockingAction);
}
}
}
}
#endregion
#region IDesignerFilter overrides
/// <summary>
/// Adjusts the set of properties the component exposes through a <see cref="T:System.ComponentModel.TypeDescriptor"/>.
/// </summary>
/// <param name="properties">An <see cref="T:System.Collections.IDictionary"/> containing the properties for the class of the component. </param>
protected override void PreFilterProperties(IDictionary properties) {
// Debug.WriteLine("ObjectListViewDesigner.PreFilterProperties");
// Always call the base PreFilterProperties implementation
// before you modify the properties collection.
base.PreFilterProperties(properties);
// Give the listviewdesigner a chance to filter the properties
// (though we already know it's not going to do anything)
this.designerFilter.PreFilterProperties(properties);
// I'd like to just remove the redundant properties, but that would
// break backward compatibility. The deserialiser that handles the XXX.Designer.cs file
// works off the designer, so even if the property exists in the class, the deserialiser will
// throw an error if the associated designer actually removes that property.
// So we shadow the unwanted properties, and give the replacement properties
// non-browsable attributes so that they are hidden from the user
List<string> unwantedProperties = new List<string>(new string[] {
"BackgroundImage", "BackgroundImageTiled", "HotTracking", "HoverSelection",
"LabelEdit", "VirtualListSize", "VirtualMode" });
// Also hid Tooltip properties, since giving a tooltip to the control through the IDE
// messes up the tooltip handling
foreach (string propertyName in properties.Keys) {
if (propertyName.StartsWith("ToolTip")) {
unwantedProperties.Add(propertyName);
}
}
// If we are looking at a TreeListView, remove group related properties
// since TreeListViews can't show groups
if (this.Control is TreeListView) {
unwantedProperties.AddRange(new string[] {
"GroupImageList", "GroupWithItemCountFormat", "GroupWithItemCountSingularFormat", "HasCollapsibleGroups",
"SpaceBetweenGroups", "ShowGroups", "SortGroupItemsByPrimaryColumn", "ShowItemCountOnGroups"
});
}
// Shadow the unwanted properties, and give the replacement properties
// non-browsable attributes so that they are hidden from the user
foreach (string unwantedProperty in unwantedProperties) {
PropertyDescriptor propertyDesc = TypeDescriptor.CreateProperty(
typeof(ObjectListView),
(PropertyDescriptor)properties[unwantedProperty],
new BrowsableAttribute(false));
properties[unwantedProperty] = propertyDesc;
}
}
/// <summary>
/// Allows a designer to add to the set of events that it exposes through a <see cref="T:System.ComponentModel.TypeDescriptor"/>.
/// </summary>
/// <param name="events">The events for the class of the component. </param>
protected override void PreFilterEvents(IDictionary events) {
// Debug.WriteLine("ObjectListViewDesigner.PreFilterEvents");
base.PreFilterEvents(events);
this.designerFilter.PreFilterEvents(events);
// Remove the events that don't make sense for an ObjectListView.
// See PreFilterProperties() for why we do this dance rather than just remove the event.
List<string> unwanted = new List<string>(new string[] {
"AfterLabelEdit",
"BeforeLabelEdit",
"DrawColumnHeader",
"DrawItem",
"DrawSubItem",
"RetrieveVirtualItem",
"SearchForVirtualItem",
"VirtualItemsSelectionRangeChanged"
});
// If we are looking at a TreeListView, remove group related events
// since TreeListViews can't show groups
if (this.Control is TreeListView) {
unwanted.AddRange(new string[] {
"AboutToCreateGroups",
"AfterCreatingGroups",
"BeforeCreatingGroups",
"GroupTaskClicked",
"GroupExpandingCollapsing",
"GroupStateChanged"
});
}
foreach (string unwantedEvent in unwanted) {
EventDescriptor eventDesc = TypeDescriptor.CreateEvent(
typeof(ObjectListView),
(EventDescriptor)events[unwantedEvent],
new BrowsableAttribute(false));
events[unwantedEvent] = eventDesc;
}
}
/// <summary>
/// Allows a designer to change or remove items from the set of attributes that it exposes through a <see cref="T:System.ComponentModel.TypeDescriptor"/>.
/// </summary>
/// <param name="attributes">The attributes for the class of the component. </param>
protected override void PostFilterAttributes(IDictionary attributes) {
// Debug.WriteLine("ObjectListViewDesigner.PostFilterAttributes");
this.designerFilter.PostFilterAttributes(attributes);
base.PostFilterAttributes(attributes);
}
/// <summary>
/// Allows a designer to change or remove items from the set of events that it exposes through a <see cref="T:System.ComponentModel.TypeDescriptor"/>.
/// </summary>
/// <param name="events">The events for the class of the component. </param>
protected override void PostFilterEvents(IDictionary events) {
// Debug.WriteLine("ObjectListViewDesigner.PostFilterEvents");
this.designerFilter.PostFilterEvents(events);
base.PostFilterEvents(events);
}
#endregion
#region Overrides
/// <summary>
/// Gets the design-time action lists supported by the component associated with the designer.
/// </summary>
/// <returns>
/// The design-time action lists supported by the component associated with the designer.
/// </returns>
public override DesignerActionListCollection ActionLists {
get {
// We want to change the first action list so it only has the commands we want
DesignerActionListCollection actionLists = this.listViewDesigner.ActionLists;
if (actionLists.Count > 0 && !(actionLists[0] is ListViewActionListAdapter)) {
actionLists[0] = new ListViewActionListAdapter(this, actionLists[0]);
}
return actionLists;
}
}
/// <summary>
/// Gets the collection of components associated with the component managed by the designer.
/// </summary>
/// <returns>
/// The components that are associated with the component managed by the designer.
/// </returns>
public override ICollection AssociatedComponents {
get {
ArrayList components = new ArrayList(base.AssociatedComponents);
components.AddRange(this.listViewDesigner.AssociatedComponents);
return components;
}
}
/// <summary>
/// Indicates whether a mouse click at the specified point should be handled by the control.
/// </summary>
/// <returns>
/// true if a click at the specified point is to be handled by the control; otherwise, false.
/// </returns>
/// <param name="point">A <see cref="T:System.Drawing.Point"/> indicating the position at which the mouse was clicked, in screen coordinates. </param>
protected override bool GetHitTest(Point point) {
// The ListViewDesigner wants to allow column dividers to be resized
return (bool)this.listViewDesignGetHitTest.Invoke(listViewDesigner, new object[] { point });
}
/// <summary>
/// Processes Windows messages and optionally routes them to the control.
/// </summary>
/// <param name="m">The <see cref="T:System.Windows.Forms.Message"/> to process. </param>
protected override void WndProc(ref Message m) {
switch (m.Msg) {
case 0x4e:
case 0x204e:
// The listview designer is interested in HDN_ENDTRACK notifications
this.listViewDesignWndProc.Invoke(listViewDesigner, new object[] { m });
break;
default:
base.WndProc(ref m);
break;
}
}
#endregion
#region Implementation variables
private ControlDesigner listViewDesigner;
private IDesignerFilter designerFilter;
private MethodInfo listViewDesignGetHitTest;
private MethodInfo listViewDesignWndProc;
#endregion
#region Custom action list
/// <summary>
/// This class modifies a ListViewActionList, by removing the "Edit Items" and "Edit Groups" actions.
/// </summary>
/// <remarks>
/// <para>
/// That class is internal, so we cannot simply subclass it, which would be simplier.
/// </para>
/// <para>
/// Action lists use reflection to determine if that action can be executed, so we not
/// only have to modify the returned collection of actions, but we have to implement
/// the properties and commands that the returned actions use. </para>
/// </remarks>
private class ListViewActionListAdapter(ObjectListViewDesigner designer, DesignerActionList wrappedList) : DesignerActionList(wrappedList.Component)
{
public override DesignerActionItemCollection GetSortedActionItems() {
DesignerActionItemCollection items = wrappedList.GetSortedActionItems();
items.RemoveAt(2); // remove Edit Groups
items.RemoveAt(0); // remove Edit Items
return items;
}
private void EditValue(ComponentDesigner componentDesigner, IComponent iComponent, string propertyName) {
// One more complication. The ListViewActionList classes uses an internal class, EditorServiceContext, to
// edit the items/columns/groups collections. So, we use reflection to bypass the data hiding.
Type tEditorServiceContext = Type.GetType("System.Windows.Forms.Design.EditorServiceContext, System.Design");
tEditorServiceContext.InvokeMember("EditValue", BindingFlags.InvokeMethod | BindingFlags.Static, null, null, new object[] { componentDesigner, iComponent, propertyName });
}
private void SetValue(object target, string propertyName, object value) {
TypeDescriptor.GetProperties(target)[propertyName].SetValue(target, value);
}
public void InvokeColumnsDialog() {
EditValue(this.designer, base.Component, "Columns");
}
// Don't need these since we removed their corresponding actions from the list.
// Keep the methods just in case.
//public void InvokeGroupsDialog() {
// EditValue(this.designer, base.Component, "Groups");
//}
//public void InvokeItemsDialog() {
// EditValue(this.designer, base.Component, "Items");
//}
public ImageList LargeImageList {
get { return ((ListView)base.Component).LargeImageList; }
set { SetValue(base.Component, "LargeImageList", value); }
}
public ImageList SmallImageList {
get { return ((ListView)base.Component).SmallImageList; }
set { SetValue(base.Component, "SmallImageList", value); }
}
public View View {
get { return ((ListView)base.Component).View; }
set { SetValue(base.Component, "View", value); }
}
ObjectListViewDesigner designer = designer;
DesignerActionList wrappedList = wrappedList;
}
#endregion
#region DesignerCommandSet
private class CDDesignerCommandSet(ComponentDesigner componentDesigner) : DesignerCommandSet
{
public override ICollection GetCommands(string name) {
// Debug.WriteLine("CDDesignerCommandSet.GetCommands:" + name);
if (componentDesigner != null) {
if (name.Equals("Verbs")) {
return componentDesigner.Verbs;
}
if (name.Equals("ActionLists")) {
return componentDesigner.ActionLists;
}
}
return base.GetCommands(name);
}
private readonly ComponentDesigner componentDesigner = componentDesigner;
}
#endregion
}
/// <summary>
/// This class works in conjunction with the OLVColumns property to allow OLVColumns
/// to be added to the ObjectListView.
/// </summary>
/// <remarks>
/// Create a OLVColumnCollectionEditor
/// </remarks>
/// <param name="t"></param>
public class OLVColumnCollectionEditor(Type t) : System.ComponentModel.Design.CollectionEditor(t)
{
/// <summary>
/// What type of object does this editor create?
/// </summary>
/// <returns></returns>
protected override Type CreateCollectionItemType() {
return typeof(OLVColumn);
}
/// <summary>
/// Edit a given value
/// </summary>
/// <param name="context"></param>
/// <param name="provider"></param>
/// <param name="value"></param>
/// <returns></returns>
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) {
if (context == null)
throw new ArgumentNullException("context");
if (provider == null)
throw new ArgumentNullException("provider");
// Figure out which ObjectListView we are working on. This should be the Instance of the context.
ObjectListView olv = context.Instance as ObjectListView;
Debug.Assert(olv != null, "Instance must be an ObjectListView");
// Edit all the columns, not just the ones that are visible
base.EditValue(context, provider, olv.AllColumns);
// Set the columns on the ListView to just the visible columns
List<OLVColumn> newColumns = olv.GetFilteredColumns(View.Details);
olv.Columns.Clear();
olv.Columns.AddRange(newColumns.ToArray());
return olv.Columns;
}
/// <summary>
/// What text should be shown in the list for the given object?
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
protected override string GetDisplayText(object value) {
OLVColumn col = value as OLVColumn;
if (col == null || String.IsNullOrEmpty(col.AspectName))
return base.GetDisplayText(value);
return String.Format("{0} ({1})", base.GetDisplayText(value), col.AspectName);
}
}
/// <summary>
/// Control how the overlay is presented in the IDE
/// </summary>
internal class OverlayConverter : ExpandableObjectConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) {
if (destinationType == typeof(string)) {
ImageOverlay imageOverlay = value as ImageOverlay;
if (imageOverlay != null) {
return imageOverlay.Image == null ? "(none)" : "(set)";
}
TextOverlay textOverlay = value as TextOverlay;
if (textOverlay != null) {
return String.IsNullOrEmpty(textOverlay.Text) ? "(none)" : "(set)";
}
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}

View File

@@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
<Deterministic>false</Deterministic>
<RootNamespace>BrightIdeasSoftware</RootNamespace>
<AssemblyName>ObjectListView</AssemblyName>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<UseWindowsForms>true</UseWindowsForms>
<EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
<NoWarn>$(NoWarn);WFO1000</NoWarn>
<EnableWinFormsAnalyzers>false</EnableWinFormsAnalyzers>
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)' == 'x64'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)' == 'ARM64'">
<PlatformTarget>ARM64</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<Content Include="CustomDictionary.xml" Link="CustomDictionary.xml" />
<Content Include="Resources\clear-filter.png" Link="Resources\clear-filter.png" />
<Content Include="Resources\coffee.jpg" Link="Resources\coffee.jpg" />
<Content Include="Resources\filter-icons3.png" Link="Resources\filter-icons3.png" />
<Content Include="Resources\filter.png" Link="Resources\filter.png" />
<Content Include="Resources\sort-ascending.png" Link="Resources\sort-ascending.png" />
<Content Include="Resources\sort-descending.png" Link="Resources\sort-descending.png" />
</ItemGroup>
<ItemGroup>
<Folder Include="CellEditing\" />
<Folder Include="DragDrop\" />
<Folder Include="Filtering\" />
<Folder Include="Implementation\" />
<Folder Include="Utilities\" />
<Folder Include="SubControls\" />
<Folder Include="Resources\" />
<Folder Include="Rendering\" />
</ItemGroup>
</Project>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/01/nuspec.xsd">
<metadata minClientVersion="2.12">
<id>ObjectListView.Updated</id>
<title>ObjectListView (Updated)</title>
<version>$version$</version>
<authors>Phillip Piper</authors>
<owners>$author$</owners>
<license type="file">LICENSE</license>
<icon>.editoricon.png</icon>
<projectUrl>https://github.com/ennerperez/ObjectListView</projectUrl>
<description>
ObjectListView is a .NET ListView wired on caffeine, guarana and steroids.
More calmly, it is a C# wrapper around a .NET ListView, which makes the ListView much easier to use and teaches it lots of neat new tricks.
</description>
<summary>$description$</summary>
<copyright>$copyright$</copyright>
<tags>.Net WinForms ListView Controls</tags>
<repository type="git" url="https://github.com/ennerperez/ObjectListView" />
<dependencies>
<group targetFramework=".NETFramework4.0" />
<group targetFramework=".NETStandard2.0">
<dependency id="System.Drawing.Common" version="4.7.0" />
</group>
</dependencies>
</metadata>
<files>
<file src="..\.editoricon.png" target=".editoricon.png" />
<file src="..\..\README.md" target="README.md" />
<file src="..\..\CHANGELOG.md" target="CHANGELOG.md" />
<file src="..\..\LICENSE" target="LICENSE" />
<!-- NETFX -->
<file src="..\ObjectListView\bin\release\ObjectListView.dll" target="lib\net40\ObjectListView.dll" />
<!-- NETCORE -->
<file src="..\ObjectListView.NetCore\bin\release\netcoreapp3.1\ObjectListView.dll" target="lib\netstandard2.0\ObjectListView.dll" />
</files>
</package>

View File

@@ -0,0 +1,22 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ObjectListView")]
[assembly: AssemblyDescription("A much easier to use ListView and friends")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Bright Ideas Software")]
[assembly: AssemblyProduct("ObjectListView")]
[assembly: AssemblyCopyright("Copyright © 2006-2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("ef28c7a8-77ae-442d-abc3-bb023fa31e57")]

View File

@@ -0,0 +1,113 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.18444
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace BrightIdeasSoftware.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("BrightIdeasSoftware.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap ClearFiltering {
get {
object obj = ResourceManager.GetObject("ClearFiltering", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap ColumnFilterIndicator {
get {
object obj = ResourceManager.GetObject("ColumnFilterIndicator", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap Filtering {
get {
object obj = ResourceManager.GetObject("Filtering", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap SortAscending {
get {
object obj = ResourceManager.GetObject("SortAscending", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap SortDescending {
get {
object obj = ResourceManager.GetObject("SortDescending", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
}
}

View File

@@ -0,0 +1,137 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="ClearFiltering" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\clear-filter.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="ColumnFilterIndicator" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\filter-icons3.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Filtering" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\filter.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="SortAscending" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\sort-ascending.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="SortDescending" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\sort-descending.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

View File

@@ -0,0 +1,743 @@
/*
* Adornments - Adornments are the basis for overlays and decorations -- things that can be rendered over the top of a ListView
*
* Author: Phillip Piper
* Date: 16/08/2009 1:02 AM
*
* Change log:
* v2.6
* 2012-08-18 JPP - Correctly dispose of brush and pen resources
* v2.3
* 2009-09-22 JPP - Added Wrap property to TextAdornment, to allow text wrapping to be disabled
* - Added ShrinkToWidth property to ImageAdornment
* 2009-08-17 JPP - Initial version
*
* To do:
* - Use IPointLocator rather than Corners
* - Add RotationCenter property ratherr than always using middle center
*
* Copyright (C) 2009-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
namespace BrightIdeasSoftware
{
/// <summary>
/// An adorment is the common base for overlays and decorations.
/// </summary>
public class GraphicAdornment
{
#region Public properties
/// <summary>
/// Gets or sets the corner of the adornment that will be positioned at the reference corner
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public System.Drawing.ContentAlignment AdornmentCorner {
get { return this.adornmentCorner; }
set { this.adornmentCorner = value; }
}
private System.Drawing.ContentAlignment adornmentCorner = System.Drawing.ContentAlignment.MiddleCenter;
/// <summary>
/// Gets or sets location within the reference rectange where the adornment will be drawn
/// </summary>
/// <remarks>This is a simplied interface to ReferenceCorner and AdornmentCorner </remarks>
[Category("ObjectListView"),
Description("How will the adornment be aligned"),
DefaultValue(System.Drawing.ContentAlignment.BottomRight),
NotifyParentProperty(true)]
public System.Drawing.ContentAlignment Alignment {
get { return this.alignment; }
set {
this.alignment = value;
this.ReferenceCorner = value;
this.AdornmentCorner = value;
}
}
private System.Drawing.ContentAlignment alignment = System.Drawing.ContentAlignment.BottomRight;
/// <summary>
/// Gets or sets the offset by which the position of the adornment will be adjusted
/// </summary>
[Category("ObjectListView"),
Description("The offset by which the position of the adornment will be adjusted"),
DefaultValue(typeof(Size), "0,0")]
public Size Offset {
get { return this.offset; }
set { this.offset = value; }
}
private Size offset = new Size();
/// <summary>
/// Gets or sets the point of the reference rectangle to which the adornment will be aligned.
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public System.Drawing.ContentAlignment ReferenceCorner {
get { return this.referenceCorner; }
set { this.referenceCorner = value; }
}
private System.Drawing.ContentAlignment referenceCorner = System.Drawing.ContentAlignment.MiddleCenter;
/// <summary>
/// Gets or sets the degree of rotation by which the adornment will be transformed.
/// The centre of rotation will be the center point of the adornment.
/// </summary>
[Category("ObjectListView"),
Description("The degree of rotation that will be applied to the adornment."),
DefaultValue(0),
NotifyParentProperty(true)]
public int Rotation {
get { return this.rotation; }
set { this.rotation = value; }
}
private int rotation;
/// <summary>
/// Gets or sets the transparency of the overlay.
/// 0 is completely transparent, 255 is completely opaque.
/// </summary>
[Category("ObjectListView"),
Description("The transparency of this adornment. 0 is completely transparent, 255 is completely opaque."),
DefaultValue(128)]
public int Transparency {
get { return this.transparency; }
set { this.transparency = Math.Min(255, Math.Max(0, value)); }
}
private int transparency = 128;
#endregion
#region Calculations
/// <summary>
/// Calculate the location of rectangle of the given size,
/// so that it's indicated corner would be at the given point.
/// </summary>
/// <param name="pt">The point</param>
/// <param name="size"></param>
/// <param name="corner">Which corner will be positioned at the reference point</param>
/// <returns></returns>
/// <example>CalculateAlignedPosition(new Point(50, 100), new Size(10, 20), System.Drawing.ContentAlignment.TopLeft) -> Point(50, 100)</example>
/// <example>CalculateAlignedPosition(new Point(50, 100), new Size(10, 20), System.Drawing.ContentAlignment.MiddleCenter) -> Point(45, 90)</example>
/// <example>CalculateAlignedPosition(new Point(50, 100), new Size(10, 20), System.Drawing.ContentAlignment.BottomRight) -> Point(40, 80)</example>
public virtual Point CalculateAlignedPosition(Point pt, Size size, System.Drawing.ContentAlignment corner) {
switch (corner) {
case System.Drawing.ContentAlignment.TopLeft:
return pt;
case System.Drawing.ContentAlignment.TopCenter:
return new Point(pt.X - (size.Width / 2), pt.Y);
case System.Drawing.ContentAlignment.TopRight:
return new Point(pt.X - size.Width, pt.Y);
case System.Drawing.ContentAlignment.MiddleLeft:
return new Point(pt.X, pt.Y - (size.Height / 2));
case System.Drawing.ContentAlignment.MiddleCenter:
return new Point(pt.X - (size.Width / 2), pt.Y - (size.Height / 2));
case System.Drawing.ContentAlignment.MiddleRight:
return new Point(pt.X - size.Width, pt.Y - (size.Height / 2));
case System.Drawing.ContentAlignment.BottomLeft:
return new Point(pt.X, pt.Y - size.Height);
case System.Drawing.ContentAlignment.BottomCenter:
return new Point(pt.X - (size.Width / 2), pt.Y - size.Height);
case System.Drawing.ContentAlignment.BottomRight:
return new Point(pt.X - size.Width, pt.Y - size.Height);
}
// Should never reach here
return pt;
}
/// <summary>
/// Calculate a rectangle that has the given size which is positioned so that
/// its alignment point is at the reference location of the given rect.
/// </summary>
/// <param name="r"></param>
/// <param name="sz"></param>
/// <returns></returns>
public virtual Rectangle CreateAlignedRectangle(Rectangle r, Size sz) {
return this.CreateAlignedRectangle(r, sz, this.ReferenceCorner, this.AdornmentCorner, this.Offset);
}
/// <summary>
/// Create a rectangle of the given size which is positioned so that
/// its indicated corner is at the indicated corner of the reference rect.
/// </summary>
/// <param name="r"></param>
/// <param name="sz"></param>
/// <param name="corner"></param>
/// <param name="referenceCorner"></param>
/// <param name="offset"></param>
/// <returns></returns>
/// <remarks>
/// <para>Creates a rectangle so that its bottom left is at the centre of the reference:
/// corner=BottomLeft, referenceCorner=MiddleCenter</para>
/// <para>This is a powerful concept that takes some getting used to, but is
/// very neat once you understand it.</para>
/// </remarks>
public virtual Rectangle CreateAlignedRectangle(Rectangle r, Size sz,
System.Drawing.ContentAlignment corner, System.Drawing.ContentAlignment referenceCorner, Size offset) {
Point referencePt = this.CalculateCorner(r, referenceCorner);
Point topLeft = this.CalculateAlignedPosition(referencePt, sz, corner);
return new Rectangle(topLeft + offset, sz);
}
/// <summary>
/// Return the point at the indicated corner of the given rectangle (it doesn't
/// have to be a corner, but a named location)
/// </summary>
/// <param name="r">The reference rectangle</param>
/// <param name="corner">Which point of the rectangle should be returned?</param>
/// <returns>A point</returns>
/// <example>CalculateReferenceLocation(new Rectangle(0, 0, 50, 100), System.Drawing.ContentAlignment.TopLeft) -> Point(0, 0)</example>
/// <example>CalculateReferenceLocation(new Rectangle(0, 0, 50, 100), System.Drawing.ContentAlignment.MiddleCenter) -> Point(25, 50)</example>
/// <example>CalculateReferenceLocation(new Rectangle(0, 0, 50, 100), System.Drawing.ContentAlignment.BottomRight) -> Point(50, 100)</example>
public virtual Point CalculateCorner(Rectangle r, System.Drawing.ContentAlignment corner) {
switch (corner) {
case System.Drawing.ContentAlignment.TopLeft:
return new Point(r.Left, r.Top);
case System.Drawing.ContentAlignment.TopCenter:
return new Point(r.X + (r.Width / 2), r.Top);
case System.Drawing.ContentAlignment.TopRight:
return new Point(r.Right, r.Top);
case System.Drawing.ContentAlignment.MiddleLeft:
return new Point(r.Left, r.Top + (r.Height / 2));
case System.Drawing.ContentAlignment.MiddleCenter:
return new Point(r.X + (r.Width / 2), r.Top + (r.Height / 2));
case System.Drawing.ContentAlignment.MiddleRight:
return new Point(r.Right, r.Top + (r.Height / 2));
case System.Drawing.ContentAlignment.BottomLeft:
return new Point(r.Left, r.Bottom);
case System.Drawing.ContentAlignment.BottomCenter:
return new Point(r.X + (r.Width / 2), r.Bottom);
case System.Drawing.ContentAlignment.BottomRight:
return new Point(r.Right, r.Bottom);
}
// Should never reach here
return r.Location;
}
/// <summary>
/// Given the item and the subitem, calculate its bounds.
/// </summary>
/// <param name="item"></param>
/// <param name="subItem"></param>
/// <returns></returns>
public virtual Rectangle CalculateItemBounds(OLVListItem item, OLVListSubItem subItem) {
if (item == null)
return Rectangle.Empty;
if (subItem == null)
return item.Bounds;
return item.GetSubItemBounds(item.SubItems.IndexOf(subItem));
}
#endregion
#region Commands
/// <summary>
/// Apply any specified rotation to the Graphic content.
/// </summary>
/// <param name="g">The Graphics to be transformed</param>
/// <param name="r">The rotation will be around the centre of this rect</param>
protected virtual void ApplyRotation(Graphics g, Rectangle r) {
if (this.Rotation == 0)
return;
// THINK: Do we want to reset the transform? I think we want to push a new transform
g.ResetTransform();
Matrix m = new Matrix();
m.RotateAt(this.Rotation, new Point(r.Left + r.Width / 2, r.Top + r.Height / 2));
g.Transform = m;
}
/// <summary>
/// Reverse the rotation created by ApplyRotation()
/// </summary>
/// <param name="g"></param>
protected virtual void UnapplyRotation(Graphics g) {
if (this.Rotation != 0)
g.ResetTransform();
}
#endregion
}
/// <summary>
/// An overlay that will draw an image over the top of the ObjectListView
/// </summary>
public class ImageAdornment : GraphicAdornment
{
#region Public properties
/// <summary>
/// Gets or sets the image that will be drawn
/// </summary>
[Category("ObjectListView"),
Description("The image that will be drawn"),
DefaultValue(null),
NotifyParentProperty(true)]
public Image Image {
get { return this.image; }
set { this.image = value; }
}
private Image image;
/// <summary>
/// Gets or sets if the image will be shrunk to fit with its horizontal bounds
/// </summary>
[Category("ObjectListView"),
Description("Will the image be shrunk to fit within its width?"),
DefaultValue(false)]
public bool ShrinkToWidth {
get { return this.shrinkToWidth; }
set { this.shrinkToWidth = value; }
}
private bool shrinkToWidth;
#endregion
#region Commands
/// <summary>
/// Draw the image in its specified location
/// </summary>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="r">The bounds of the rendering</param>
public virtual void DrawImage(Graphics g, Rectangle r) {
if (this.ShrinkToWidth)
this.DrawScaledImage(g, r, this.Image, this.Transparency);
else
this.DrawImage(g, r, this.Image, this.Transparency);
}
/// <summary>
/// Draw the image in its specified location
/// </summary>
/// <param name="image">The image to be drawn</param>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="r">The bounds of the rendering</param>
/// <param name="transparency">How transparent should the image be (0 is completely transparent, 255 is opaque)</param>
public virtual void DrawImage(Graphics g, Rectangle r, Image image, int transparency) {
if (image != null)
this.DrawImage(g, r, image, image.Size, transparency);
}
/// <summary>
/// Draw the image in its specified location
/// </summary>
/// <param name="image">The image to be drawn</param>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="r">The bounds of the rendering</param>
/// <param name="sz">How big should the image be?</param>
/// <param name="transparency">How transparent should the image be (0 is completely transparent, 255 is opaque)</param>
public virtual void DrawImage(Graphics g, Rectangle r, Image image, Size sz, int transparency) {
if (image == null)
return;
Rectangle adornmentBounds = this.CreateAlignedRectangle(r, sz);
try {
this.ApplyRotation(g, adornmentBounds);
this.DrawTransparentBitmap(g, adornmentBounds, image, transparency);
}
finally {
this.UnapplyRotation(g);
}
}
/// <summary>
/// Draw the image in its specified location, scaled so that it is not wider
/// than the given rectangle. Height is scaled proportional to the width.
/// </summary>
/// <param name="image">The image to be drawn</param>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="r">The bounds of the rendering</param>
/// <param name="transparency">How transparent should the image be (0 is completely transparent, 255 is opaque)</param>
public virtual void DrawScaledImage(Graphics g, Rectangle r, Image image, int transparency) {
if (image == null)
return;
// If the image is too wide to be drawn in the space provided, proportionally scale it down.
// Too tall images are not scaled.
Size size = image.Size;
if (image.Width > r.Width) {
float scaleRatio = (float)r.Width / (float)image.Width;
size.Height = (int)((float)image.Height * scaleRatio);
size.Width = r.Width - 1;
}
this.DrawImage(g, r, image, size, transparency);
}
/// <summary>
/// Utility to draw a bitmap transparenly.
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
/// <param name="image"></param>
/// <param name="transparency"></param>
protected virtual void DrawTransparentBitmap(Graphics g, Rectangle r, Image image, int transparency) {
ImageAttributes imageAttributes = null;
if (transparency != 255) {
imageAttributes = new ImageAttributes();
float a = (float)transparency / 255.0f;
float[][] colorMatrixElements = {
new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 0, 0, 0},
new float[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, a, 0},
new float[] {0, 0, 0, 0, 1}};
imageAttributes.SetColorMatrix(new ColorMatrix(colorMatrixElements));
}
g.DrawImage(image,
r, // destination rectangle
0, 0, image.Size.Width, image.Size.Height, // source rectangle
GraphicsUnit.Pixel,
imageAttributes);
}
#endregion
}
/// <summary>
/// An adornment that will draw text
/// </summary>
public class TextAdornment : GraphicAdornment
{
#region Public properties
/// <summary>
/// Gets or sets the background color of the text
/// Set this to Color.Empty to not draw a background
/// </summary>
[Category("ObjectListView"),
Description("The background color of the text"),
DefaultValue(typeof(Color), "")]
public Color BackColor {
get { return this.backColor; }
set { this.backColor = value; }
}
private Color backColor = Color.Empty;
/// <summary>
/// Gets the brush that will be used to paint the text
/// </summary>
[Browsable(false)]
public Brush BackgroundBrush {
get {
return new SolidBrush(Color.FromArgb(this.workingTransparency, this.BackColor));
}
}
/// <summary>
/// Gets or sets the color of the border around the billboard.
/// Set this to Color.Empty to remove the border
/// </summary>
[Category("ObjectListView"),
Description("The color of the border around the text"),
DefaultValue(typeof(Color), "")]
public Color BorderColor {
get { return this.borderColor; }
set { this.borderColor = value; }
}
private Color borderColor = Color.Empty;
/// <summary>
/// Gets the brush that will be used to paint the text
/// </summary>
[Browsable(false)]
public Pen BorderPen {
get {
return new Pen(Color.FromArgb(this.workingTransparency, this.BorderColor), this.BorderWidth);
}
}
/// <summary>
/// Gets or sets the width of the border around the text
/// </summary>
[Category("ObjectListView"),
Description("The width of the border around the text"),
DefaultValue(0.0f)]
public float BorderWidth {
get { return this.borderWidth; }
set { this.borderWidth = value; }
}
private float borderWidth;
/// <summary>
/// How rounded should the corners of the border be? 0 means no rounding.
/// </summary>
/// <remarks>If this value is too large, the edges of the border will appear odd.</remarks>
[Category("ObjectListView"),
Description("How rounded should the corners of the border be? 0 means no rounding."),
DefaultValue(16.0f),
NotifyParentProperty(true)]
public float CornerRounding {
get { return this.cornerRounding; }
set { this.cornerRounding = value; }
}
private float cornerRounding = 16.0f;
/// <summary>
/// Gets or sets the font that will be used to draw the text
/// </summary>
[Category("ObjectListView"),
Description("The font that will be used to draw the text"),
DefaultValue(null),
NotifyParentProperty(true)]
public Font Font {
get { return this.font; }
set { this.font = value; }
}
private Font font;
/// <summary>
/// Gets the font that will be used to draw the text or a reasonable default
/// </summary>
[Browsable(false)]
public Font FontOrDefault {
get {
return this.Font ?? new Font("Tahoma", 16);
}
}
/// <summary>
/// Does this text have a background?
/// </summary>
[Browsable(false)]
public bool HasBackground {
get {
return this.BackColor != Color.Empty;
}
}
/// <summary>
/// Does this overlay have a border?
/// </summary>
[Browsable(false)]
public bool HasBorder {
get {
return this.BorderColor != Color.Empty && this.BorderWidth > 0;
}
}
/// <summary>
/// Gets or sets the maximum width of the text. Text longer than this will wrap.
/// 0 means no maximum.
/// </summary>
[Category("ObjectListView"),
Description("The maximum width the text (0 means no maximum). Text longer than this will wrap"),
DefaultValue(0)]
public int MaximumTextWidth {
get { return this.maximumTextWidth; }
set { this.maximumTextWidth = value; }
}
private int maximumTextWidth;
/// <summary>
/// Gets or sets the formatting that should be used on the text
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public virtual StringFormat StringFormat {
get {
if (this.stringFormat == null) {
this.stringFormat = new StringFormat();
this.stringFormat.Alignment = StringAlignment.Center;
this.stringFormat.LineAlignment = StringAlignment.Center;
this.stringFormat.Trimming = StringTrimming.EllipsisCharacter;
if (!this.Wrap)
this.stringFormat.FormatFlags = StringFormatFlags.NoWrap;
}
return this.stringFormat;
}
set { this.stringFormat = value; }
}
private StringFormat stringFormat;
/// <summary>
/// Gets or sets the text that will be drawn
/// </summary>
[Category("ObjectListView"),
Description("The text that will be drawn over the top of the ListView"),
DefaultValue(null),
NotifyParentProperty(true),
Localizable(true)]
public string Text {
get { return this.text; }
set { this.text = value; }
}
private string text;
/// <summary>
/// Gets the brush that will be used to paint the text
/// </summary>
[Browsable(false)]
public Brush TextBrush {
get {
return new SolidBrush(Color.FromArgb(this.workingTransparency, this.TextColor));
}
}
/// <summary>
/// Gets or sets the color of the text
/// </summary>
[Category("ObjectListView"),
Description("The color of the text"),
DefaultValue(typeof(Color), "DarkBlue"),
NotifyParentProperty(true)]
public Color TextColor {
get { return this.textColor; }
set { this.textColor = value; }
}
private Color textColor = Color.DarkBlue;
/// <summary>
/// Gets or sets whether the text will wrap when it exceeds its bounds
/// </summary>
[Category("ObjectListView"),
Description("Will the text wrap?"),
DefaultValue(true)]
public bool Wrap {
get { return this.wrap; }
set { this.wrap = value; }
}
private bool wrap = true;
#endregion
#region Implementation
/// <summary>
/// Draw our text with our stored configuration in relation to the given
/// reference rectangle
/// </summary>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="r">The reference rectangle in relation to which the text will be drawn</param>
public virtual void DrawText(Graphics g, Rectangle r) {
this.DrawText(g, r, this.Text, this.Transparency);
}
/// <summary>
/// Draw the given text with our stored configuration
/// </summary>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="r">The reference rectangle in relation to which the text will be drawn</param>
/// <param name="s">The text to draw</param>
/// <param name="transparency">How opaque should be text be</param>
public virtual void DrawText(Graphics g, Rectangle r, string s, int transparency) {
if (String.IsNullOrEmpty(s))
return;
Rectangle textRect = this.CalculateTextBounds(g, r, s);
this.DrawBorderedText(g, textRect, s, transparency);
}
/// <summary>
/// Draw the text with a border
/// </summary>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="textRect">The bounds within which the text should be drawn</param>
/// <param name="text">The text to draw</param>
/// <param name="transparency">How opaque should be text be</param>
protected virtual void DrawBorderedText(Graphics g, Rectangle textRect, string text, int transparency) {
Rectangle borderRect = textRect;
borderRect.Inflate((int)this.BorderWidth / 2, (int)this.BorderWidth / 2);
borderRect.Y -= 1; // Looker better a little higher
try {
this.ApplyRotation(g, textRect);
using (GraphicsPath path = this.GetRoundedRect(borderRect, this.CornerRounding)) {
this.workingTransparency = transparency;
if (this.HasBackground) {
using (Brush b = this.BackgroundBrush)
g.FillPath(b, path);
}
using (Brush b = this.TextBrush)
g.DrawString(text, this.FontOrDefault, b, textRect, this.StringFormat);
if (this.HasBorder) {
using (Pen p = this.BorderPen)
g.DrawPath(p, path);
}
}
}
finally {
this.UnapplyRotation(g);
}
}
/// <summary>
/// Return the rectangle that will be the precise bounds of the displayed text
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
/// <param name="s"></param>
/// <returns>The bounds of the text</returns>
protected virtual Rectangle CalculateTextBounds(Graphics g, Rectangle r, string s) {
int maxWidth = this.MaximumTextWidth <= 0 ? r.Width : this.MaximumTextWidth;
SizeF sizeF = g.MeasureString(s, this.FontOrDefault, maxWidth, this.StringFormat);
Size size = new Size(1 + (int)sizeF.Width, 1 + (int)sizeF.Height);
return this.CreateAlignedRectangle(r, size);
}
/// <summary>
/// Return a GraphicPath that is a round cornered rectangle
/// </summary>
/// <param name="rect">The rectangle</param>
/// <param name="diameter">The diameter of the corners</param>
/// <returns>A round cornered rectagle path</returns>
/// <remarks>If I could rely on people using C# 3.0+, this should be
/// an extension method of GraphicsPath.</remarks>
protected virtual GraphicsPath GetRoundedRect(Rectangle rect, float diameter) {
GraphicsPath path = new GraphicsPath();
if (diameter > 0) {
RectangleF arc = new RectangleF(rect.X, rect.Y, diameter, diameter);
path.AddArc(arc, 180, 90);
arc.X = rect.Right - diameter;
path.AddArc(arc, 270, 90);
arc.Y = rect.Bottom - diameter;
path.AddArc(arc, 0, 90);
arc.X = rect.Left;
path.AddArc(arc, 90, 90);
path.CloseFigure();
} else {
path.AddRectangle(rect);
}
return path;
}
#endregion
private int workingTransparency;
}
}

View File

@@ -0,0 +1,820 @@
/*
* Decorations - Images, text or other things that can be rendered onto an ObjectListView
*
* Author: Phillip Piper
* Date: 19/08/2009 10:56 PM
*
* Change log:
* 2011-04-04 JPP - Added ability to have a gradient background on BorderDecoration
* v2.4
* 2010-04-15 JPP - Tweaked LightBoxDecoration a little
* v2.3
* 2009-09-23 JPP - Added LeftColumn and RightColumn to RowBorderDecoration
* 2009-08-23 JPP - Added LightBoxDecoration
* 2009-08-19 JPP - Initial version. Separated from Overlays.cs
*
* To do:
*
* Copyright (C) 2009-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Windows.Forms;
namespace BrightIdeasSoftware
{
/// <summary>
/// A decoration is an overlay that draws itself in relation to a given row or cell.
/// Decorations scroll when the listview scrolls.
/// </summary>
public interface IDecoration : IOverlay
{
/// <summary>
/// Gets or sets the row that is to be decorated
/// </summary>
OLVListItem ListItem { get; set; }
/// <summary>
/// Gets or sets the subitem that is to be decorated
/// </summary>
OLVListSubItem SubItem { get; set; }
}
/// <summary>
/// An AbstractDecoration is a safe do-nothing implementation of the IDecoration interface
/// </summary>
public class AbstractDecoration : IDecoration
{
#region IDecoration Members
/// <summary>
/// Gets or sets the row that is to be decorated
/// </summary>
public OLVListItem ListItem {
get { return listItem; }
set { listItem = value; }
}
private OLVListItem listItem;
/// <summary>
/// Gets or sets the subitem that is to be decorated
/// </summary>
public OLVListSubItem SubItem {
get { return subItem; }
set { subItem = value; }
}
private OLVListSubItem subItem;
#endregion
#region Public properties
/// <summary>
/// Gets the bounds of the decorations row
/// </summary>
public Rectangle RowBounds {
get {
if (this.ListItem == null)
return Rectangle.Empty;
else
return this.ListItem.Bounds;
}
}
/// <summary>
/// Get the bounds of the decorations cell
/// </summary>
public Rectangle CellBounds {
get {
if (this.ListItem == null || this.SubItem == null)
return Rectangle.Empty;
else
return this.ListItem.GetSubItemBounds(this.ListItem.SubItems.IndexOf(this.SubItem));
}
}
#endregion
#region IOverlay Members
/// <summary>
/// Draw the decoration
/// </summary>
/// <param name="olv"></param>
/// <param name="g"></param>
/// <param name="r"></param>
public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) {
}
#endregion
}
/// <summary>
/// This decoration draws a slight tint over a column of the
/// owning listview. If no column is explicitly set, the selected
/// column in the listview will be used.
/// The selected column is normally the sort column, but does not have to be.
/// </summary>
public class TintedColumnDecoration : AbstractDecoration
{
#region Constructors
/// <summary>
/// Create a TintedColumnDecoration
/// </summary>
public TintedColumnDecoration() {
this.Tint = Color.FromArgb(15, Color.Blue);
}
/// <summary>
/// Create a TintedColumnDecoration
/// </summary>
/// <param name="column"></param>
public TintedColumnDecoration(OLVColumn column)
: this() {
this.ColumnToTint = column;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the column that will be tinted
/// </summary>
public OLVColumn ColumnToTint {
get { return this.columnToTint; }
set { this.columnToTint = value; }
}
private OLVColumn columnToTint;
/// <summary>
/// Gets or sets the color that will be 'tinted' over the selected column
/// </summary>
public Color Tint {
get { return this.tint; }
set {
if (this.tint == value)
return;
if (this.tintBrush != null) {
this.tintBrush.Dispose();
this.tintBrush = null;
}
this.tint = value;
this.tintBrush = new SolidBrush(this.tint);
}
}
private Color tint;
private SolidBrush tintBrush;
#endregion
#region IOverlay Members
/// <summary>
/// Draw a slight colouring over our tinted column
/// </summary>
/// <remarks>
/// This overlay only works when:
/// - the list is in Details view
/// - there is at least one row
/// - there is a selected column (or a specified tint column)
/// </remarks>
/// <param name="olv"></param>
/// <param name="g"></param>
/// <param name="r"></param>
public override void Draw(ObjectListView olv, Graphics g, Rectangle r) {
if (olv.View != System.Windows.Forms.View.Details)
return;
if (olv.GetItemCount() == 0)
return;
OLVColumn column = this.ColumnToTint ?? olv.SelectedColumn;
if (column == null)
return;
Point sides = NativeMethods.GetScrolledColumnSides(olv, column.Index);
if (sides.X == -1)
return;
Rectangle columnBounds = new Rectangle(sides.X, r.Top, sides.Y - sides.X, r.Bottom);
// Find the bottom of the last item. The tinting should extend only to there.
OLVListItem lastItem = olv.GetLastItemInDisplayOrder();
if (lastItem != null) {
Rectangle lastItemBounds = lastItem.Bounds;
if (!lastItemBounds.IsEmpty && lastItemBounds.Bottom < columnBounds.Bottom)
columnBounds.Height = lastItemBounds.Bottom - columnBounds.Top;
}
g.FillRectangle(this.tintBrush, columnBounds);
}
#endregion
}
/// <summary>
/// This decoration draws an optionally filled border around a rectangle.
/// Subclasses must override CalculateBounds().
/// </summary>
public class BorderDecoration : AbstractDecoration
{
#region Constructors
/// <summary>
/// Create a BorderDecoration
/// </summary>
public BorderDecoration()
: this(new Pen(Color.FromArgb(64, Color.Blue), 1)) {
}
/// <summary>
/// Create a BorderDecoration
/// </summary>
/// <param name="borderPen">The pen used to draw the border</param>
public BorderDecoration(Pen borderPen) {
this.BorderPen = borderPen;
}
/// <summary>
/// Create a BorderDecoration
/// </summary>
/// <param name="borderPen">The pen used to draw the border</param>
/// <param name="fill">The brush used to fill the rectangle</param>
public BorderDecoration(Pen borderPen, Brush fill) {
this.BorderPen = borderPen;
this.FillBrush = fill;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the pen that will be used to draw the border
/// </summary>
public Pen BorderPen {
get { return this.borderPen; }
set { this.borderPen = value; }
}
private Pen borderPen;
/// <summary>
/// Gets or sets the padding that will be added to the bounds of the item
/// before drawing the border and fill.
/// </summary>
public Size BoundsPadding {
get { return this.boundsPadding; }
set { this.boundsPadding = value; }
}
private Size boundsPadding = new Size(-1, 2);
/// <summary>
/// How rounded should the corners of the border be? 0 means no rounding.
/// </summary>
/// <remarks>If this value is too large, the edges of the border will appear odd.</remarks>
public float CornerRounding {
get { return this.cornerRounding; }
set { this.cornerRounding = value; }
}
private float cornerRounding = 16.0f;
/// <summary>
/// Gets or sets the brush that will be used to fill the border
/// </summary>
/// <remarks>This value is ignored when using gradient brush</remarks>
public Brush FillBrush {
get { return this.fillBrush; }
set { this.fillBrush = value; }
}
private Brush fillBrush = new SolidBrush(Color.FromArgb(64, Color.Blue));
/// <summary>
/// Gets or sets the color that will be used as the start of a gradient fill.
/// </summary>
/// <remarks>This and FillGradientTo must be given value to show a gradient</remarks>
public Color? FillGradientFrom {
get { return this.fillGradientFrom; }
set { this.fillGradientFrom = value; }
}
private Color? fillGradientFrom;
/// <summary>
/// Gets or sets the color that will be used as the end of a gradient fill.
/// </summary>
/// <remarks>This and FillGradientFrom must be given value to show a gradient</remarks>
public Color? FillGradientTo {
get { return this.fillGradientTo; }
set { this.fillGradientTo = value; }
}
private Color? fillGradientTo;
/// <summary>
/// Gets or sets the fill mode that will be used for the gradient.
/// </summary>
public LinearGradientMode FillGradientMode {
get { return this.fillGradientMode; }
set { this.fillGradientMode = value; }
}
private LinearGradientMode fillGradientMode = LinearGradientMode.Vertical;
#endregion
#region IOverlay Members
/// <summary>
/// Draw a filled border
/// </summary>
/// <param name="olv"></param>
/// <param name="g"></param>
/// <param name="r"></param>
public override void Draw(ObjectListView olv, Graphics g, Rectangle r) {
Rectangle bounds = this.CalculateBounds();
if (!bounds.IsEmpty)
this.DrawFilledBorder(g, bounds);
}
#endregion
#region Subclass responsibility
/// <summary>
/// Subclasses should override this to say where the border should be drawn
/// </summary>
/// <returns></returns>
protected virtual Rectangle CalculateBounds() {
return Rectangle.Empty;
}
#endregion
#region Implementation utlities
/// <summary>
/// Do the actual work of drawing the filled border
/// </summary>
/// <param name="g"></param>
/// <param name="bounds"></param>
protected void DrawFilledBorder(Graphics g, Rectangle bounds) {
bounds.Inflate(this.BoundsPadding);
GraphicsPath path = this.GetRoundedRect(bounds, this.CornerRounding);
if (this.FillGradientFrom != null && this.FillGradientTo != null) {
if (this.FillBrush != null)
this.FillBrush.Dispose();
this.FillBrush = new LinearGradientBrush(bounds, this.FillGradientFrom.Value, this.FillGradientTo.Value, this.FillGradientMode);
}
if (this.FillBrush != null)
g.FillPath(this.FillBrush, path);
if (this.BorderPen != null)
g.DrawPath(this.BorderPen, path);
}
/// <summary>
/// Create a GraphicsPath that represents a round cornered rectangle.
/// </summary>
/// <param name="rect"></param>
/// <param name="diameter">If this is 0 or less, the rectangle will not be rounded.</param>
/// <returns></returns>
protected GraphicsPath GetRoundedRect(RectangleF rect, float diameter) {
GraphicsPath path = new GraphicsPath();
if (diameter <= 0.0f) {
path.AddRectangle(rect);
} else {
RectangleF arc = new RectangleF(rect.X, rect.Y, diameter, diameter);
path.AddArc(arc, 180, 90);
arc.X = rect.Right - diameter;
path.AddArc(arc, 270, 90);
arc.Y = rect.Bottom - diameter;
path.AddArc(arc, 0, 90);
arc.X = rect.Left;
path.AddArc(arc, 90, 90);
path.CloseFigure();
}
return path;
}
#endregion
}
/// <summary>
/// Instances of this class draw a border around the decorated row
/// </summary>
public class RowBorderDecoration : BorderDecoration
{
/// <summary>
/// Gets or sets the index of the left most column to be used for the border
/// </summary>
public int LeftColumn {
get { return leftColumn; }
set { leftColumn = value; }
}
private int leftColumn = -1;
/// <summary>
/// Gets or sets the index of the right most column to be used for the border
/// </summary>
public int RightColumn {
get { return rightColumn; }
set { rightColumn = value; }
}
private int rightColumn = -1;
/// <summary>
/// Calculate the boundaries of the border
/// </summary>
/// <returns></returns>
protected override Rectangle CalculateBounds() {
Rectangle bounds = this.RowBounds;
if (this.ListItem == null)
return bounds;
if (this.LeftColumn >= 0) {
Rectangle leftCellBounds = this.ListItem.GetSubItemBounds(this.LeftColumn);
if (!leftCellBounds.IsEmpty) {
bounds.Width = bounds.Right - leftCellBounds.Left;
bounds.X = leftCellBounds.Left;
}
}
if (this.RightColumn >= 0) {
Rectangle rightCellBounds = this.ListItem.GetSubItemBounds(this.RightColumn);
if (!rightCellBounds.IsEmpty) {
bounds.Width = rightCellBounds.Right - bounds.Left;
}
}
return bounds;
}
}
/// <summary>
/// Instances of this class draw a border around the decorated subitem.
/// </summary>
public class CellBorderDecoration : BorderDecoration
{
/// <summary>
/// Calculate the boundaries of the border
/// </summary>
/// <returns></returns>
protected override Rectangle CalculateBounds() {
return this.CellBounds;
}
}
/// <summary>
/// This decoration puts a border around the cell being edited and
/// optionally "lightboxes" the cell (makes the rest of the control dark).
/// </summary>
public class EditingCellBorderDecoration : BorderDecoration
{
#region Life and death
/// <summary>
/// Create a EditingCellBorderDecoration
/// </summary>
public EditingCellBorderDecoration() {
this.FillBrush = null;
this.BorderPen = new Pen(Color.DarkBlue, 2);
this.CornerRounding = 8;
this.BoundsPadding = new Size(10, 8);
}
/// <summary>
/// Create a EditingCellBorderDecoration
/// </summary>
/// <param name="useLightBox">Should the decoration use a lighbox display style?</param>
public EditingCellBorderDecoration(bool useLightBox) : this()
{
this.UseLightbox = useLightbox;
}
#endregion
#region Configuration properties
/// <summary>
/// Gets or set whether the decoration should make the rest of
/// the control dark when a cell is being edited
/// </summary>
/// <remarks>If this is true, FillBrush is used to overpaint
/// the control.</remarks>
public bool UseLightbox {
get { return this.useLightbox; }
set {
if (this.useLightbox == value)
return;
this.useLightbox = value;
if (this.useLightbox) {
if (this.FillBrush == null)
this.FillBrush = new SolidBrush(Color.FromArgb(64, Color.Black));
}
}
}
private bool useLightbox;
#endregion
#region Implementation
/// <summary>
/// Draw the decoration
/// </summary>
/// <param name="olv"></param>
/// <param name="g"></param>
/// <param name="r"></param>
public override void Draw(ObjectListView olv, Graphics g, Rectangle r) {
if (!olv.IsCellEditing)
return;
Rectangle bounds = olv.CellEditor.Bounds;
if (bounds.IsEmpty)
return;
bounds.Inflate(this.BoundsPadding);
GraphicsPath path = this.GetRoundedRect(bounds, this.CornerRounding);
if (this.FillBrush != null) {
if (this.UseLightbox) {
using (Region newClip = new Region(r)) {
newClip.Exclude(path);
Region originalClip = g.Clip;
g.Clip = newClip;
g.FillRectangle(this.FillBrush, r);
g.Clip = originalClip;
}
} else {
g.FillPath(this.FillBrush, path);
}
}
if (this.BorderPen != null)
g.DrawPath(this.BorderPen, path);
}
#endregion
}
/// <summary>
/// This decoration causes everything *except* the row under the mouse to be overpainted
/// with a tint, making the row under the mouse stand out in comparison.
/// The darker and more opaque the fill color, the more obvious the
/// decorated row becomes.
/// </summary>
public class LightBoxDecoration : BorderDecoration
{
/// <summary>
/// Create a LightBoxDecoration
/// </summary>
public LightBoxDecoration() {
this.BoundsPadding = new Size(-1, 4);
this.CornerRounding = 8.0f;
this.FillBrush = new SolidBrush(Color.FromArgb(72, Color.Black));
}
/// <summary>
/// Draw a tint over everything in the ObjectListView except the
/// row under the mouse.
/// </summary>
/// <param name="olv"></param>
/// <param name="g"></param>
/// <param name="r"></param>
public override void Draw(ObjectListView olv, Graphics g, Rectangle r) {
if (!r.Contains(olv.PointToClient(Cursor.Position)))
return;
Rectangle bounds = this.RowBounds;
if (bounds.IsEmpty) {
if (olv.View == View.Tile)
g.FillRectangle(this.FillBrush, r);
return;
}
using (Region newClip = new Region(r)) {
bounds.Inflate(this.BoundsPadding);
newClip.Exclude(this.GetRoundedRect(bounds, this.CornerRounding));
Region originalClip = g.Clip;
g.Clip = newClip;
g.FillRectangle(this.FillBrush, r);
g.Clip = originalClip;
}
}
}
/// <summary>
/// Instances of this class put an Image over the row/cell that it is decorating
/// </summary>
public class ImageDecoration : ImageAdornment, IDecoration
{
#region Constructors
/// <summary>
/// Create an image decoration
/// </summary>
public ImageDecoration() {
this.Alignment = ContentAlignment.MiddleRight;
}
/// <summary>
/// Create an image decoration
/// </summary>
/// <param name="image"></param>
public ImageDecoration(Image image)
: this() {
this.Image = image;
}
/// <summary>
/// Create an image decoration
/// </summary>
/// <param name="image"></param>
/// <param name="transparency"></param>
public ImageDecoration(Image image, int transparency)
: this() {
this.Image = image;
this.Transparency = transparency;
}
/// <summary>
/// Create an image decoration
/// </summary>
/// <param name="image"></param>
/// <param name="alignment"></param>
public ImageDecoration(Image image, ContentAlignment alignment)
: this() {
this.Image = image;
this.Alignment = alignment;
}
/// <summary>
/// Create an image decoration
/// </summary>
/// <param name="image"></param>
/// <param name="transparency"></param>
/// <param name="alignment"></param>
public ImageDecoration(Image image, int transparency, ContentAlignment alignment)
: this() {
this.Image = image;
this.Transparency = transparency;
this.Alignment = alignment;
}
#endregion
#region IDecoration Members
/// <summary>
/// Gets or sets the item being decorated
/// </summary>
public OLVListItem ListItem {
get { return listItem; }
set { listItem = value; }
}
private OLVListItem listItem;
/// <summary>
/// Gets or sets the sub item being decorated
/// </summary>
public OLVListSubItem SubItem {
get { return subItem; }
set { subItem = value; }
}
private OLVListSubItem subItem;
#endregion
#region Commands
/// <summary>
/// Draw this decoration
/// </summary>
/// <param name="olv">The ObjectListView being decorated</param>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="r">The bounds of the rendering</param>
public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) {
this.DrawImage(g, this.CalculateItemBounds(this.ListItem, this.SubItem));
}
#endregion
}
/// <summary>
/// Instances of this class draw some text over the row/cell that they are decorating
/// </summary>
public class TextDecoration : TextAdornment, IDecoration
{
#region Constructors
/// <summary>
/// Create a TextDecoration
/// </summary>
public TextDecoration() {
this.Alignment = ContentAlignment.MiddleRight;
}
/// <summary>
/// Create a TextDecoration
/// </summary>
/// <param name="text"></param>
public TextDecoration(string text)
: this() {
this.Text = text;
}
/// <summary>
/// Create a TextDecoration
/// </summary>
/// <param name="text"></param>
/// <param name="transparency"></param>
public TextDecoration(string text, int transparency)
: this() {
this.Text = text;
this.Transparency = transparency;
}
/// <summary>
/// Create a TextDecoration
/// </summary>
/// <param name="text"></param>
/// <param name="alignment"></param>
public TextDecoration(string text, ContentAlignment alignment)
: this() {
this.Text = text;
this.Alignment = alignment;
}
/// <summary>
/// Create a TextDecoration
/// </summary>
/// <param name="text"></param>
/// <param name="transparency"></param>
/// <param name="alignment"></param>
public TextDecoration(string text, int transparency, ContentAlignment alignment)
: this() {
this.Text = text;
this.Transparency = transparency;
this.Alignment = alignment;
}
#endregion
#region IDecoration Members
/// <summary>
/// Gets or sets the item being decorated
/// </summary>
public OLVListItem ListItem {
get { return listItem; }
set { listItem = value; }
}
private OLVListItem listItem;
/// <summary>
/// Gets or sets the sub item being decorated
/// </summary>
public OLVListSubItem SubItem {
get { return subItem; }
set { subItem = value; }
}
private OLVListSubItem subItem;
#endregion
#region Commands
/// <summary>
/// Draw this decoration
/// </summary>
/// <param name="olv">The ObjectListView being decorated</param>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="r">The bounds of the rendering</param>
public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) {
this.DrawText(g, this.CalculateItemBounds(this.ListItem, this.SubItem));
}
#endregion
}
}

View File

@@ -0,0 +1,302 @@
/*
* Overlays - Images, text or other things that can be rendered over the top of a ListView
*
* Author: Phillip Piper
* Date: 14/04/2009 4:36 PM
*
* Change log:
* v2.3
* 2009-08-17 JPP - Overlays now use Adornments
* - Added ITransparentOverlay interface. Overlays can now have separate transparency levels
* 2009-08-10 JPP - Moved decoration related code to new file
* v2.2.1
* 200-07-24 JPP - TintedColumnDecoration now works when last item is a member of a collapsed
* group (well, it no longer crashes).
* v2.2
* 2009-06-01 JPP - Make sure that TintedColumnDecoration reaches to the last item in group view
* 2009-05-05 JPP - Unified BillboardOverlay text rendering with that of TextOverlay
* 2009-04-30 JPP - Added TintedColumnDecoration
* 2009-04-14 JPP - Initial version
*
* To do:
*
* Copyright (C) 2009-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
namespace BrightIdeasSoftware
{
/// <summary>
/// The interface for an object which can draw itself over the top of
/// an ObjectListView.
/// </summary>
public interface IOverlay
{
/// <summary>
/// Draw this overlay
/// </summary>
/// <param name="olv">The ObjectListView that is being overlaid</param>
/// <param name="g">The Graphics onto the given OLV</param>
/// <param name="r">The content area of the OLV</param>
void Draw(ObjectListView olv, Graphics g, Rectangle r);
}
/// <summary>
/// An interface for an overlay that supports variable levels of transparency
/// </summary>
public interface ITransparentOverlay : IOverlay
{
/// <summary>
/// Gets or sets the transparency of the overlay.
/// 0 is completely transparent, 255 is completely opaque.
/// </summary>
int Transparency { get; set; }
}
/// <summary>
/// A null implementation of the IOverlay interface
/// </summary>
public class AbstractOverlay : ITransparentOverlay
{
#region IOverlay Members
/// <summary>
/// Draw this overlay
/// </summary>
/// <param name="olv">The ObjectListView that is being overlaid</param>
/// <param name="g">The Graphics onto the given OLV</param>
/// <param name="r">The content area of the OLV</param>
public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) {
}
#endregion
#region ITransparentOverlay Members
/// <summary>
/// How transparent should this overlay be?
/// </summary>
[Category("ObjectListView"),
Description("How transparent should this overlay be"),
DefaultValue(128),
NotifyParentProperty(true)]
public int Transparency {
get { return this.transparency; }
set { this.transparency = Math.Min(255, Math.Max(0, value)); }
}
private int transparency = 128;
#endregion
}
/// <summary>
/// An overlay that will draw an image over the top of the ObjectListView
/// </summary>
[TypeConverter("BrightIdeasSoftware.Design.OverlayConverter")]
public class ImageOverlay : ImageAdornment, ITransparentOverlay
{
/// <summary>
/// Create an ImageOverlay
/// </summary>
public ImageOverlay() {
this.Alignment = System.Drawing.ContentAlignment.BottomRight;
}
#region Public properties
/// <summary>
/// Gets or sets the horizontal inset by which the position of the overlay will be adjusted
/// </summary>
[Category("ObjectListView"),
Description("The horizontal inset by which the position of the overlay will be adjusted"),
DefaultValue(20),
NotifyParentProperty(true)]
public int InsetX {
get { return this.insetX; }
set { this.insetX = Math.Max(0, value); }
}
private int insetX = 20;
/// <summary>
/// Gets or sets the vertical inset by which the position of the overlay will be adjusted
/// </summary>
[Category("ObjectListView"),
Description("Gets or sets the vertical inset by which the position of the overlay will be adjusted"),
DefaultValue(20),
NotifyParentProperty(true)]
public int InsetY {
get { return this.insetY; }
set { this.insetY = Math.Max(0, value); }
}
private int insetY = 20;
#endregion
#region Commands
/// <summary>
/// Draw this overlay
/// </summary>
/// <param name="olv">The ObjectListView being decorated</param>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="r">The bounds of the rendering</param>
public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) {
Rectangle insetRect = r;
insetRect.Inflate(-this.InsetX, -this.InsetY);
// We hard code a transparency of 255 here since transparency is handled by the glass panel
this.DrawImage(g, insetRect, this.Image, 255);
}
#endregion
}
/// <summary>
/// An overlay that will draw text over the top of the ObjectListView
/// </summary>
[TypeConverter("BrightIdeasSoftware.Design.OverlayConverter")]
public class TextOverlay : TextAdornment, ITransparentOverlay
{
/// <summary>
/// Create a TextOverlay
/// </summary>
public TextOverlay() {
this.Alignment = System.Drawing.ContentAlignment.BottomRight;
}
#region Public properties
/// <summary>
/// Gets or sets the horizontal inset by which the position of the overlay will be adjusted
/// </summary>
[Category("ObjectListView"),
Description("The horizontal inset by which the position of the overlay will be adjusted"),
DefaultValue(20),
NotifyParentProperty(true)]
public int InsetX {
get { return this.insetX; }
set { this.insetX = Math.Max(0, value); }
}
private int insetX = 20;
/// <summary>
/// Gets or sets the vertical inset by which the position of the overlay will be adjusted
/// </summary>
[Category("ObjectListView"),
Description("Gets or sets the vertical inset by which the position of the overlay will be adjusted"),
DefaultValue(20),
NotifyParentProperty(true)]
public int InsetY {
get { return this.insetY; }
set { this.insetY = Math.Max(0, value); }
}
private int insetY = 20;
/// <summary>
/// Gets or sets whether the border will be drawn with rounded corners
/// </summary>
[Browsable(false),
Obsolete("Use CornerRounding instead", false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool RoundCorneredBorder {
get { return this.CornerRounding > 0; }
set {
if (value)
this.CornerRounding = 16.0f;
else
this.CornerRounding = 0.0f;
}
}
#endregion
#region Commands
/// <summary>
/// Draw this overlay
/// </summary>
/// <param name="olv">The ObjectListView being decorated</param>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="r">The bounds of the rendering</param>
public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) {
if (String.IsNullOrEmpty(this.Text))
return;
Rectangle insetRect = r;
insetRect.Inflate(-this.InsetX, -this.InsetY);
// We hard code a transparency of 255 here since transparency is handled by the glass panel
this.DrawText(g, insetRect, this.Text, 255);
}
#endregion
}
/// <summary>
/// A Billboard overlay is a TextOverlay positioned at an absolute point
/// </summary>
public class BillboardOverlay : TextOverlay
{
/// <summary>
/// Create a BillboardOverlay
/// </summary>
public BillboardOverlay() {
this.Transparency = 255;
this.BackColor = Color.PeachPuff;
this.TextColor = Color.Black;
this.BorderColor = Color.Empty;
this.Font = new Font("Tahoma", 10);
}
/// <summary>
/// Gets or sets where should the top left of the billboard be placed
/// </summary>
public Point Location {
get { return this.location; }
set { this.location = value; }
}
private Point location;
/// <summary>
/// Draw this overlay
/// </summary>
/// <param name="olv">The ObjectListView being decorated</param>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="r">The bounds of the rendering</param>
public override void Draw(ObjectListView olv, Graphics g, Rectangle r) {
if (String.IsNullOrEmpty(this.Text))
return;
// Calculate the bounds of the text, and then move it to where it should be
Rectangle textRect = this.CalculateTextBounds(g, r, this.Text);
textRect.Location = this.Location;
// Make sure the billboard is within the bounds of the List, as far as is possible
if (textRect.Right > r.Width)
textRect.X = Math.Max(r.Left, r.Width - textRect.Width);
if (textRect.Bottom > r.Height)
textRect.Y = Math.Max(r.Top, r.Height - textRect.Height);
this.DrawBorderedText(g, textRect, this.Text, 255);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,400 @@
/*
* Styles - A style is a group of formatting attributes that can be applied to a row or a cell
*
* Author: Phillip Piper
* Date: 29/07/2009 23:09
*
* Change log:
* v2.4
* 2010-03-23 JPP - Added HeaderFormatStyle and support
* v2.3
* 2009-08-15 JPP - Added Decoration and Overlay properties to HotItemStyle
* 2009-07-29 JPP - Initial version
*
* To do:
* - These should be more generally available. It should be possible to do something like this:
* this.olv.GetItem(i).Style = new ItemStyle();
* this.olv.GetItem(i).GetSubItem(j).Style = new CellStyle();
*
* Copyright (C) 2009-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace BrightIdeasSoftware
{
/// <summary>
/// The common interface supported by all style objects
/// </summary>
public interface IItemStyle
{
/// <summary>
/// Gets or set the font that will be used by this style
/// </summary>
Font Font { get; set; }
/// <summary>
/// Gets or set the font style
/// </summary>
FontStyle FontStyle { get; set; }
/// <summary>
/// Gets or sets the ForeColor
/// </summary>
Color ForeColor { get; set; }
/// <summary>
/// Gets or sets the BackColor
/// </summary>
Color BackColor { get; set; }
}
/// <summary>
/// Basic implementation of IItemStyle
/// </summary>
public class SimpleItemStyle : System.ComponentModel.Component, IItemStyle
{
/// <summary>
/// Gets or sets the font that will be applied by this style
/// </summary>
[DefaultValue(null)]
public Font Font
{
get { return this.font; }
set { this.font = value; }
}
private Font font;
/// <summary>
/// Gets or sets the style of font that will be applied by this style
/// </summary>
[DefaultValue(FontStyle.Regular)]
public FontStyle FontStyle
{
get { return this.fontStyle; }
set { this.fontStyle = value; }
}
private FontStyle fontStyle;
/// <summary>
/// Gets or sets the color of the text that will be applied by this style
/// </summary>
[DefaultValue(typeof (Color), "")]
public Color ForeColor
{
get { return this.foreColor; }
set { this.foreColor = value; }
}
private Color foreColor;
/// <summary>
/// Gets or sets the background color that will be applied by this style
/// </summary>
[DefaultValue(typeof (Color), "")]
public Color BackColor
{
get { return this.backColor; }
set { this.backColor = value; }
}
private Color backColor;
}
/// <summary>
/// Instances of this class specify how should "hot items" (non-selected
/// rows under the cursor) be renderered.
/// </summary>
public class HotItemStyle : SimpleItemStyle
{
/// <summary>
/// Gets or sets the overlay that should be drawn as part of the hot item
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IOverlay Overlay {
get { return this.overlay; }
set { this.overlay = value; }
}
private IOverlay overlay;
/// <summary>
/// Gets or sets the decoration that should be drawn as part of the hot item
/// </summary>
/// <remarks>A decoration is different from an overlay in that an decoration
/// scrolls with the listview contents, whilst an overlay does not.</remarks>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IDecoration Decoration {
get { return this.decoration; }
set { this.decoration = value; }
}
private IDecoration decoration;
}
/// <summary>
/// This class defines how a cell should be formatted
/// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))]
public class CellStyle : IItemStyle
{
/// <summary>
/// Gets or sets the font that will be applied by this style
/// </summary>
public Font Font {
get { return this.font; }
set { this.font = value; }
}
private Font font;
/// <summary>
/// Gets or sets the style of font that will be applied by this style
/// </summary>
[DefaultValue(FontStyle.Regular)]
public FontStyle FontStyle {
get { return this.fontStyle; }
set { this.fontStyle = value; }
}
private FontStyle fontStyle;
/// <summary>
/// Gets or sets the color of the text that will be applied by this style
/// </summary>
[DefaultValue(typeof(Color), "")]
public Color ForeColor {
get { return this.foreColor; }
set { this.foreColor = value; }
}
private Color foreColor;
/// <summary>
/// Gets or sets the background color that will be applied by this style
/// </summary>
[DefaultValue(typeof(Color), "")]
public Color BackColor {
get { return this.backColor; }
set { this.backColor = value; }
}
private Color backColor;
}
/// <summary>
/// Instances of this class describe how hyperlinks will appear
/// </summary>
public class HyperlinkStyle : System.ComponentModel.Component
{
/// <summary>
/// Create a HyperlinkStyle
/// </summary>
public HyperlinkStyle() {
this.Normal = new CellStyle();
this.Normal.ForeColor = Color.Blue;
this.Over = new CellStyle();
this.Over.FontStyle = FontStyle.Underline;
this.Visited = new CellStyle();
this.Visited.ForeColor = Color.Purple;
this.OverCursor = Cursors.Hand;
}
/// <summary>
/// What sort of formatting should be applied to hyperlinks in their normal state?
/// </summary>
[Category("Appearance"),
Description("How should hyperlinks be drawn")]
public CellStyle Normal {
get { return this.normalStyle; }
set { this.normalStyle = value; }
}
private CellStyle normalStyle;
/// <summary>
/// What sort of formatting should be applied to hyperlinks when the mouse is over them?
/// </summary>
[Category("Appearance"),
Description("How should hyperlinks be drawn when the mouse is over them?")]
public CellStyle Over {
get { return this.overStyle; }
set { this.overStyle = value; }
}
private CellStyle overStyle;
/// <summary>
/// What sort of formatting should be applied to hyperlinks after they have been clicked?
/// </summary>
[Category("Appearance"),
Description("How should hyperlinks be drawn after they have been clicked")]
public CellStyle Visited {
get { return this.visitedStyle; }
set { this.visitedStyle = value; }
}
private CellStyle visitedStyle;
/// <summary>
/// Gets or sets the cursor that should be shown when the mouse is over a hyperlink.
/// </summary>
[Category("Appearance"),
Description("What cursor should be shown when the mouse is over a link?")]
public Cursor OverCursor {
get { return this.overCursor; }
set { this.overCursor = value; }
}
private Cursor overCursor;
}
/// <summary>
/// Instances of this class control one the styling of one particular state
/// (normal, hot, pressed) of a header control
/// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))]
public class HeaderStateStyle
{
/// <summary>
/// Gets or sets the font that will be applied by this style
/// </summary>
[DefaultValue(null)]
public Font Font {
get { return this.font; }
set { this.font = value; }
}
private Font font;
/// <summary>
/// Gets or sets the color of the text that will be applied by this style
/// </summary>
[DefaultValue(typeof(Color), "")]
public Color ForeColor {
get { return this.foreColor; }
set { this.foreColor = value; }
}
private Color foreColor;
/// <summary>
/// Gets or sets the background color that will be applied by this style
/// </summary>
[DefaultValue(typeof(Color), "")]
public Color BackColor {
get { return this.backColor; }
set { this.backColor = value; }
}
private Color backColor;
/// <summary>
/// Gets or sets the color in which a frame will be drawn around the header for this column
/// </summary>
[DefaultValue(typeof(Color), "")]
public Color FrameColor {
get { return this.frameColor; }
set { this.frameColor = value; }
}
private Color frameColor;
/// <summary>
/// Gets or sets the width of the frame that will be drawn around the header for this column
/// </summary>
[DefaultValue(0.0f)]
public float FrameWidth {
get { return this.frameWidth; }
set { this.frameWidth = value; }
}
private float frameWidth;
}
/// <summary>
/// This class defines how a header should be formatted in its various states.
/// </summary>
public class HeaderFormatStyle : System.ComponentModel.Component
{
/// <summary>
/// Create a new HeaderFormatStyle
/// </summary>
public HeaderFormatStyle() {
this.Hot = new HeaderStateStyle();
this.Normal = new HeaderStateStyle();
this.Pressed = new HeaderStateStyle();
}
/// <summary>
/// What sort of formatting should be applied to a column header when the mouse is over it?
/// </summary>
[Category("Appearance"),
Description("How should the header be drawn when the mouse is over it?")]
public HeaderStateStyle Hot {
get { return this.hotStyle; }
set { this.hotStyle = value; }
}
private HeaderStateStyle hotStyle;
/// <summary>
/// What sort of formatting should be applied to a column header in its normal state?
/// </summary>
[Category("Appearance"),
Description("How should a column header normally be drawn")]
public HeaderStateStyle Normal {
get { return this.normalStyle; }
set { this.normalStyle = value; }
}
private HeaderStateStyle normalStyle;
/// <summary>
/// What sort of formatting should be applied to a column header when pressed?
/// </summary>
[Category("Appearance"),
Description("How should a column header be drawn when it is pressed")]
public HeaderStateStyle Pressed {
get { return this.pressedStyle; }
set { this.pressedStyle = value; }
}
private HeaderStateStyle pressedStyle;
/// <summary>
/// Set the font for all three states
/// </summary>
/// <param name="font"></param>
public void SetFont(Font font) {
this.Normal.Font = font;
this.Hot.Font = font;
this.Pressed.Font = font;
}
/// <summary>
/// Set the fore color for all three states
/// </summary>
/// <param name="color"></param>
public void SetForeColor(Color color) {
this.Normal.ForeColor = color;
this.Hot.ForeColor = color;
this.Pressed.ForeColor = color;
}
/// <summary>
/// Set the back color for all three states
/// </summary>
/// <param name="color"></param>
public void SetBackColor(Color color) {
this.Normal.BackColor = color;
this.Hot.BackColor = color;
this.Pressed.BackColor = color;
}
}
}

View File

@@ -0,0 +1,309 @@
/*
* TreeRenderer - Draw the major column in a TreeListView
*
* Author: Phillip Piper
* Date: 27/06/2015
*
* Change log:
* 2016-07-17 JPP - Added TreeRenderer.UseTriangles and IsShowGlyphs
* 2015-06-27 JPP - Split out from TreeListView.cs
*
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
using System.Drawing.Drawing2D;
namespace BrightIdeasSoftware {
public partial class TreeListView {
/// <summary>
/// This class handles drawing the tree structure of the primary column.
/// </summary>
public class TreeRenderer : HighlightTextRenderer {
/// <summary>
/// Create a TreeRenderer
/// </summary>
public TreeRenderer() {
this.LinePen = new Pen(Color.Blue, 1.0f);
this.LinePen.DashStyle = DashStyle.Dot;
}
#region Configuration properties
/// <summary>
/// Should the renderer draw glyphs at the expansion points?
/// </summary>
/// <remarks>The expansion points will still function to expand/collapse even if this is false.</remarks>
public bool IsShowGlyphs
{
get { return isShowGlyphs; }
set { isShowGlyphs = value; }
}
private bool isShowGlyphs = true;
/// <summary>
/// Should the renderer draw lines connecting siblings?
/// </summary>
public bool IsShowLines
{
get { return isShowLines; }
set { isShowLines = value; }
}
private bool isShowLines = true;
/// <summary>
/// Return the pen that will be used to draw the lines between branches
/// </summary>
public Pen LinePen
{
get { return linePen; }
set { linePen = value; }
}
private Pen linePen;
/// <summary>
/// Should the renderer draw triangles as the expansion glyphs?
/// </summary>
/// <remarks>
/// This looks best with ShowLines = false
/// </remarks>
public bool UseTriangles
{
get { return useTriangles; }
set { useTriangles = value; }
}
private bool useTriangles = false;
#endregion
/// <summary>
/// Return the branch that the renderer is currently drawing.
/// </summary>
private Branch Branch {
get {
return this.TreeListView.TreeModel.GetBranch(this.RowObject);
}
}
/// <summary>
/// Return the TreeListView for which the renderer is being used.
/// </summary>
public TreeListView TreeListView {
get {
return (TreeListView)this.ListView;
}
}
/// <summary>
/// How many pixels will be reserved for each level of indentation?
/// </summary>
public static int PIXELS_PER_LEVEL = 16 + 1;
/// <summary>
/// The real work of drawing the tree is done in this method
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
public override void Render(System.Drawing.Graphics g, System.Drawing.Rectangle r) {
this.DrawBackground(g, r);
Branch br = this.Branch;
Rectangle paddedRectangle = this.ApplyCellPadding(r);
Rectangle expandGlyphRectangle = paddedRectangle;
expandGlyphRectangle.Offset((br.Level - 1) * PIXELS_PER_LEVEL, 0);
expandGlyphRectangle.Width = PIXELS_PER_LEVEL;
expandGlyphRectangle.Height = PIXELS_PER_LEVEL;
expandGlyphRectangle.Y = this.AlignVertically(paddedRectangle, expandGlyphRectangle);
int expandGlyphRectangleMidVertical = expandGlyphRectangle.Y + (expandGlyphRectangle.Height/2);
if (this.IsShowLines)
this.DrawLines(g, r, this.LinePen, br, expandGlyphRectangleMidVertical);
if (br.CanExpand && this.IsShowGlyphs)
this.DrawExpansionGlyph(g, expandGlyphRectangle, br.IsExpanded);
int indent = br.Level * PIXELS_PER_LEVEL;
paddedRectangle.Offset(indent, 0);
paddedRectangle.Width -= indent;
this.DrawImageAndText(g, paddedRectangle);
}
/// <summary>
/// Draw the expansion indicator
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
/// <param name="isExpanded"></param>
protected virtual void DrawExpansionGlyph(Graphics g, Rectangle r, bool isExpanded) {
if (this.UseStyles) {
this.DrawExpansionGlyphStyled(g, r, isExpanded);
} else {
this.DrawExpansionGlyphManual(g, r, isExpanded);
}
}
/// <summary>
/// Gets whether or not we should render using styles
/// </summary>
protected virtual bool UseStyles {
get {
return !this.IsPrinting && Application.RenderWithVisualStyles;
}
}
/// <summary>
/// Draw the expansion indicator using styles
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
/// <param name="isExpanded"></param>
protected virtual void DrawExpansionGlyphStyled(Graphics g, Rectangle r, bool isExpanded) {
if (this.UseTriangles && this.IsShowLines) {
using (SolidBrush b = new SolidBrush(GetBackgroundColor())) {
Rectangle r2 = r;
r2.Inflate(-2, -2);
g.FillRectangle(b, r2);
}
}
VisualStyleRenderer renderer = new VisualStyleRenderer(DecideVisualElement(isExpanded));
renderer.DrawBackground(g, r);
}
private VisualStyleElement DecideVisualElement(bool isExpanded) {
string klass = this.UseTriangles ? "Explorer::TreeView" : "TREEVIEW";
int part = this.UseTriangles && this.IsExpansionHot ? 4 : 2;
int state = isExpanded ? 2 : 1;
return VisualStyleElement.CreateElement(klass, part, state);
}
/// <summary>
/// Is the mouse over a checkbox in this cell?
/// </summary>
protected bool IsExpansionHot {
get { return this.IsCellHot && this.ListView.HotCellHitLocation == HitTestLocation.ExpandButton; }
}
/// <summary>
/// Draw the expansion indicator without using styles
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
/// <param name="isExpanded"></param>
protected virtual void DrawExpansionGlyphManual(Graphics g, Rectangle r, bool isExpanded) {
int h = 8;
int w = 8;
int x = r.X + 4;
int y = r.Y + (r.Height / 2) - 4;
g.DrawRectangle(new Pen(SystemBrushes.ControlDark), x, y, w, h);
g.FillRectangle(Brushes.White, x + 1, y + 1, w - 1, h - 1);
g.DrawLine(Pens.Black, x + 2, y + 4, x + w - 2, y + 4);
if (!isExpanded)
g.DrawLine(Pens.Black, x + 4, y + 2, x + 4, y + h - 2);
}
/// <summary>
/// Draw the lines of the tree
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
/// <param name="p"></param>
/// <param name="br"></param>
/// <param name="glyphMidVertical"> </param>
protected virtual void DrawLines(Graphics g, Rectangle r, Pen p, Branch br, int glyphMidVertical) {
Rectangle r2 = r;
r2.Width = PIXELS_PER_LEVEL;
// Vertical lines have to start on even points, otherwise the dotted line looks wrong.
// This is only needed if pen is dotted.
int top = r2.Top;
//if (p.DashStyle == DashStyle.Dot && (top & 1) == 0)
// top += 1;
// Draw lines for ancestors
int midX;
IList<Branch> ancestors = br.Ancestors;
foreach (Branch ancestor in ancestors) {
if (!ancestor.IsLastChild && !ancestor.IsOnlyBranch) {
midX = r2.Left + r2.Width / 2;
g.DrawLine(p, midX, top, midX, r2.Bottom);
}
r2.Offset(PIXELS_PER_LEVEL, 0);
}
// Draw lines for this branch
midX = r2.Left + r2.Width / 2;
// Horizontal line first
g.DrawLine(p, midX, glyphMidVertical, r2.Right, glyphMidVertical);
// Vertical line second
if (br.IsFirstBranch) {
if (!br.IsLastChild && !br.IsOnlyBranch)
g.DrawLine(p, midX, glyphMidVertical, midX, r2.Bottom);
} else {
if (br.IsLastChild)
g.DrawLine(p, midX, top, midX, glyphMidVertical);
else
g.DrawLine(p, midX, top, midX, r2.Bottom);
}
}
/// <summary>
/// Do the hit test
/// </summary>
/// <param name="g"></param>
/// <param name="hti"></param>
/// <param name="x"></param>
/// <param name="y"></param>
protected override void HandleHitTest(Graphics g, OlvListViewHitTestInfo hti, int x, int y) {
Branch br = this.Branch;
Rectangle r = this.ApplyCellPadding(this.Bounds);
if (br.CanExpand) {
r.Offset((br.Level - 1) * PIXELS_PER_LEVEL, 0);
r.Width = PIXELS_PER_LEVEL;
if (r.Contains(x, y)) {
hti.HitTestLocation = HitTestLocation.ExpandButton;
return;
}
}
r = this.Bounds;
int indent = br.Level * PIXELS_PER_LEVEL;
r.X += indent;
r.Width -= indent;
// Ignore events in the indent zone
if (x < r.Left) {
hti.HitTestLocation = HitTestLocation.Nothing;
} else {
this.StandardHitTest(g, hti, r, x, y);
}
}
/// <summary>
/// Calculate the edit rect
/// </summary>
/// <param name="g"></param>
/// <param name="cellBounds"></param>
/// <param name="item"></param>
/// <param name="subItemIndex"></param>
/// <param name="preferredSize"> </param>
/// <returns></returns>
protected override Rectangle HandleGetEditRectangle(Graphics g, Rectangle cellBounds, OLVListItem item, int subItemIndex, Size preferredSize) {
return this.StandardGetEditRectangle(g, cellBounds, preferredSize);
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,459 @@
/*
* GlassPanelForm - A transparent form that is placed over an ObjectListView
* to allow flicker-free overlay images during scrolling.
*
* Author: Phillip Piper
* Date: 14/04/2009 4:36 PM
*
* Change log:
* 2010-08-18 JPP - Added WS_EX_TOOLWINDOW style so that the form won't appear in Alt-Tab list.
* v2.4
* 2010-03-11 JPP - Work correctly in MDI applications -- more or less. Actually, less than more.
* They don't crash but they don't correctly handle overlapping MDI children.
* Overlays from one control are shown on top of other other windows.
* 2010-03-09 JPP - Correctly Unbind() when the related ObjectListView is disposed.
* 2009-10-28 JPP - Use FindForm() rather than TopMostControl, since the latter doesn't work
* as I expected when the OLV is part of an MDI child window. Thanks to
* wvd_vegt who tracked this down.
* v2.3
* 2009-08-19 JPP - Only hide the glass pane on resize, not on move
* - Each glass panel now only draws one overlays
* v2.2
* 2009-06-05 JPP - Handle when owning window is a topmost window
* 2009-04-14 JPP - Initial version
*
* To do:
*
* Copyright (C) 2009-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
namespace BrightIdeasSoftware
{
/// <summary>
/// A GlassPanelForm sits transparently over an ObjectListView to show overlays.
/// </summary>
internal partial class GlassPanelForm : Form
{
public GlassPanelForm() {
this.Name = "GlassPanelForm";
this.Text = "GlassPanelForm";
ClientSize = new System.Drawing.Size(0, 0);
ControlBox = false;
FormBorderStyle = FormBorderStyle.None;
SizeGripStyle = SizeGripStyle.Hide;
StartPosition = FormStartPosition.Manual;
MaximizeBox = false;
MinimizeBox = false;
ShowIcon = false;
ShowInTaskbar = false;
FormBorderStyle = FormBorderStyle.None;
SetStyle(ControlStyles.Selectable, false);
this.Opacity = 0.5f;
this.BackColor = Color.FromArgb(255, 254, 254, 254);
this.TransparencyKey = this.BackColor;
this.HideGlass();
NativeMethods.ShowWithoutActivate(this);
}
protected override void Dispose(bool disposing) {
if (disposing)
this.Unbind();
base.Dispose(disposing);
}
#region Properties
/// <summary>
/// Get the low-level windows flag that will be given to CreateWindow.
/// </summary>
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x20; // WS_EX_TRANSPARENT
cp.ExStyle |= 0x80; // WS_EX_TOOLWINDOW
return cp;
}
}
#endregion
#region Commands
/// <summary>
/// Attach this form to the given ObjectListView
/// </summary>
public void Bind(ObjectListView olv, IOverlay overlay) {
if (this.objectListView != null)
this.Unbind();
this.objectListView = olv;
this.Overlay = overlay;
this.mdiClient = null;
this.mdiOwner = null;
if (this.objectListView == null)
return;
// NOTE: If you listen to any events here, you *must* stop listening in Unbind()
this.objectListView.Disposed += new EventHandler(objectListView_Disposed);
this.objectListView.LocationChanged += new EventHandler(objectListView_LocationChanged);
this.objectListView.SizeChanged += new EventHandler(objectListView_SizeChanged);
this.objectListView.VisibleChanged += new EventHandler(objectListView_VisibleChanged);
this.objectListView.ParentChanged += new EventHandler(objectListView_ParentChanged);
// Collect our ancestors in the widget hierachy
if (this.ancestors == null)
this.ancestors = new List<Control>();
Control parent = this.objectListView.Parent;
while (parent != null) {
this.ancestors.Add(parent);
parent = parent.Parent;
}
// Listen for changes in the hierachy
foreach (Control ancestor in this.ancestors) {
ancestor.ParentChanged += new EventHandler(objectListView_ParentChanged);
TabControl tabControl = ancestor as TabControl;
if (tabControl != null) {
tabControl.Selected += new TabControlEventHandler(tabControl_Selected);
}
}
// Listen for changes in our owning form
this.Owner = this.objectListView.FindForm();
this.myOwner = this.Owner;
if (this.Owner != null) {
this.Owner.LocationChanged += new EventHandler(Owner_LocationChanged);
this.Owner.SizeChanged += new EventHandler(Owner_SizeChanged);
this.Owner.ResizeBegin += new EventHandler(Owner_ResizeBegin);
this.Owner.ResizeEnd += new EventHandler(Owner_ResizeEnd);
if (this.Owner.TopMost) {
// We can't do this.TopMost = true; since that will activate the panel,
// taking focus away from the owner of the listview
NativeMethods.MakeTopMost(this);
}
// We need special code to handle MDI
this.mdiOwner = this.Owner.MdiParent;
if (this.mdiOwner != null) {
this.mdiOwner.LocationChanged += new EventHandler(Owner_LocationChanged);
this.mdiOwner.SizeChanged += new EventHandler(Owner_SizeChanged);
this.mdiOwner.ResizeBegin += new EventHandler(Owner_ResizeBegin);
this.mdiOwner.ResizeEnd += new EventHandler(Owner_ResizeEnd);
// Find the MDIClient control, which houses all MDI children
foreach (Control c in this.mdiOwner.Controls) {
this.mdiClient = c as MdiClient;
if (this.mdiClient != null) {
break;
}
}
if (this.mdiClient != null) {
this.mdiClient.ClientSizeChanged += new EventHandler(myMdiClient_ClientSizeChanged);
}
}
}
this.UpdateTransparency();
}
void myMdiClient_ClientSizeChanged(object sender, EventArgs e) {
this.RecalculateBounds();
this.Invalidate();
}
/// <summary>
/// Made the overlay panel invisible
/// </summary>
public void HideGlass() {
if (!this.isGlassShown)
return;
this.isGlassShown = false;
this.Bounds = new Rectangle(-10000, -10000, 1, 1);
}
/// <summary>
/// Show the overlay panel in its correctly location
/// </summary>
/// <remarks>
/// If the panel is always shown, this method does nothing.
/// If the panel is being resized, this method also does nothing.
/// </remarks>
public void ShowGlass() {
if (this.isGlassShown || this.isDuringResizeSequence)
return;
this.isGlassShown = true;
this.RecalculateBounds();
}
/// <summary>
/// Detach this glass panel from its previous ObjectListView
/// </summary>
/// <remarks>
/// You should unbind the overlay panel before making any changes to the
/// widget hierarchy.
/// </remarks>
public void Unbind() {
if (this.objectListView != null) {
this.objectListView.Disposed -= new EventHandler(objectListView_Disposed);
this.objectListView.LocationChanged -= new EventHandler(objectListView_LocationChanged);
this.objectListView.SizeChanged -= new EventHandler(objectListView_SizeChanged);
this.objectListView.VisibleChanged -= new EventHandler(objectListView_VisibleChanged);
this.objectListView.ParentChanged -= new EventHandler(objectListView_ParentChanged);
this.objectListView = null;
}
if (this.ancestors != null) {
foreach (Control parent in this.ancestors) {
parent.ParentChanged -= new EventHandler(objectListView_ParentChanged);
TabControl tabControl = parent as TabControl;
if (tabControl != null) {
tabControl.Selected -= new TabControlEventHandler(tabControl_Selected);
}
}
this.ancestors = null;
}
if (this.myOwner != null) {
this.myOwner.LocationChanged -= new EventHandler(Owner_LocationChanged);
this.myOwner.SizeChanged -= new EventHandler(Owner_SizeChanged);
this.myOwner.ResizeBegin -= new EventHandler(Owner_ResizeBegin);
this.myOwner.ResizeEnd -= new EventHandler(Owner_ResizeEnd);
this.myOwner = null;
}
if (this.mdiOwner != null) {
this.mdiOwner.LocationChanged -= new EventHandler(Owner_LocationChanged);
this.mdiOwner.SizeChanged -= new EventHandler(Owner_SizeChanged);
this.mdiOwner.ResizeBegin -= new EventHandler(Owner_ResizeBegin);
this.mdiOwner.ResizeEnd -= new EventHandler(Owner_ResizeEnd);
this.mdiOwner = null;
}
if (this.mdiClient != null) {
this.mdiClient.ClientSizeChanged -= new EventHandler(myMdiClient_ClientSizeChanged);
this.mdiClient = null;
}
}
#endregion
#region Event Handlers
void objectListView_Disposed(object sender, EventArgs e) {
this.Unbind();
}
/// <summary>
/// Handle when the form that owns the ObjectListView begins to be resized
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void Owner_ResizeBegin(object sender, EventArgs e) {
// When the top level window is being resized, we just want to hide
// the overlay window. When the resizing finishes, we want to show
// the overlay window, if it was shown before the resize started.
this.isDuringResizeSequence = true;
this.wasGlassShownBeforeResize = this.isGlassShown;
}
/// <summary>
/// Handle when the form that owns the ObjectListView finished to be resized
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void Owner_ResizeEnd(object sender, EventArgs e) {
this.isDuringResizeSequence = false;
if (this.wasGlassShownBeforeResize)
this.ShowGlass();
}
/// <summary>
/// The owning form has moved. Move the overlay panel too.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void Owner_LocationChanged(object sender, EventArgs e) {
if (this.mdiOwner != null)
this.HideGlass();
else
this.RecalculateBounds();
}
/// <summary>
/// The owning form is resizing. Hide our overlay panel until the resizing stops
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void Owner_SizeChanged(object sender, EventArgs e) {
this.HideGlass();
}
/// <summary>
/// Handle when the bound OLV changes its location. The overlay panel must
/// be moved too, IFF it is currently visible.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void objectListView_LocationChanged(object sender, EventArgs e) {
if (this.isGlassShown) {
this.RecalculateBounds();
}
}
/// <summary>
/// Handle when the bound OLV changes size. The overlay panel must
/// resize too, IFF it is currently visible.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void objectListView_SizeChanged(object sender, EventArgs e) {
// This event is triggered in all sorts of places, and not always when the size changes.
//if (this.isGlassShown) {
// this.Size = this.objectListView.ClientSize;
//}
}
/// <summary>
/// Handle when the bound OLV is part of a TabControl and that
/// TabControl changes tabs. The overlay panel is hidden. The
/// first time the bound OLV is redrawn, the overlay panel will
/// be shown again.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void tabControl_Selected(object sender, TabControlEventArgs e) {
this.HideGlass();
}
/// <summary>
/// Somewhere the parent of the bound OLV has changed. Update
/// our events.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void objectListView_ParentChanged(object sender, EventArgs e) {
ObjectListView olv = this.objectListView;
IOverlay overlay = this.Overlay;
this.Unbind();
this.Bind(olv, overlay);
}
/// <summary>
/// Handle when the bound OLV changes its visibility.
/// The overlay panel should match the OLV's visibility.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void objectListView_VisibleChanged(object sender, EventArgs e) {
if (this.objectListView.Visible)
this.ShowGlass();
else
this.HideGlass();
}
#endregion
#region Implementation
protected override void OnPaint(PaintEventArgs e) {
if (this.objectListView == null || this.Overlay == null)
return;
Graphics g = e.Graphics;
g.TextRenderingHint = ObjectListView.TextRenderingHint;
g.SmoothingMode = ObjectListView.SmoothingMode;
//g.DrawRectangle(new Pen(Color.Green, 4.0f), this.ClientRectangle);
// If we are part of an MDI app, make sure we don't draw outside the bounds
if (this.mdiClient != null) {
Rectangle r = mdiClient.RectangleToScreen(mdiClient.ClientRectangle);
Rectangle r2 = this.objectListView.RectangleToClient(r);
g.SetClip(r2, System.Drawing.Drawing2D.CombineMode.Intersect);
}
this.Overlay.Draw(this.objectListView, g, this.objectListView.ClientRectangle);
}
protected void RecalculateBounds() {
if (!this.isGlassShown)
return;
Rectangle rect = this.objectListView.ClientRectangle;
rect.X = 0;
rect.Y = 0;
this.Bounds = this.objectListView.RectangleToScreen(rect);
}
internal void UpdateTransparency() {
ITransparentOverlay transparentOverlay = this.Overlay as ITransparentOverlay;
if (transparentOverlay == null)
this.Opacity = this.objectListView.OverlayTransparency / 255.0f;
else
this.Opacity = transparentOverlay.Transparency / 255.0f;
}
protected override void WndProc(ref Message m) {
const int WM_NCHITTEST = 132;
const int HTTRANSPARENT = -1;
switch (m.Msg) {
// Ignore all mouse interactions
case WM_NCHITTEST:
m.Result = (IntPtr)HTTRANSPARENT;
break;
}
base.WndProc(ref m);
}
#endregion
#region Implementation variables
internal IOverlay Overlay;
#endregion
#region Private variables
private ObjectListView objectListView;
private bool isDuringResizeSequence;
private bool isGlassShown;
private bool wasGlassShownBeforeResize;
// Cache these so we can unsubscribe from events even when the OLV has been disposed.
private Form myOwner;
private Form mdiOwner;
private List<Control> ancestors;
MdiClient mdiClient;
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,189 @@
/*
* ToolStripCheckedListBox - Puts a CheckedListBox into a tool strip menu item
*
* Author: Phillip Piper
* Date: 4-March-2011 11:59 pm
*
* Change log:
* 2011-03-04 JPP - First version
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
namespace BrightIdeasSoftware {
/// <summary>
/// Instances of this class put a CheckedListBox into a tool strip menu item.
/// </summary>
public class ToolStripCheckedListBox : ToolStripControlHost {
/// <summary>
/// Create a ToolStripCheckedListBox
/// </summary>
public ToolStripCheckedListBox()
: base(new CheckedListBox()) {
this.CheckedListBoxControl.MaximumSize = new Size(400, 700);
this.CheckedListBoxControl.ThreeDCheckBoxes = true;
this.CheckedListBoxControl.CheckOnClick = true;
this.CheckedListBoxControl.SelectionMode = SelectionMode.One;
}
/// <summary>
/// Gets the control embedded in the menu
/// </summary>
public CheckedListBox CheckedListBoxControl {
get {
return Control as CheckedListBox;
}
}
/// <summary>
/// Gets the items shown in the checkedlistbox
/// </summary>
public CheckedListBox.ObjectCollection Items {
get {
return this.CheckedListBoxControl.Items;
}
}
/// <summary>
/// Gets or sets whether an item should be checked when it is clicked
/// </summary>
public bool CheckedOnClick {
get {
return this.CheckedListBoxControl.CheckOnClick;
}
set {
this.CheckedListBoxControl.CheckOnClick = value;
}
}
/// <summary>
/// Gets a collection of the checked items
/// </summary>
public CheckedListBox.CheckedItemCollection CheckedItems {
get {
return this.CheckedListBoxControl.CheckedItems;
}
}
/// <summary>
/// Add a possibly checked item to the control
/// </summary>
/// <param name="item"></param>
/// <param name="isChecked"></param>
public void AddItem(object item, bool isChecked) {
this.Items.Add(item);
if (isChecked)
this.CheckedListBoxControl.SetItemChecked(this.Items.Count - 1, true);
}
/// <summary>
/// Add an item with the given state to the control
/// </summary>
/// <param name="item"></param>
/// <param name="state"></param>
public void AddItem(object item, CheckState state) {
this.Items.Add(item);
this.CheckedListBoxControl.SetItemCheckState(this.Items.Count - 1, state);
}
/// <summary>
/// Gets the checkedness of the i'th item
/// </summary>
/// <param name="i"></param>
/// <returns></returns>
public CheckState GetItemCheckState(int i) {
return this.CheckedListBoxControl.GetItemCheckState(i);
}
/// <summary>
/// Set the checkedness of the i'th item
/// </summary>
/// <param name="i"></param>
/// <param name="checkState"></param>
public void SetItemState(int i, CheckState checkState) {
if (i >= 0 && i < this.Items.Count)
this.CheckedListBoxControl.SetItemCheckState(i, checkState);
}
/// <summary>
/// Check all the items in the control
/// </summary>
public void CheckAll() {
for (int i = 0; i < this.Items.Count; i++)
this.CheckedListBoxControl.SetItemChecked(i, true);
}
/// <summary>
/// Unchecked all the items in the control
/// </summary>
public void UncheckAll() {
for (int i = 0; i < this.Items.Count; i++)
this.CheckedListBoxControl.SetItemChecked(i, false);
}
#region Events
/// <summary>
/// Listen for events on the underlying control
/// </summary>
/// <param name="c"></param>
protected override void OnSubscribeControlEvents(Control c) {
base.OnSubscribeControlEvents(c);
CheckedListBox control = (CheckedListBox)c;
control.ItemCheck += new ItemCheckEventHandler(OnItemCheck);
}
/// <summary>
/// Stop listening for events on the underlying control
/// </summary>
/// <param name="c"></param>
protected override void OnUnsubscribeControlEvents(Control c) {
base.OnUnsubscribeControlEvents(c);
CheckedListBox control = (CheckedListBox)c;
control.ItemCheck -= new ItemCheckEventHandler(OnItemCheck);
}
/// <summary>
/// Tell the world that an item was checked
/// </summary>
public event ItemCheckEventHandler ItemCheck;
/// <summary>
/// Trigger the ItemCheck event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnItemCheck(object sender, ItemCheckEventArgs e) {
if (ItemCheck != null) {
ItemCheck(this, e);
}
}
#endregion
}
}

View File

@@ -0,0 +1,699 @@
/*
* ToolTipControl - A limited wrapper around a Windows tooltip control
*
* For some reason, the ToolTip class in the .NET framework is implemented in a significantly
* different manner to other controls. For our purposes, the worst of these problems
* is that we cannot get the Handle, so we cannot send Windows level messages to the control.
*
* Author: Phillip Piper
* Date: 2009-05-17 7:22PM
*
* Change log:
* v2.3
* 2009-06-13 JPP - Moved ToolTipShowingEventArgs to Events.cs
* v2.2
* 2009-06-06 JPP - Fixed some Vista specific problems
* 2009-05-17 JPP - Initial version
*
* TO DO:
*
* Copyright (C) 2006-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Permissions;
using System.Windows.Forms;
namespace BrightIdeasSoftware
{
[SupportedOSPlatform("windows")]
/// <summary>
/// A limited wrapper around a Windows tooltip window.
/// </summary>
public class ToolTipControl : NativeWindow
{
#region Constants
/// <summary>
/// These are the standard icons that a tooltip can display.
/// </summary>
public enum StandardIcons
{
/// <summary>
/// No icon
/// </summary>
None = 0,
/// <summary>
/// Info
/// </summary>
Info = 1,
/// <summary>
/// Warning
/// </summary>
Warning = 2,
/// <summary>
/// Error
/// </summary>
Error = 3,
/// <summary>
/// Large info (Vista and later only)
/// </summary>
InfoLarge = 4,
/// <summary>
/// Large warning (Vista and later only)
/// </summary>
WarningLarge = 5,
/// <summary>
/// Large error (Vista and later only)
/// </summary>
ErrorLarge = 6
}
const int GWL_STYLE = -16;
const int WM_GETFONT = 0x31;
const int WM_SETFONT = 0x30;
const int WS_BORDER = 0x800000;
const int WS_EX_TOPMOST = 8;
const int TTM_ADDTOOL = 0x432;
const int TTM_ADJUSTRECT = 0x400 + 31;
const int TTM_DELTOOL = 0x433;
const int TTM_GETBUBBLESIZE = 0x400 + 30;
const int TTM_GETCURRENTTOOL = 0x400 + 59;
const int TTM_GETTIPBKCOLOR = 0x400 + 22;
const int TTM_GETTIPTEXTCOLOR = 0x400 + 23;
const int TTM_GETDELAYTIME = 0x400 + 21;
const int TTM_NEWTOOLRECT = 0x400 + 52;
const int TTM_POP = 0x41c;
const int TTM_SETDELAYTIME = 0x400 + 3;
const int TTM_SETMAXTIPWIDTH = 0x400 + 24;
const int TTM_SETTIPBKCOLOR = 0x400 + 19;
const int TTM_SETTIPTEXTCOLOR = 0x400 + 20;
const int TTM_SETTITLE = 0x400 + 33;
const int TTM_SETTOOLINFO = 0x400 + 54;
const int TTF_IDISHWND = 1;
//const int TTF_ABSOLUTE = 0x80;
const int TTF_CENTERTIP = 2;
const int TTF_RTLREADING = 4;
const int TTF_SUBCLASS = 0x10;
//const int TTF_TRACK = 0x20;
//const int TTF_TRANSPARENT = 0x100;
const int TTF_PARSELINKS = 0x1000;
const int TTS_NOPREFIX = 2;
const int TTS_BALLOON = 0x40;
const int TTS_USEVISUALSTYLE = 0x100;
const int TTN_FIRST = -520;
/// <summary>
///
/// </summary>
public const int TTN_SHOW = (TTN_FIRST - 1);
/// <summary>
///
/// </summary>
public const int TTN_POP = (TTN_FIRST - 2);
/// <summary>
///
/// </summary>
public const int TTN_LINKCLICK = (TTN_FIRST - 3);
/// <summary>
///
/// </summary>
public const int TTN_GETDISPINFO = (TTN_FIRST - 10);
const int TTDT_AUTOMATIC = 0;
const int TTDT_RESHOW = 1;
const int TTDT_AUTOPOP = 2;
const int TTDT_INITIAL = 3;
#endregion
#region Properties
/// <summary>
/// Get or set if the style of the tooltip control
/// </summary>
internal int WindowStyle {
get {
return (int)NativeMethods.GetWindowLong(this.Handle, GWL_STYLE);
}
set {
NativeMethods.SetWindowLong(this.Handle, GWL_STYLE, value);
}
}
/// <summary>
/// Get or set if the tooltip should be shown as a ballon
/// </summary>
public bool IsBalloon {
get {
return (this.WindowStyle & TTS_BALLOON) == TTS_BALLOON;
}
set {
if (this.IsBalloon == value)
return;
int windowStyle = this.WindowStyle;
if (value) {
windowStyle |= (TTS_BALLOON | TTS_USEVISUALSTYLE);
// On XP, a border makes the ballon look wrong
if (!ObjectListView.IsVistaOrLater)
windowStyle &= ~WS_BORDER;
} else {
windowStyle &= ~(TTS_BALLOON | TTS_USEVISUALSTYLE);
if (!ObjectListView.IsVistaOrLater) {
if (this.hasBorder)
windowStyle |= WS_BORDER;
else
windowStyle &= ~WS_BORDER;
}
}
this.WindowStyle = windowStyle;
}
}
/// <summary>
/// Get or set if the tooltip should be shown as a ballon
/// </summary>
public bool HasBorder {
get {
return this.hasBorder;
}
set {
if (this.hasBorder == value)
return;
if (value) {
this.WindowStyle |= WS_BORDER;
} else {
this.WindowStyle &= ~WS_BORDER;
}
}
}
private bool hasBorder = true;
/// <summary>
/// Get or set the background color of the tooltip
/// </summary>
public Color BackColor {
get {
int color = (int)NativeMethods.SendMessage(this.Handle, TTM_GETTIPBKCOLOR, 0, 0);
return ColorTranslator.FromWin32(color);
}
set {
// For some reason, setting the color fails on Vista and messes up later ops.
// So we don't even try to set it.
if (!ObjectListView.IsVistaOrLater) {
int color = ColorTranslator.ToWin32(value);
NativeMethods.SendMessage(this.Handle, TTM_SETTIPBKCOLOR, color, 0);
//int x2 = Marshal.GetLastWin32Error();
}
}
}
/// <summary>
/// Get or set the color of the text and border on the tooltip.
/// </summary>
public Color ForeColor {
get {
int color = (int)NativeMethods.SendMessage(this.Handle, TTM_GETTIPTEXTCOLOR, 0, 0);
return ColorTranslator.FromWin32(color);
}
set {
// For some reason, setting the color fails on Vista and messes up later ops.
// So we don't even try to set it.
if (!ObjectListView.IsVistaOrLater) {
int color = ColorTranslator.ToWin32(value);
NativeMethods.SendMessage(this.Handle, TTM_SETTIPTEXTCOLOR, color, 0);
}
}
}
/// <summary>
/// Get or set the title that will be shown on the tooltip.
/// </summary>
public string Title {
get {
return this.title;
}
set {
if (String.IsNullOrEmpty(value))
this.title = String.Empty;
else
if (value.Length >= 100)
this.title = value.Substring(0, 99);
else
this.title = value;
NativeMethods.SendMessageString(this.Handle, TTM_SETTITLE, (int)this.standardIcon, this.title);
}
}
private string title;
/// <summary>
/// Get or set the icon that will be shown on the tooltip.
/// </summary>
public StandardIcons StandardIcon {
get {
return this.standardIcon;
}
set {
this.standardIcon = value;
NativeMethods.SendMessageString(this.Handle, TTM_SETTITLE, (int)this.standardIcon, this.title);
}
}
private StandardIcons standardIcon;
/// <summary>
/// Gets or sets the font that will be used to draw this control.
/// is still.
/// </summary>
/// <remarks>Setting this to null reverts to the default font.</remarks>
public Font Font {
get {
IntPtr hfont = NativeMethods.SendMessage(this.Handle, WM_GETFONT, 0, 0);
if (hfont == IntPtr.Zero)
return Control.DefaultFont;
else
return Font.FromHfont(hfont);
}
set {
Font newFont = value ?? Control.DefaultFont;
if (newFont == this.font)
return;
this.font = newFont;
IntPtr hfont = this.font.ToHfont(); // THINK: When should we delete this hfont?
NativeMethods.SendMessage(this.Handle, WM_SETFONT, hfont, 0);
}
}
private Font font;
/// <summary>
/// Gets or sets how many milliseconds the tooltip will remain visible while the mouse
/// is still.
/// </summary>
public int AutoPopDelay {
get { return this.GetDelayTime(TTDT_AUTOPOP); }
set { this.SetDelayTime(TTDT_AUTOPOP, value); }
}
/// <summary>
/// Gets or sets how many milliseconds the mouse must be still before the tooltip is shown.
/// </summary>
public int InitialDelay {
get { return this.GetDelayTime(TTDT_INITIAL); }
set { this.SetDelayTime(TTDT_INITIAL, value); }
}
/// <summary>
/// Gets or sets how many milliseconds the mouse must be still before the tooltip is shown again.
/// </summary>
public int ReshowDelay {
get { return this.GetDelayTime(TTDT_RESHOW); }
set { this.SetDelayTime(TTDT_RESHOW, value); }
}
private int GetDelayTime(int which) {
return (int)NativeMethods.SendMessage(this.Handle, TTM_GETDELAYTIME, which, 0);
}
private void SetDelayTime(int which, int value) {
NativeMethods.SendMessage(this.Handle, TTM_SETDELAYTIME, which, value);
}
#endregion
#region Commands
/// <summary>
/// Create the underlying control.
/// </summary>
/// <param name="parentHandle">The parent of the tooltip</param>
/// <remarks>This does nothing if the control has already been created</remarks>
public void Create(IntPtr parentHandle) {
if (this.Handle != IntPtr.Zero)
return;
CreateParams cp = new CreateParams();
cp.ClassName = "tooltips_class32";
cp.Style = TTS_NOPREFIX;
cp.ExStyle = WS_EX_TOPMOST;
cp.Parent = parentHandle;
this.CreateHandle(cp);
// Ensure that multiline tooltips work correctly
this.SetMaxWidth();
}
/// <summary>
/// Take a copy of the current settings and restore them when the
/// tooltip is poppped.
/// </summary>
/// <remarks>
/// This call cannot be nested. Subsequent calls to this method will be ignored
/// until PopSettings() is called.
/// </remarks>
public void PushSettings() {
// Ignore any nested calls
if (this.settings != null)
return;
this.settings = new Hashtable();
this.settings["IsBalloon"] = this.IsBalloon;
this.settings["HasBorder"] = this.HasBorder;
this.settings["BackColor"] = this.BackColor;
this.settings["ForeColor"] = this.ForeColor;
this.settings["Title"] = this.Title;
this.settings["StandardIcon"] = this.StandardIcon;
this.settings["AutoPopDelay"] = this.AutoPopDelay;
this.settings["InitialDelay"] = this.InitialDelay;
this.settings["ReshowDelay"] = this.ReshowDelay;
this.settings["Font"] = this.Font;
}
private Hashtable settings;
/// <summary>
/// Restore the settings of the tooltip as they were when PushSettings()
/// was last called.
/// </summary>
public void PopSettings() {
if (this.settings == null)
return;
this.IsBalloon = (bool)this.settings["IsBalloon"];
this.HasBorder = (bool)this.settings["HasBorder"];
this.BackColor = (Color)this.settings["BackColor"];
this.ForeColor = (Color)this.settings["ForeColor"];
this.Title = (string)this.settings["Title"];
this.StandardIcon = (StandardIcons)this.settings["StandardIcon"];
this.AutoPopDelay = (int)this.settings["AutoPopDelay"];
this.InitialDelay = (int)this.settings["InitialDelay"];
this.ReshowDelay = (int)this.settings["ReshowDelay"];
this.Font = (Font)this.settings["Font"];
this.settings = null;
}
/// <summary>
/// Add the given window to those for whom this tooltip will show tips
/// </summary>
/// <param name="window">The window</param>
public void AddTool(IWin32Window window) {
NativeMethods.TOOLINFO lParam = this.MakeToolInfoStruct(window);
NativeMethods.SendMessageTOOLINFO(this.Handle, TTM_ADDTOOL, 0, lParam);
}
/// <summary>
/// Hide any currently visible tooltip
/// </summary>
/// <param name="window"></param>
public void PopToolTip(IWin32Window window) {
NativeMethods.SendMessage(this.Handle, TTM_POP, 0, 0);
}
//public void Munge() {
// NativeMethods.TOOLINFO tool = new NativeMethods.TOOLINFO();
// IntPtr result = NativeMethods.SendMessageTOOLINFO(this.Handle, TTM_GETCURRENTTOOL, 0, tool);
// System.Diagnostics.Trace.WriteLine("-");
// System.Diagnostics.Trace.WriteLine(result);
// result = NativeMethods.SendMessageTOOLINFO(this.Handle, TTM_GETBUBBLESIZE, 0, tool);
// System.Diagnostics.Trace.WriteLine(String.Format("{0} {1}", result.ToInt32() >> 16, result.ToInt32() & 0xFFFF));
// NativeMethods.ChangeSize(this, result.ToInt32() & 0xFFFF, result.ToInt32() >> 16);
// //NativeMethods.RECT r = new NativeMethods.RECT();
// //r.right
// //IntPtr x = NativeMethods.SendMessageRECT(this.Handle, TTM_ADJUSTRECT, true, ref r);
// //System.Diagnostics.Trace.WriteLine(String.Format("{0} {1} {2} {3}", r.left, r.top, r.right, r.bottom));
//}
/// <summary>
/// Remove the given window from those managed by this tooltip
/// </summary>
/// <param name="window"></param>
public void RemoveToolTip(IWin32Window window) {
NativeMethods.TOOLINFO lParam = this.MakeToolInfoStruct(window);
NativeMethods.SendMessageTOOLINFO(this.Handle, TTM_DELTOOL, 0, lParam);
}
/// <summary>
/// Set the maximum width of a tooltip string.
/// </summary>
public void SetMaxWidth() {
this.SetMaxWidth(SystemInformation.MaxWindowTrackSize.Width);
}
/// <summary>
/// Set the maximum width of a tooltip string.
/// </summary>
/// <remarks>Setting this ensures that line breaks in the tooltip are honoured.</remarks>
public void SetMaxWidth(int maxWidth) {
NativeMethods.SendMessage(this.Handle, TTM_SETMAXTIPWIDTH, 0, maxWidth);
}
#endregion
#region Implementation
/// <summary>
/// Make a TOOLINFO structure for the given window
/// </summary>
/// <param name="window"></param>
/// <returns>A filled in TOOLINFO</returns>
private NativeMethods.TOOLINFO MakeToolInfoStruct(IWin32Window window) {
NativeMethods.TOOLINFO toolinfo_tooltip = new NativeMethods.TOOLINFO();
toolinfo_tooltip.hwnd = window.Handle;
toolinfo_tooltip.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
toolinfo_tooltip.uId = window.Handle;
toolinfo_tooltip.lpszText = (IntPtr)(-1); // LPSTR_TEXTCALLBACK
return toolinfo_tooltip;
}
/// <summary>
/// Handle a WmNotify message
/// </summary>
/// <param name="msg">The msg</param>
/// <returns>True if the message has been handled</returns>
protected virtual bool HandleNotify(ref Message msg) {
//THINK: What do we have to do here? Nothing it seems :)
//NativeMethods.NMHEADER nmheader = (NativeMethods.NMHEADER)msg.GetLParam(typeof(NativeMethods.NMHEADER));
//System.Diagnostics.Trace.WriteLine("HandleNotify");
//System.Diagnostics.Trace.WriteLine(nmheader.nhdr.code);
//switch (nmheader.nhdr.code) {
//}
return false;
}
/// <summary>
/// Handle a get display info message
/// </summary>
/// <param name="msg">The msg</param>
/// <returns>True if the message has been handled</returns>
public virtual bool HandleGetDispInfo(ref Message msg) {
//System.Diagnostics.Trace.WriteLine("HandleGetDispInfo");
this.SetMaxWidth();
ToolTipShowingEventArgs args = new ToolTipShowingEventArgs();
args.ToolTipControl = this;
this.OnShowing(args);
if (String.IsNullOrEmpty(args.Text))
return false;
this.ApplyEventFormatting(args);
NativeMethods.NMTTDISPINFO dispInfo = (NativeMethods.NMTTDISPINFO)msg.GetLParam(typeof(NativeMethods.NMTTDISPINFO));
dispInfo.lpszText = args.Text;
dispInfo.hinst = IntPtr.Zero;
if (args.RightToLeft == RightToLeft.Yes)
dispInfo.uFlags |= TTF_RTLREADING;
Marshal.StructureToPtr(dispInfo, msg.LParam, false);
return true;
}
private void ApplyEventFormatting(ToolTipShowingEventArgs args) {
if (!args.IsBalloon.HasValue &&
!args.BackColor.HasValue &&
!args.ForeColor.HasValue &&
args.Title == null &&
!args.StandardIcon.HasValue &&
!args.AutoPopDelay.HasValue &&
args.Font == null)
return;
this.PushSettings();
if (args.IsBalloon.HasValue)
this.IsBalloon = args.IsBalloon.Value;
if (args.BackColor.HasValue)
this.BackColor = args.BackColor.Value;
if (args.ForeColor.HasValue)
this.ForeColor = args.ForeColor.Value;
if (args.StandardIcon.HasValue)
this.StandardIcon = args.StandardIcon.Value;
if (args.AutoPopDelay.HasValue)
this.AutoPopDelay = args.AutoPopDelay.Value;
if (args.Font != null)
this.Font = args.Font;
if (args.Title != null)
this.Title = args.Title;
}
/// <summary>
/// Handle a TTN_LINKCLICK message
/// </summary>
/// <param name="msg">The msg</param>
/// <returns>True if the message has been handled</returns>
/// <remarks>This cannot call base.WndProc() since the msg may have come from another control.</remarks>
public virtual bool HandleLinkClick(ref Message msg) {
//System.Diagnostics.Trace.WriteLine("HandleLinkClick");
return false;
}
/// <summary>
/// Handle a TTN_POP message
/// </summary>
/// <param name="msg">The msg</param>
/// <returns>True if the message has been handled</returns>
/// <remarks>This cannot call base.WndProc() since the msg may have come from another control.</remarks>
public virtual bool HandlePop(ref Message msg) {
//System.Diagnostics.Trace.WriteLine("HandlePop");
this.PopSettings();
return true;
}
/// <summary>
/// Handle a TTN_SHOW message
/// </summary>
/// <param name="msg">The msg</param>
/// <returns>True if the message has been handled</returns>
/// <remarks>This cannot call base.WndProc() since the msg may have come from another control.</remarks>
public virtual bool HandleShow(ref Message msg) {
//System.Diagnostics.Trace.WriteLine("HandleShow");
return false;
}
/// <summary>
/// Handle a reflected notify message
/// </summary>
/// <param name="msg">The msg</param>
/// <returns>True if the message has been handled</returns>
protected virtual bool HandleReflectNotify(ref Message msg) {
NativeMethods.NMHEADER nmheader = (NativeMethods.NMHEADER)msg.GetLParam(typeof(NativeMethods.NMHEADER));
switch (nmheader.nhdr.code) {
case TTN_SHOW:
//System.Diagnostics.Trace.WriteLine("reflect TTN_SHOW");
if (this.HandleShow(ref msg))
return true;
break;
case TTN_POP:
//System.Diagnostics.Trace.WriteLine("reflect TTN_POP");
if (this.HandlePop(ref msg))
return true;
break;
case TTN_LINKCLICK:
//System.Diagnostics.Trace.WriteLine("reflect TTN_LINKCLICK");
if (this.HandleLinkClick(ref msg))
return true;
break;
case TTN_GETDISPINFO:
//System.Diagnostics.Trace.WriteLine("reflect TTN_GETDISPINFO");
if (this.HandleGetDispInfo(ref msg))
return true;
break;
}
return false;
}
/// <summary>
/// Mess with the basic message pump of the tooltip
/// </summary>
/// <param name="msg"></param>
override protected void WndProc(ref Message msg) {
//System.Diagnostics.Trace.WriteLine(String.Format("xx {0:x}", msg.Msg));
switch (msg.Msg) {
case 0x4E: // WM_NOTIFY
if (!this.HandleNotify(ref msg))
return;
break;
case 0x204E: // WM_REFLECT_NOTIFY
if (!this.HandleReflectNotify(ref msg))
return;
break;
}
base.WndProc(ref msg);
}
#endregion
#region Events
/// <summary>
/// Tell the world that a tooltip is about to show
/// </summary>
public event EventHandler<ToolTipShowingEventArgs> Showing;
/// <summary>
/// Tell the world that a tooltip is about to disappear
/// </summary>
public event EventHandler<EventArgs> Pop;
/// <summary>
///
/// </summary>
/// <param name="e"></param>
protected virtual void OnShowing(ToolTipShowingEventArgs e) {
if (this.Showing != null)
this.Showing(this, e);
}
/// <summary>
///
/// </summary>
/// <param name="e"></param>
protected virtual void OnPop(EventArgs e) {
if (this.Pop != null)
this.Pop(this, e);
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,190 @@
namespace BrightIdeasSoftware
{
partial class ColumnSelectionForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.buttonMoveUp = new System.Windows.Forms.Button();
this.buttonMoveDown = new System.Windows.Forms.Button();
this.buttonShow = new System.Windows.Forms.Button();
this.buttonHide = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.buttonOK = new System.Windows.Forms.Button();
this.buttonCancel = new System.Windows.Forms.Button();
this.objectListView1 = new BrightIdeasSoftware.ObjectListView();
this.olvColumn1 = new BrightIdeasSoftware.OLVColumn();
((System.ComponentModel.ISupportInitialize)(this.objectListView1)).BeginInit();
this.SuspendLayout();
//
// buttonMoveUp
//
this.buttonMoveUp.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.buttonMoveUp.Location = new System.Drawing.Point(295, 31);
this.buttonMoveUp.Name = "buttonMoveUp";
this.buttonMoveUp.Size = new System.Drawing.Size(87, 23);
this.buttonMoveUp.TabIndex = 1;
this.buttonMoveUp.Text = "Move &Up";
this.buttonMoveUp.UseVisualStyleBackColor = true;
this.buttonMoveUp.Click += new System.EventHandler(this.buttonMoveUp_Click);
//
// buttonMoveDown
//
this.buttonMoveDown.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.buttonMoveDown.Location = new System.Drawing.Point(295, 60);
this.buttonMoveDown.Name = "buttonMoveDown";
this.buttonMoveDown.Size = new System.Drawing.Size(87, 23);
this.buttonMoveDown.TabIndex = 2;
this.buttonMoveDown.Text = "Move &Down";
this.buttonMoveDown.UseVisualStyleBackColor = true;
this.buttonMoveDown.Click += new System.EventHandler(this.buttonMoveDown_Click);
//
// buttonShow
//
this.buttonShow.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.buttonShow.Location = new System.Drawing.Point(295, 89);
this.buttonShow.Name = "buttonShow";
this.buttonShow.Size = new System.Drawing.Size(87, 23);
this.buttonShow.TabIndex = 3;
this.buttonShow.Text = "&Show";
this.buttonShow.UseVisualStyleBackColor = true;
this.buttonShow.Click += new System.EventHandler(this.buttonShow_Click);
//
// buttonHide
//
this.buttonHide.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.buttonHide.Location = new System.Drawing.Point(295, 118);
this.buttonHide.Name = "buttonHide";
this.buttonHide.Size = new System.Drawing.Size(87, 23);
this.buttonHide.TabIndex = 4;
this.buttonHide.Text = "&Hide";
this.buttonHide.UseVisualStyleBackColor = true;
this.buttonHide.Click += new System.EventHandler(this.buttonHide_Click);
//
// label1
//
this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.label1.BackColor = System.Drawing.SystemColors.Control;
this.label1.Location = new System.Drawing.Point(13, 9);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(366, 19);
this.label1.TabIndex = 5;
this.label1.Text = "Choose the columns you want to see in this list. ";
//
// buttonOK
//
this.buttonOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.buttonOK.Location = new System.Drawing.Point(198, 304);
this.buttonOK.Name = "buttonOK";
this.buttonOK.Size = new System.Drawing.Size(87, 23);
this.buttonOK.TabIndex = 6;
this.buttonOK.Text = "&OK";
this.buttonOK.UseVisualStyleBackColor = true;
this.buttonOK.Click += new System.EventHandler(this.buttonOK_Click);
//
// buttonCancel
//
this.buttonCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.buttonCancel.Location = new System.Drawing.Point(295, 304);
this.buttonCancel.Name = "buttonCancel";
this.buttonCancel.Size = new System.Drawing.Size(87, 23);
this.buttonCancel.TabIndex = 7;
this.buttonCancel.Text = "&Cancel";
this.buttonCancel.UseVisualStyleBackColor = true;
this.buttonCancel.Click += new System.EventHandler(this.buttonCancel_Click);
//
// objectListView1
//
this.objectListView1.AllColumns.Add(this.olvColumn1);
this.objectListView1.AlternateRowBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(192)))), ((int)(((byte)(255)))), ((int)(((byte)(192)))));
this.objectListView1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.objectListView1.CellEditActivation = BrightIdeasSoftware.ObjectListView.CellEditActivateMode.SingleClick;
this.objectListView1.SetCheckBoxes(true);
this.objectListView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
this.olvColumn1});
this.objectListView1.FullRowSelect = true;
this.objectListView1.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.None;
this.objectListView1.HideSelection = false;
this.objectListView1.Location = new System.Drawing.Point(12, 31);
this.objectListView1.MultiSelect = false;
this.objectListView1.Name = "objectListView1";
this.objectListView1.ShowGroups = false;
this.objectListView1.ShowSortIndicators = false;
this.objectListView1.Size = new System.Drawing.Size(273, 259);
this.objectListView1.TabIndex = 0;
this.objectListView1.UseCompatibleStateImageBehavior = false;
this.objectListView1.View = System.Windows.Forms.View.Details;
this.objectListView1.SelectionChanged += new System.EventHandler(this.objectListView1_SelectionChanged);
//
// olvColumn1
//
this.olvColumn1.AspectName = "Text";
this.olvColumn1.IsVisible = true;
this.olvColumn1.Text = "Column";
this.olvColumn1.Width = 267;
//
// ColumnSelectionForm
//
this.AcceptButton = this.buttonOK;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.buttonCancel;
this.ClientSize = new System.Drawing.Size(391, 339);
this.Controls.Add(this.buttonCancel);
this.Controls.Add(this.buttonOK);
this.Controls.Add(this.label1);
this.Controls.Add(this.buttonHide);
this.Controls.Add(this.buttonShow);
this.Controls.Add(this.buttonMoveDown);
this.Controls.Add(this.buttonMoveUp);
this.Controls.Add(this.objectListView1);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "ColumnSelectionForm";
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.Text = "Column Selection";
((System.ComponentModel.ISupportInitialize)(this.objectListView1)).EndInit();
this.ResumeLayout(false);
}
#endregion
private BrightIdeasSoftware.ObjectListView objectListView1;
private System.Windows.Forms.Button buttonMoveUp;
private System.Windows.Forms.Button buttonMoveDown;
private System.Windows.Forms.Button buttonShow;
private System.Windows.Forms.Button buttonHide;
private BrightIdeasSoftware.OLVColumn olvColumn1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Button buttonOK;
private System.Windows.Forms.Button buttonCancel;
}
}

View File

@@ -0,0 +1,259 @@
/*
* ColumnSelectionForm - A utility form that allows columns to be rearranged and/or hidden
*
* Author: Phillip Piper
* Date: 1/04/2011 11:15 AM
*
* Change log:
* 2013-04-21 JPP - Fixed obscure bug in column re-ordered. Thanks to Edwin Chen.
*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace BrightIdeasSoftware
{
/// <summary>
/// This form is an example of how an application could allows the user to select which columns
/// an ObjectListView will display, as well as select which order the columns are displayed in.
/// </summary>
/// <remarks>
/// <para>In Tile view, ColumnHeader.DisplayIndex does nothing. To reorder the columns you have
/// to change the order of objects in the Columns property.</para>
/// <para>Remember that the first column is special!
/// It has to remain the first column.</para>
/// </remarks>
public partial class ColumnSelectionForm : Form
{
/// <summary>
/// Make a new ColumnSelectionForm
/// </summary>
public ColumnSelectionForm()
{
InitializeComponent();
}
/// <summary>
/// Open this form so it will edit the columns that are available in the listview's current view
/// </summary>
/// <param name="olv">The ObjectListView whose columns are to be altered</param>
public void OpenOn(ObjectListView olv)
{
this.OpenOn(olv, olv.View);
}
/// <summary>
/// Open this form so it will edit the columns that are available in the given listview
/// when the listview is showing the given type of view.
/// </summary>
/// <param name="olv">The ObjectListView whose columns are to be altered</param>
/// <param name="view">The view that is to be altered. Must be View.Details or View.Tile</param>
public void OpenOn(ObjectListView olv, View view)
{
if (view != View.Details && view != View.Tile)
return;
this.InitializeForm(olv, view);
if (this.ShowDialog() == DialogResult.OK)
this.Apply(olv, view);
}
/// <summary>
/// Initialize the form to show the columns of the given view
/// </summary>
/// <param name="olv"></param>
/// <param name="view"></param>
protected void InitializeForm(ObjectListView olv, View view)
{
this.AllColumns = olv.AllColumns;
this.RearrangableColumns = new List<OLVColumn>(this.AllColumns);
foreach (OLVColumn col in this.RearrangableColumns) {
if (view == View.Details)
this.MapColumnToVisible[col] = col.IsVisible;
else
this.MapColumnToVisible[col] = col.IsTileViewColumn;
}
this.RearrangableColumns.Sort(new SortByDisplayOrder(this));
this.objectListView1.BooleanCheckStateGetter = delegate(Object rowObject) {
return this.MapColumnToVisible[(OLVColumn)rowObject];
};
this.objectListView1.BooleanCheckStatePutter = delegate(Object rowObject, bool newValue) {
// Some columns should always be shown, so ignore attempts to hide them
OLVColumn column = (OLVColumn)rowObject;
if (!column.CanBeHidden)
return true;
this.MapColumnToVisible[column] = newValue;
EnableControls();
return newValue;
};
this.objectListView1.SetObjects(this.RearrangableColumns);
this.EnableControls();
}
private List<OLVColumn> AllColumns = null;
private List<OLVColumn> RearrangableColumns = new List<OLVColumn>();
private Dictionary<OLVColumn, bool> MapColumnToVisible = new Dictionary<OLVColumn, bool>();
/// <summary>
/// The user has pressed OK. Do what's requied.
/// </summary>
/// <param name="olv"></param>
/// <param name="view"></param>
protected void Apply(ObjectListView olv, View view)
{
olv.Freeze();
// Update the column definitions to reflect whether they have been hidden
if (view == View.Details) {
foreach (OLVColumn col in olv.AllColumns)
col.IsVisible = this.MapColumnToVisible[col];
} else {
foreach (OLVColumn col in olv.AllColumns)
col.IsTileViewColumn = this.MapColumnToVisible[col];
}
// Collect the columns are still visible
List<OLVColumn> visibleColumns = this.RearrangableColumns.FindAll(
delegate(OLVColumn x) { return this.MapColumnToVisible[x]; });
// Detail view and Tile view have to be handled in different ways.
if (view == View.Details) {
// Of the still visible columns, change DisplayIndex to reflect their position in the rearranged list
olv.ChangeToFilteredColumns(view);
foreach (OLVColumn col in visibleColumns) {
col.DisplayIndex = visibleColumns.IndexOf((OLVColumn)col);
col.LastDisplayIndex = col.DisplayIndex;
}
} else {
// In Tile view, DisplayOrder does nothing. So to change the display order, we have to change the
// order of the columns in the Columns property.
// Remember, the primary column is special and has to remain first!
OLVColumn primaryColumn = this.AllColumns[0];
visibleColumns.Remove(primaryColumn);
olv.Columns.Clear();
olv.Columns.Add(primaryColumn);
olv.Columns.AddRange(visibleColumns.ToArray());
olv.CalculateReasonableTileSize();
}
olv.Unfreeze();
}
#region Event handlers
private void buttonMoveUp_Click(object sender, EventArgs e)
{
int selectedIndex = this.objectListView1.SelectedIndices[0];
OLVColumn col = this.RearrangableColumns[selectedIndex];
this.RearrangableColumns.RemoveAt(selectedIndex);
this.RearrangableColumns.Insert(selectedIndex-1, col);
this.objectListView1.BuildList();
EnableControls();
}
private void buttonMoveDown_Click(object sender, EventArgs e)
{
int selectedIndex = this.objectListView1.SelectedIndices[0];
OLVColumn col = this.RearrangableColumns[selectedIndex];
this.RearrangableColumns.RemoveAt(selectedIndex);
this.RearrangableColumns.Insert(selectedIndex + 1, col);
this.objectListView1.BuildList();
EnableControls();
}
private void buttonShow_Click(object sender, EventArgs e)
{
this.objectListView1.SelectedItem.Checked = true;
}
private void buttonHide_Click(object sender, EventArgs e)
{
this.objectListView1.SelectedItem.Checked = false;
}
private void buttonOK_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.OK;
this.Close();
}
private void buttonCancel_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.Cancel;
this.Close();
}
private void objectListView1_SelectionChanged(object sender, EventArgs e)
{
EnableControls();
}
#endregion
#region Control enabling
/// <summary>
/// Enable the controls on the dialog to match the current state
/// </summary>
protected void EnableControls()
{
if (this.objectListView1.SelectedIndices.Count == 0) {
this.buttonMoveUp.Enabled = false;
this.buttonMoveDown.Enabled = false;
this.buttonShow.Enabled = false;
this.buttonHide.Enabled = false;
} else {
// Can't move the first row up or the last row down
this.buttonMoveUp.Enabled = (this.objectListView1.SelectedIndices[0] != 0);
this.buttonMoveDown.Enabled = (this.objectListView1.SelectedIndices[0] < (this.objectListView1.GetItemCount() - 1));
OLVColumn selectedColumn = (OLVColumn)this.objectListView1.SelectedObject;
// Some columns cannot be hidden (and hence cannot be Shown)
this.buttonShow.Enabled = !this.MapColumnToVisible[selectedColumn] && selectedColumn.CanBeHidden;
this.buttonHide.Enabled = this.MapColumnToVisible[selectedColumn] && selectedColumn.CanBeHidden;
}
}
#endregion
/// <summary>
/// A Comparer that will sort a list of columns so that visible ones come before hidden ones,
/// and that are ordered by their display order.
/// </summary>
private class SortByDisplayOrder(ColumnSelectionForm form) : IComparer<OLVColumn>
{
private ColumnSelectionForm Form = form;
#region IComparer<OLVColumn> Members
int IComparer<OLVColumn>.Compare(OLVColumn x, OLVColumn y)
{
if (this.Form.MapColumnToVisible[x] && !this.Form.MapColumnToVisible[y])
return -1;
if (!this.Form.MapColumnToVisible[x] && this.Form.MapColumnToVisible[y])
return 1;
if (x.DisplayIndex == y.DisplayIndex)
return x.Text.CompareTo(y.Text);
else
return x.DisplayIndex - y.DisplayIndex;
}
#endregion
}
}
}

Some files were not shown because too many files have changed in this diff Show More