Compare commits
796 Commits
v1.77.2-de
...
20250915-v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b2ae29f4a | ||
|
|
99a2b968d5 | ||
|
|
029672a907 | ||
|
|
31ecbaa977 | ||
|
|
6580e1b6db | ||
|
|
16bbe4ccc0 | ||
|
|
6ea3642700 | ||
|
|
7b7fe1b062 | ||
|
|
e67c97cc21 | ||
|
|
1033214658 | ||
|
|
55552ad118 | ||
|
|
552c53b15d | ||
|
|
278fc30ae8 | ||
|
|
80fb676763 | ||
|
|
c511fd1895 | ||
|
|
e43a644eb0 | ||
|
|
70c16caf6b | ||
|
|
21dcfc9435 | ||
|
|
56ff019185 | ||
|
|
44a6cb31aa | ||
|
|
a01ebdaa9d | ||
|
|
651b742cc2 | ||
|
|
e2b1eb6e4a | ||
|
|
2546620c8d | ||
|
|
826f75348c | ||
|
|
b27462da05 | ||
|
|
ad0ade5dd4 | ||
|
|
6e3cf4630e | ||
|
|
6f3768db4b | ||
|
|
98220ccf93 | ||
|
|
52688d7145 | ||
|
|
b24edae66b | ||
|
|
ac39ce26ee | ||
|
|
cf041c661b | ||
|
|
9d173d0bdc | ||
|
|
aafb608149 | ||
|
|
04dd125ee6 | ||
|
|
e501235b2e | ||
|
|
7c6a322787 | ||
|
|
eed729181b | ||
|
|
ce9d42d304 | ||
|
|
733e31e54b | ||
|
|
f1714f3228 | ||
|
|
44c90f0de4 | ||
|
|
8268310685 | ||
|
|
d64eaf6234 | ||
|
|
4d0b848676 | ||
|
|
d508ca33c8 | ||
|
|
86fdea129b | ||
|
|
79af98845a | ||
|
|
2c15c6b0ed | ||
|
|
52ee10efda | ||
|
|
2da7d02a2c | ||
|
|
7af4ba27f3 | ||
|
|
46cd974a58 | ||
|
|
3d791b7f78 | ||
|
|
29e27c1283 | ||
|
|
8434ab460c | ||
|
|
561d073bba | ||
|
|
d46d37acb9 | ||
|
|
4f4d841137 | ||
|
|
b7e80ceee9 | ||
|
|
8a3112c71f | ||
|
|
4276b1439b | ||
|
|
3f626149f4 | ||
|
|
30b674f8f5 | ||
|
|
fe3fb46e94 | ||
|
|
99c524c7b6 | ||
|
|
5da7a582bf | ||
|
|
229c46a0c1 | ||
|
|
78d347889b | ||
|
|
244741ba15 | ||
|
|
015fa601a3 | ||
|
|
1bd9e44ecb | ||
|
|
dc7cfdb762 | ||
|
|
36afbae5af | ||
|
|
89d56a10f0 | ||
|
|
8dcc6031e6 | ||
|
|
b524a5424a | ||
|
|
f06ad99658 | ||
|
|
f4ad61a41b | ||
|
|
3c948aca96 | ||
|
|
57ee792198 | ||
|
|
6d84364cec | ||
|
|
d74fc16cfd | ||
|
|
56d72cb30b | ||
|
|
2f6f03ce8f | ||
|
|
b437ff3dcc | ||
|
|
93d17c2c35 | ||
|
|
b70ea04f02 | ||
|
|
89cde8b518 | ||
|
|
8d78623730 | ||
|
|
4a5b05f669 | ||
|
|
c4f2b7ed2e | ||
|
|
e296c77b14 | ||
|
|
56611c6e41 | ||
|
|
1e216b8cb3 | ||
|
|
302700ac80 | ||
|
|
cc5d943a86 | ||
|
|
7942f93649 | ||
|
|
270c6dd64b | ||
|
|
9f1633bc63 | ||
|
|
8953ec0e1f | ||
|
|
8f5dd3bc93 | ||
|
|
97ee6e6ee0 | ||
|
|
ae626294f3 | ||
|
|
842a45aa17 | ||
|
|
686936e7b2 | ||
|
|
8ac8ad6ed9 | ||
|
|
e7eda49676 | ||
|
|
faae8c7a77 | ||
|
|
5cb67a6e09 | ||
|
|
06747dc38d | ||
|
|
c3813fd87b | ||
|
|
901584a2e4 | ||
|
|
f9e33c7fca | ||
|
|
ea36ca37f0 | ||
|
|
2ea06f3096 | ||
|
|
dc208973c8 | ||
|
|
18ddb1001b | ||
|
|
567bc4a112 | ||
|
|
c5dbf5a412 | ||
|
|
c084e66db5 | ||
|
|
bb6d35f91f | ||
|
|
ac59bc2999 | ||
|
|
7dfb1d395d | ||
|
|
46672ca976 | ||
|
|
94e7e23cda | ||
|
|
2fa0ca41d6 | ||
|
|
2ebe35c6d6 | ||
|
|
354066de0b | ||
|
|
3d3c1f8f23 | ||
|
|
24448d604d | ||
|
|
7d8e530ebf | ||
|
|
5db88040cc | ||
|
|
d9de6e78d7 | ||
|
|
da136bfebd | ||
|
|
6657063441 | ||
|
|
47e6d4d711 | ||
|
|
32c7bd2627 | ||
|
|
3422d064ac | ||
|
|
0b01d40a85 | ||
|
|
7f79dc6098 | ||
|
|
ac7590f23c | ||
|
|
998bd3a1a9 | ||
|
|
0f62f1d72c | ||
|
|
cb1b1a5cac | ||
|
|
63ccdd0a23 | ||
|
|
ead809cc90 | ||
|
|
dfd4851bf1 | ||
|
|
e50f11697f | ||
|
|
4800ad53dc | ||
|
|
9349c3f055 | ||
|
|
d7a82ae911 | ||
|
|
b3354fb033 | ||
|
|
9b56b11c9b | ||
|
|
15247c3637 | ||
|
|
4311d3b057 | ||
|
|
076db0637f | ||
|
|
8a7108910d | ||
|
|
be12600094 | ||
|
|
988305100d | ||
|
|
16ae28ee62 | ||
|
|
30aab57bc9 | ||
|
|
a573914668 | ||
|
|
83771cc2e6 | ||
|
|
d8eaba75d2 | ||
|
|
96f917a1ab | ||
|
|
44d81a6bdf | ||
|
|
1b0e8440ae | ||
|
|
e236dbe661 | ||
|
|
2ba7d09c21 | ||
|
|
2b937689bd | ||
|
|
eeb7eb7ad2 | ||
|
|
c8e29211eb | ||
|
|
57cce7ec22 | ||
|
|
78af721250 | ||
|
|
cba6c2dacc | ||
|
|
b4b12cdbdc | ||
|
|
e7c86c95fe | ||
|
|
f28d96e91a | ||
|
|
b25217dd38 | ||
|
|
f60da8665b | ||
|
|
3d8daff050 | ||
|
|
c41f3c21c1 | ||
|
|
8e7897bf76 | ||
|
|
050bf75fdd | ||
|
|
1bbfcd0a7c | ||
|
|
30106d0006 | ||
|
|
16974068d7 | ||
|
|
08d0f67062 | ||
|
|
2edcb5863c | ||
|
|
44b9e49a54 | ||
|
|
0175903f51 | ||
|
|
0ef282f89c | ||
|
|
b4eec7d44c | ||
|
|
2e040a39db | ||
|
|
7e0ee705ff | ||
|
|
361005e242 | ||
|
|
2ea26f7319 | ||
|
|
44c4ca232f | ||
|
|
f7bb45288e | ||
|
|
c99aa41744 | ||
|
|
4eb1cc939c | ||
|
|
d4c51f18f5 | ||
|
|
af89c8bba7 | ||
|
|
2e951ef0db | ||
|
|
80b82366c4 | ||
|
|
3d75eae9c0 | ||
|
|
0bd8d9c4ca | ||
|
|
b3c1132a25 | ||
|
|
bddcb16e8a | ||
|
|
3cb2ba6fa3 | ||
|
|
205c8914ed | ||
|
|
76e0b67599 | ||
|
|
2c617b230a | ||
|
|
72d3268d39 | ||
|
|
a093a77610 | ||
|
|
a9c6443c18 | ||
|
|
757932ed4a | ||
|
|
55aad85bdd | ||
|
|
952d87f858 | ||
|
|
81fbe68e3b | ||
|
|
c82cd15f24 | ||
|
|
9991f7015f | ||
|
|
e94fac0459 | ||
|
|
816833c9f3 | ||
|
|
a202d72cd6 | ||
|
|
0558b0621a | ||
|
|
6e12b398d4 | ||
|
|
cce9aa5e97 | ||
|
|
6020122297 | ||
|
|
bed11116fe | ||
|
|
66cf93ffdd | ||
|
|
8f1ca7dee2 | ||
|
|
b24eac52ae | ||
|
|
5aaa3a00b4 | ||
|
|
0d737228b0 | ||
|
|
f57c862c3c | ||
|
|
ba476533ed | ||
|
|
28b39688ec | ||
|
|
fd8b1a8807 | ||
|
|
bff44b5d74 | ||
|
|
a9ee32d020 | ||
|
|
4fd1864eef | ||
|
|
3775279659 | ||
|
|
5bba633ab1 | ||
|
|
900cc0afe3 | ||
|
|
29fa64825e | ||
|
|
1c59404299 | ||
|
|
563613be6b | ||
|
|
5ae2296400 | ||
|
|
9534ceccd9 | ||
|
|
878845941d | ||
|
|
a5be2bb5b5 | ||
|
|
19c954f972 | ||
|
|
576f9db387 | ||
|
|
28ff02f8cb | ||
|
|
d4a590d292 | ||
|
|
25f928fc8a | ||
|
|
5dd3d927d6 | ||
|
|
77950632f8 | ||
|
|
e85cee752d | ||
|
|
5c48f13a27 | ||
|
|
18b283db0a | ||
|
|
ca7888a537 | ||
|
|
246e90acd1 | ||
|
|
e58e33974a | ||
|
|
5cb07d003c | ||
|
|
3ecedc8fcf | ||
|
|
a88ed5d3a9 | ||
|
|
37fead2076 | ||
|
|
2770c761b2 | ||
|
|
4b28eb6758 | ||
|
|
ba45b44f45 | ||
|
|
f5186cbadd | ||
|
|
807924c51e | ||
|
|
155d849201 | ||
|
|
e9efd2705c | ||
|
|
204e0d041c | ||
|
|
227d75d956 | ||
|
|
d2706c8748 | ||
|
|
2518b8600f | ||
|
|
9300839d59 | ||
|
|
a069d1f057 | ||
|
|
7557733b81 | ||
|
|
59ac3020fe | ||
|
|
f0e4cee613 | ||
|
|
0f395dad9e | ||
|
|
7e59362b5c | ||
|
|
e69492e94f | ||
|
|
2b31b173c0 | ||
|
|
8d3a1ab3a7 | ||
|
|
0349c5b402 | ||
|
|
0b7be8b0c6 | ||
|
|
3a8ca5fb4a | ||
|
|
c88b50ad89 | ||
|
|
3746ba1819 | ||
|
|
945bff3c17 | ||
|
|
d7e318b1f8 | ||
|
|
eb3db198a1 | ||
|
|
b380d32ce1 | ||
|
|
7a6056c95f | ||
|
|
a908a783be | ||
|
|
1ae02885cf | ||
|
|
8ea210e14f | ||
|
|
1ae9960e82 | ||
|
|
bed946988a | ||
|
|
e42632b82d | ||
|
|
5aa8425209 | ||
|
|
b794aa8089 | ||
|
|
e8bb0bffaa | ||
|
|
e1e0661f25 | ||
|
|
7848c83c72 | ||
|
|
2de37ed9c7 | ||
|
|
425b433cd2 | ||
|
|
44e07e3cdb | ||
|
|
689f882104 | ||
|
|
207beaee12 | ||
|
|
bd21b85de7 | ||
|
|
2e7579cac5 | ||
|
|
ec63812af0 | ||
|
|
8dfe1a22c9 | ||
|
|
c0e3f547ec | ||
|
|
dafb1b364c | ||
|
|
7d1b9d8635 | ||
|
|
8041f58ffd | ||
|
|
d018fdbace | ||
|
|
489602ffb3 | ||
|
|
600ba73f5f | ||
|
|
88600ae6df | ||
|
|
5e3bce9a92 | ||
|
|
9b28b7057d | ||
|
|
0a17220446 | ||
|
|
7684b19d13 | ||
|
|
47baf3b662 | ||
|
|
bc5e2b9437 | ||
|
|
cf0ef6d9d0 | ||
|
|
b6f3047f82 | ||
|
|
0a418561d7 | ||
|
|
6da79a41df | ||
|
|
3ddeac6c74 | ||
|
|
98f97c442d | ||
|
|
472d33ba12 | ||
|
|
d906cfcc1e | ||
|
|
0f0d8bdcdb | ||
|
|
005b2485f6 | ||
|
|
ffd90bed70 | ||
|
|
70264a24ab | ||
|
|
ad3a37fde3 | ||
|
|
9dac0eeaac | ||
|
|
8dac393bb4 | ||
|
|
287d1a5575 | ||
|
|
20278e0e53 | ||
|
|
a3c3ec8c5c | ||
|
|
3039b25a66 | ||
|
|
0b7ce92af8 | ||
|
|
e3ec521a1d | ||
|
|
88999263a7 | ||
|
|
9687ccd01d | ||
|
|
ca62502499 | ||
|
|
8b3a68f58e | ||
|
|
08eb35a360 | ||
|
|
dec82b41f2 | ||
|
|
b9ab5c76f7 | ||
|
|
6de0c536e8 | ||
|
|
3f0aca97bf | ||
|
|
ccb51a2603 | ||
|
|
99d9e70921 | ||
|
|
b8b54271dd | ||
|
|
49a1549e5a | ||
|
|
b3f936c194 | ||
|
|
f3b10d4c20 | ||
|
|
94b17037d0 | ||
|
|
83f3846ce6 | ||
|
|
711a751117 | ||
|
|
e013e32792 | ||
|
|
6e64b09256 | ||
|
|
e32d268587 | ||
|
|
05635dec93 | ||
|
|
e694923d33 | ||
|
|
7322683465 | ||
|
|
64688f3512 | ||
|
|
762760af2f | ||
|
|
48e6881401 | ||
|
|
95c958136f | ||
|
|
53a6be0f82 | ||
|
|
0b4d95be1c | ||
|
|
aad3948475 | ||
|
|
093370081e | ||
|
|
33426ceee9 | ||
|
|
5f5700b948 | ||
|
|
802e9abfef | ||
|
|
45149d6547 | ||
|
|
595681a982 | ||
|
|
e1cac723d6 | ||
|
|
683f84f053 | ||
|
|
b6d882a06f | ||
|
|
9d4bfbcb1c | ||
|
|
b563747046 | ||
|
|
1ebd0a430f | ||
|
|
ef4366be4d | ||
|
|
484283fef8 | ||
|
|
b546a48a5f | ||
|
|
51bd64d005 | ||
|
|
2c65d12b92 | ||
|
|
c43a3b5115 | ||
|
|
515778b13d | ||
|
|
bb4b9ce9e7 | ||
|
|
1d6707fecd | ||
|
|
5b2a46ed91 | ||
|
|
24fa97da4c | ||
|
|
b0109c2581 | ||
|
|
20f237ec4c | ||
|
|
6ea535ae29 | ||
|
|
57ca8a3eee | ||
|
|
307ea42a0f | ||
|
|
ac41c04f67 | ||
|
|
b3496e79de | ||
|
|
4f7aa755e3 | ||
|
|
d3af50f009 | ||
|
|
ba77a7b7c0 | ||
|
|
666a0e1537 | ||
|
|
96f4307910 | ||
|
|
81e997eb60 | ||
|
|
7132c6e7c2 | ||
|
|
cb01e92fe2 | ||
|
|
2c9c512d74 | ||
|
|
8d05c9040b | ||
|
|
4e994fb202 | ||
|
|
ab2a778694 | ||
|
|
115375820d | ||
|
|
8cb753916b | ||
|
|
ec3a0cee9b | ||
|
|
13f8f82537 | ||
|
|
54544dd2aa | ||
|
|
ac99115424 | ||
|
|
d6c6038193 | ||
|
|
c97b84e4a2 | ||
|
|
6698ec0b06 | ||
|
|
290b1c4de5 | ||
|
|
d497a50727 | ||
|
|
2837c91f5e | ||
|
|
9a717f28c6 | ||
|
|
e416c2c9c9 | ||
|
|
331cdaa6ce | ||
|
|
aa329b1a9a | ||
|
|
16c080942d | ||
|
|
4acc111b3d | ||
|
|
afe453d371 | ||
|
|
e93c120dac | ||
|
|
2f701458f4 | ||
|
|
6a35961dfb | ||
|
|
1a90095ba3 | ||
|
|
5dc87213b5 | ||
|
|
ff8044b09b | ||
|
|
f1fa40f6fd | ||
|
|
9bc5b62828 | ||
|
|
5aae586812 | ||
|
|
bc5e5be42e | ||
|
|
be68573378 | ||
|
|
cf0bc3d270 | ||
|
|
d5773eb2e9 | ||
|
|
50523e5b67 | ||
|
|
feeca86c6a | ||
|
|
a2e2671483 | ||
|
|
0b4d6ef8ef | ||
|
|
bdf0e532a5 | ||
|
|
d8cf72d09b | ||
|
|
6c3ec8f058 | ||
|
|
fbd9b76a09 | ||
|
|
bee216220a | ||
|
|
4ca7f34989 | ||
|
|
51015e6b51 | ||
|
|
46100d6d94 | ||
|
|
c94eac3863 | ||
|
|
965b0e5b6d | ||
|
|
000cea1dda | ||
|
|
6d4d60e3b1 | ||
|
|
9957282bfa | ||
|
|
9b603dd956 | ||
|
|
759ee55780 | ||
|
|
93b2edb5a5 | ||
|
|
4d49c14bb9 | ||
|
|
6398808a0a | ||
|
|
64e20f3665 | ||
|
|
3b542d8868 | ||
|
|
d5af0e43a5 | ||
|
|
7a8ef87dc7 | ||
|
|
7a5293cde3 | ||
|
|
26400631a0 | ||
|
|
97a5c584be | ||
|
|
0700b6573c | ||
|
|
d8f66a4188 | ||
|
|
c22971cf54 | ||
|
|
9d2dd5dd93 | ||
|
|
c19d512631 | ||
|
|
9efda5fef3 | ||
|
|
a27cdcfdb5 | ||
|
|
5667a07b25 | ||
|
|
7d0bd85466 | ||
|
|
4f269442a6 | ||
|
|
16d182fca2 | ||
|
|
6d1a206cc9 | ||
|
|
d8a7fdbf4f | ||
|
|
516b365ebc | ||
|
|
6da6a5ede9 | ||
|
|
67244f1957 | ||
|
|
0a304e9a03 | ||
|
|
05154af606 | ||
|
|
7454b15ec9 | ||
|
|
38f435f639 | ||
|
|
5f38ec7210 | ||
|
|
ca143bf969 | ||
|
|
402e5c3665 | ||
|
|
290dbcc3ed | ||
|
|
a7749c70e7 | ||
|
|
9f0cfd5f22 | ||
|
|
f47434c09e | ||
|
|
795a4b571e | ||
|
|
f71727e05e | ||
|
|
d2fa8e86b1 | ||
|
|
23dcd9d30f | ||
|
|
d6890c6ecd | ||
|
|
b1a6ba78d4 | ||
|
|
e6abe3f3a1 | ||
|
|
6c32540ecb | ||
|
|
951ad5cd0a | ||
|
|
5f56477123 | ||
|
|
3180f38a1d | ||
|
|
07f15993e5 | ||
|
|
fc8e9c7689 | ||
|
|
7cb20b9e28 | ||
|
|
ffc2351d64 | ||
|
|
f10fec61ac | ||
|
|
e41200067c | ||
|
|
30f6d3dde7 | ||
|
|
7f9dcb11f0 | ||
|
|
d5fa2f62c0 | ||
|
|
fa7967190d | ||
|
|
20e04c0d75 | ||
|
|
d5eea9db20 | ||
|
|
8cb598ce25 | ||
|
|
f3a57d7f23 | ||
|
|
14e47def9d | ||
|
|
7573081d7a | ||
|
|
aaca581324 | ||
|
|
43c2797a51 | ||
|
|
da6bbf65f0 | ||
|
|
7d0cbf423e | ||
|
|
d94eab71da | ||
|
|
b0a8ccc5b6 | ||
|
|
5cbf1f1568 | ||
|
|
22cde74db2 | ||
|
|
cdc3759c04 | ||
|
|
38abc8a167 | ||
|
|
7c7c7086ce | ||
|
|
50daf64025 | ||
|
|
dc38df5389 | ||
|
|
a46b2c9d98 | ||
|
|
200756361c | ||
|
|
877a1e4cd0 | ||
|
|
a382206f5a | ||
|
|
1af7a622a6 | ||
|
|
3f95b2aa7e | ||
|
|
b7871e2e04 | ||
|
|
6b6ceda497 | ||
|
|
b01b8e4bc8 | ||
|
|
20f7cb1cd0 | ||
|
|
3e1ee0056d | ||
|
|
a3a851e57f | ||
|
|
cf650d4318 | ||
|
|
58e7420f04 | ||
|
|
aedaf084d9 | ||
|
|
c18f638989 | ||
|
|
fb50cdc058 | ||
|
|
68e702c344 | ||
|
|
651b18a8b3 | ||
|
|
2174e56407 | ||
|
|
ea27a4da02 | ||
|
|
321fd1162b | ||
|
|
c51f38ddc7 | ||
|
|
b34cd1acc5 | ||
|
|
43f20bb83a | ||
|
|
79b8ddef15 | ||
|
|
2642ea0601 | ||
|
|
7a2f934f6a | ||
|
|
f299fefcdc | ||
|
|
209319f460 | ||
|
|
5ec4f4dcea | ||
|
|
16f67d58d1 | ||
|
|
542d794ab4 | ||
|
|
ede1573b56 | ||
|
|
bc8eec8887 | ||
|
|
59b0654b95 | ||
|
|
cae3477591 | ||
|
|
6a3115b80a | ||
|
|
1232d7c288 | ||
|
|
8e1b4c9271 | ||
|
|
0f8810e22a | ||
|
|
7bbff38b6b | ||
|
|
4a7b8fa250 | ||
|
|
53acc94976 | ||
|
|
ab8d83b12d | ||
|
|
713a0a6174 | ||
|
|
a3b9695b81 | ||
|
|
85dd381bd9 | ||
|
|
05b418e9b6 | ||
|
|
40af584afe | ||
|
|
93fe32491b | ||
|
|
4b67fcb0d4 | ||
|
|
0b0c92717d | ||
|
|
4564cafa85 | ||
|
|
2e21f8d03a | ||
|
|
4336254d56 | ||
|
|
bc5e9279a5 | ||
|
|
1bfbb956c2 | ||
|
|
b336db773c | ||
|
|
30e7d2424c | ||
|
|
cbb8c0234d | ||
|
|
95469107fd | ||
|
|
ba0058c0b9 | ||
|
|
8f35afe353 | ||
|
|
aaf219eb90 | ||
|
|
1167794d58 | ||
|
|
929babd69a | ||
|
|
870b7c1ffd | ||
|
|
9ed9d835b9 | ||
|
|
985feb4b91 | ||
|
|
c43c85bb21 | ||
|
|
038074131c | ||
|
|
ab9156a7d3 | ||
|
|
f4b76818e8 | ||
|
|
12a399a354 | ||
|
|
dfdbbea85c | ||
|
|
5fad5ec4c7 | ||
|
|
fe94da4727 | ||
|
|
361ae3af50 | ||
|
|
a2e302ebfa | ||
|
|
186ab9c00e | ||
|
|
9a2453524a | ||
|
|
3a2e128e98 | ||
|
|
1bd2dbf9f0 | ||
|
|
b7f880e7f8 | ||
|
|
13cd926b4f | ||
|
|
6fb7b1c6e4 | ||
|
|
8f35015a6c | ||
|
|
a926bccb97 | ||
|
|
b9f2abf5eb | ||
|
|
8dc5bda171 | ||
|
|
d24bdb543b | ||
|
|
dc921161e4 | ||
|
|
8e248cb545 | ||
|
|
ae93be4cfb | ||
|
|
42949fbe91 | ||
|
|
21dd175456 | ||
|
|
a3df0da26b | ||
|
|
6b9d4fd107 | ||
|
|
fac2ae6399 | ||
|
|
46732803c3 | ||
|
|
39d205bd4d | ||
|
|
8e2bd7997e | ||
|
|
76db9a6c62 | ||
|
|
c03d5e891d | ||
|
|
b21f6e8ce7 | ||
|
|
4b916f1888 | ||
|
|
1f80e5339b | ||
|
|
dc616f8ea1 | ||
|
|
dc6588243f | ||
|
|
690d3b0d4e | ||
|
|
4bc5dffabb | ||
|
|
55a1e4a544 | ||
|
|
5cb32dd75a | ||
|
|
f858c9fe48 | ||
|
|
0ac39af404 | ||
|
|
fc757b236f | ||
|
|
a921e6e3d4 | ||
|
|
8cf0f50565 | ||
|
|
575ad7664f | ||
|
|
be17488070 | ||
|
|
f504107928 | ||
|
|
0113f549c5 | ||
|
|
9ff831faab | ||
|
|
a9ca243c0b | ||
|
|
553bbef45f | ||
|
|
8f713f861b | ||
|
|
324054b9d7 | ||
|
|
d3fc9404ee | ||
|
|
5bb780439b | ||
|
|
f0e6008441 | ||
|
|
39dd69f15e | ||
|
|
7b2e89df26 | ||
|
|
aa853f8481 | ||
|
|
83bba75af6 | ||
|
|
23889aa5b1 | ||
|
|
5e6094fc42 | ||
|
|
a4181cb6d6 | ||
|
|
88c49f0722 | ||
|
|
513d9e199c | ||
|
|
e80975c56e | ||
|
|
9051ac102c | ||
|
|
39a9b2e619 | ||
|
|
dbc55d248f | ||
|
|
2b46180bfb | ||
|
|
5abe6c7e27 | ||
|
|
4595ebeb9a | ||
|
|
815c08e6d4 | ||
|
|
a72ad218a0 | ||
|
|
944ad1f769 | ||
|
|
e17a68f61c | ||
|
|
0b8196be68 | ||
|
|
d9c01148b7 | ||
|
|
2b3cfd992f | ||
|
|
7e4bd7a6f3 | ||
|
|
161e0ed637 | ||
|
|
1ee03e863c | ||
|
|
2411481d8b | ||
|
|
0314a627ed | ||
|
|
4d339a0b09 | ||
|
|
c171e7f94b | ||
|
|
eac4d966d9 | ||
|
|
c20868c20c | ||
|
|
bb74d46f1f | ||
|
|
152d48c583 | ||
|
|
b7a0155ba4 | ||
|
|
0414724b21 | ||
|
|
469f21db8d | ||
|
|
b6c9d30195 | ||
|
|
7b89430317 | ||
|
|
5bd854116a | ||
|
|
3fb7575529 | ||
|
|
a3a868c2ce | ||
|
|
1eb6fc2235 | ||
|
|
5ff4502f0a | ||
|
|
3126b8d4b0 | ||
|
|
dfb1d34b8a | ||
|
|
ace62c07be | ||
|
|
ec2f3024b6 | ||
|
|
25543f6561 | ||
|
|
3278657da7 | ||
|
|
90c4d12688 | ||
|
|
4ceaea99f7 | ||
|
|
585de34ef1 | ||
|
|
60e1a3ce93 | ||
|
|
413d77ff1a | ||
|
|
d8bb561063 | ||
|
|
5dff2c20b2 | ||
|
|
27803e7787 | ||
|
|
58f9c1575f | ||
|
|
7b909665b9 | ||
|
|
11eee991a1 | ||
|
|
7056a859ef | ||
|
|
2455f0d73e | ||
|
|
943d36e23e | ||
|
|
b563ac6e0c | ||
|
|
ed37a8ca2a | ||
|
|
55bee8b0d8 | ||
|
|
6bd18c29e3 | ||
|
|
37fc611767 | ||
|
|
f95fe351e2 | ||
|
|
2fb0ab1d91 | ||
|
|
85f7be1d79 | ||
|
|
b3e5c1abc2 | ||
|
|
9e216c0020 | ||
|
|
a9f00b6a8c | ||
|
|
46e7b02da2 | ||
|
|
8db0bc6e6f | ||
|
|
1702b5867a | ||
|
|
c03a6452ef | ||
|
|
69b840fb12 | ||
|
|
9385ab287f | ||
|
|
d2b2c39b97 | ||
|
|
f8ff978738 | ||
|
|
d50b32236c | ||
|
|
88d1db2840 | ||
|
|
b1dbe562d6 | ||
|
|
e95b6f1b5d | ||
|
|
1cbb7bb5dd | ||
|
|
34f3ffa129 | ||
|
|
ecd25a673e | ||
|
|
58d0778c3d | ||
|
|
81a5422974 | ||
|
|
eb859e5ede | ||
|
|
c8035b5071 | ||
|
|
a98a336e52 | ||
|
|
d7f6535b85 | ||
|
|
f9d4e3152b | ||
|
|
69bd816aeb | ||
|
|
783810749c | ||
|
|
92c3c967ba | ||
|
|
c632ba4306 | ||
|
|
e29d2c25ba | ||
|
|
5cfdc96cd2 | ||
|
|
a20e10b342 | ||
|
|
39b7414553 | ||
|
|
0eef20caf6 |
157
.github/workflows/Build_mR-NB.yml
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
name: Build_and_Release_mR-NB
|
||||
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:
|
||||
- runner: windows-latest
|
||||
platform: x64
|
||||
arch: x64
|
||||
- runner: windows-11-arm
|
||||
platform: ARM64
|
||||
arch: arm64
|
||||
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@v5
|
||||
|
||||
- name: (02) Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
with:
|
||||
vs-version: '17.14.12'
|
||||
|
||||
- name: (03) 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: (04) Cache NuGet Packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: ${{ runner.os }}-${{ matrix.arch }}-nuget-${{ hashFiles('**/*.csproj') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.arch }}-nuget-
|
||||
|
||||
- name: (05) Restore NuGet Packages
|
||||
shell: pwsh
|
||||
run: dotnet restore
|
||||
|
||||
- name: (06) Build Release
|
||||
shell: pwsh
|
||||
run: |
|
||||
msbuild "$Env:GITHUB_WORKSPACE\mRemoteNG.sln" -p:Configuration="Release" -p:Platform=${{ matrix.platform }} /verbosity:minimal
|
||||
|
||||
- name: (07) 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 }}.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
|
||||
$version = "${{ steps.version.outputs.version }}"
|
||||
|
||||
- name: (08) 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
|
||||
|
||||
echo "log=$escaped"
|
||||
|
||||
- name: (09) 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 }}"
|
||||
|
||||
- name: (10) Create release
|
||||
id: create_release
|
||||
uses: softprops/action-gh-release@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
|
||||
with:
|
||||
tag_name: ${{ steps.version.outputs.tag }}
|
||||
name: "mRemoteNG ${{ steps.version.outputs.version }} NB ${{ steps.version.outputs.build }}"
|
||||
files: ${{ steps.version.outputs.zipname }}
|
||||
body: |
|
||||
Changes in this Release:
|
||||
${{ steps.changelog.outputs.log }}
|
||||
|
||||
Last Commit Message:
|
||||
${{ steps.version.outputs.message }}
|
||||
|
||||
draft: false
|
||||
prerelease: true
|
||||
132
.github/workflows/add_PR_2_chlog.yml
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
name: Update Changelog After Renovate PR Merge
|
||||
|
||||
on:
|
||||
# 1) Auto on pushes to your repo’s default branch (merge commits included)
|
||||
push:
|
||||
branches:
|
||||
- v1.78.2-dev
|
||||
|
||||
# 2) Auto when a PR is closed (so you can merge manually via the UI)
|
||||
pull_request_target:
|
||||
types: [closed]
|
||||
|
||||
# 3) 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 closed PR that was merged by you
|
||||
# - OR a push to default branch
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'pull_request_target' && github.event.pull_request.merged == true && github.actor == 'Dimitrij') ||
|
||||
github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- 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
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
| <b>Date added</b> | <b>Author</b> | <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.
|
||||
50
CHANGELOG.md
@@ -2,7 +2,57 @@
|
||||
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
|
||||
- #2715: Disable WinForms analyzers and suppress WFO1000 build errors for ObjectListView
|
||||
- #2712: VNCEvent_Disconnected send the ProtocolBase based object reference
|
||||
- #2668: fix ssh quickconnect exception
|
||||
- #2611: correct registry path
|
||||
- #2496: use pwfile instead of cleartext password for putty connections
|
||||
- #2734: fix native build for Windows-x64
|
||||
|
||||
### Added
|
||||
- #2728 Add support for building mRemoteNG on Windows ARM64
|
||||
- #2723: Read keyboardhook, gatewayaccesstoken and gatewaycredentialssource from RDP File
|
||||
- #2690: தமிழ் (ta) Translation update
|
||||
- #2643: Registry Settings: enhancements and new settings implementation
|
||||
- #2591: add Clickstudios Passwordstate API connector
|
||||
|
||||
### Updated
|
||||
- #2597: Remember the opened connection file on relaunch
|
||||
- #2502: Updated Polish translation
|
||||
|
||||
### Dependency update
|
||||
|
||||
## [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
|
||||
- #1546: Enable resize without reconnect for RDP Version Rdc9 or higher
|
||||
|
||||
## [1.77.2]
|
||||
### Added
|
||||
- #2086: Replace WebClient with async HttpClient for updater.
|
||||
|
||||
106
Directory.Packages.props
Normal file
@@ -0,0 +1,106 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
||||
<NoWarn>$(NoWarn);NU1507</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="AWSSDK.Core" Version="4.0.0.27" />
|
||||
<PackageVersion Include="AWSSDK.EC2" Version="4.0.37" />
|
||||
<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="6.0.4" />
|
||||
<PackageVersion Include="Cucumber.Messages" Version="29.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="35.0.0" />
|
||||
<PackageVersion Include="Google.Protobuf" Version="3.32.1" />
|
||||
<PackageVersion Include="LiteDB" Version="5.0.21" />
|
||||
<PackageVersion Include="log4net" Version="3.2.0" />
|
||||
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.1.1" />
|
||||
<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="9.0.9" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="9.0.9" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.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.3485.44" />
|
||||
<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.4.0" />
|
||||
<PackageVersion Include="NETStandard.Library" Version="2.0.3" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<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.20.1" />
|
||||
<PackageVersion Include="NUnit.ConsoleRunner" Version="3.20.1" />
|
||||
<PackageVersion Include="NUnit.Extension.TeamCityEventListener" Version="1.0.10" />
|
||||
<PackageVersion Include="NUnit.Runners" Version="3.12.0" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="5.1.0" />
|
||||
<PackageVersion Include="OpenCover" Version="4.7.1221" />
|
||||
<PackageVersion Include="Renci.SshNet.Async" Version="1.4.0" />
|
||||
<PackageVersion Include="ReportGenerator" Version="5.4.13" />
|
||||
<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="SpecFlow" Version="3.9.74" />
|
||||
<PackageVersion Include="SpecFlow.NUnit" Version="3.9.74" />
|
||||
<PackageVersion Include="SSH.NET" Version="2025.0.0" />
|
||||
<PackageVersion Include="System.Buffers" Version="4.6.1" />
|
||||
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="9.0.9" />
|
||||
<PackageVersion Include="System.Collections.Immutable" Version="9.0.9" />
|
||||
<PackageVersion Include="System.Console" Version="4.3.1" />
|
||||
<PackageVersion Include="System.Data.Common" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="9.0.9" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="9.0.9" />
|
||||
<PackageVersion Include="System.Diagnostics.EventLog" Version="9.0.9" />
|
||||
<PackageVersion Include="System.DirectoryServices" Version="9.0.9" />
|
||||
<PackageVersion Include="System.Dynamic.Runtime" Version="4.3.0" />
|
||||
<PackageVersion Include="System.IO.Pipelines" Version="9.0.9" />
|
||||
<PackageVersion Include="System.Formats.Asn1" Version="9.0.9" />
|
||||
<PackageVersion Include="System.Management" Version="9.0.9" />
|
||||
<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="9.0.9" />
|
||||
<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="9.0.9" />
|
||||
<PackageVersion Include="System.Security.Cryptography.X509Certificates" Version="4.3.2" />
|
||||
<PackageVersion Include="System.Security.Permissions" Version="9.0.9" />
|
||||
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.9" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.9" />
|
||||
<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="9.0.9" />
|
||||
<PackageVersion Include="System.Xml.ReaderWriter" Version="4.3.1" />
|
||||
<PackageVersion Include="VncSharpCore" Version="1.2.1" />
|
||||
<PackageVersion Include="ZstdSharp.Port" Version="0.8.6" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
BIN
ExternalConnectors/CPS/CPS.ico
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
241
ExternalConnectors/CPS/CPSConnectionForm.Designer.cs
generated
Normal 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;
|
||||
}
|
||||
}
|
||||
41
ExternalConnectors/CPS/CPSConnectionForm.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
149
ExternalConnectors/CPS/CPSConnectionForm.resx
Normal 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>
|
||||
295
ExternalConnectors/CPS/PasswordstateInterface.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
@@ -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);
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace ExternalConnectors.TSS
|
||||
namespace ExternalConnectors.DSS
|
||||
{
|
||||
public partial class SSConnectionForm : Form
|
||||
{
|
||||
@@ -13,7 +13,12 @@
|
||||
if (cbUseSSO.Checked)
|
||||
btnOK.Focus();
|
||||
else
|
||||
tbPassword.Focus();
|
||||
{
|
||||
if (tbPassword.Text.Length == 0)
|
||||
tbPassword.Focus();
|
||||
else
|
||||
tbOTP.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
private void cbUseSSO_CheckedChanged(object sender, EventArgs e)
|
||||
@@ -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))")]
|
||||
335
ExternalConnectors/DSS/SecretServerInterface.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<TargetFramework>net9.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 Portable;Deploy to github</Configurations>
|
||||
<SupportedOSPlatformVersion>10.0.26100.0</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Portable|x64'">
|
||||
<Optimize>True</Optimize>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Portable|arm64'">
|
||||
<Optimize>True</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AWSSDK.Core" Version="3.7.6" />
|
||||
<PackageReference Include="AWSSDK.EC2" Version="3.7.55.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="AWSSDK.Core" />
|
||||
<PackageReference Include="AWSSDK.EC2" />
|
||||
<PackageReference Include="BouncyCastle.Cryptography" />
|
||||
<PackageReference Include="Newtonsoft.Json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="AWS\AWSConnectionForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Update="TSS\SSConnectionForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Update="AWS\AWSConnectionForm.cs" />
|
||||
<Compile Update="CPS\CPSConnectionForm.cs" />
|
||||
<Compile Update="DSS\SSConnectionForm.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
111
ExternalConnectors/PuttyKeyFileGenerator.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -1,237 +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 (ssPassword != "" || 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 ?? "";
|
||||
|
||||
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;
|
||||
initdone = 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)
|
||||
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()
|
||||
{
|
||||
|
||||
string authUsername = SSConnectionData.ssUsername;
|
||||
string authPassword = SSConnectionData.ssPassword;
|
||||
string baseURL = SSConnectionData.ssUrl;
|
||||
string OTP = SSConnectionData.ssOTP;
|
||||
string Bearer = SSConnectionData.ssTokenBearer;
|
||||
string Refresh = SSConnectionData.ssTokenRefresh;
|
||||
DateTime ExpiresOn = SSConnectionData.ssTokenExpiresOn;
|
||||
|
||||
|
||||
//Check if current token is valid
|
||||
if (!String.IsNullOrEmpty(Bearer))
|
||||
{
|
||||
if (ExpiresOn >= DateTime.UtcNow)
|
||||
{
|
||||
return Bearer;
|
||||
}
|
||||
else
|
||||
{
|
||||
//try using refresh token
|
||||
using (var httpClient = new HttpClient())
|
||||
{
|
||||
var tokenClient = new OAuth2ServiceClient(baseURL, httpClient);
|
||||
var token = tokenClient.AuthorizeAsync(Grant_type.Refresh_token, null, null, Refresh, null).Result;
|
||||
var tokenResult = token.Access_token;
|
||||
|
||||
SSConnectionData.ssTokenBearer = tokenResult;
|
||||
return tokenResult;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var httpClient = new HttpClient())
|
||||
{
|
||||
// Authenticate:
|
||||
var tokenClient = new OAuth2ServiceClient(baseURL, httpClient);
|
||||
// call below will throw an exception if the creds are invalid
|
||||
var token = tokenClient.AuthorizeAsync(Grant_type.Password, authUsername, authPassword, null, OTP).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);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
ObjectListView/AssemblyInfo.Version.cs
Normal 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)]
|
||||
520
ObjectListView/CellEditing/CellEditKeyEngine.cs
Normal 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
|
||||
}
|
||||
}
|
||||
284
ObjectListView/CellEditing/CellEditors.cs
Normal 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); }
|
||||
}
|
||||
}
|
||||
}
|
||||
213
ObjectListView/CellEditing/EditorRegistry.cs
Normal 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
|
||||
}
|
||||
}
|
||||
46
ObjectListView/CustomDictionary.xml
Normal 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>
|
||||
236
ObjectListView/DataListView.cs
Normal 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
|
||||
}
|
||||
}
|
||||
240
ObjectListView/DataTreeListView.cs
Normal 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
|
||||
}
|
||||
}
|
||||
219
ObjectListView/DragDrop/DragSource.cs
Normal 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
|
||||
}
|
||||
}
|
||||
1454
ObjectListView/DragDrop/DropSink.cs
Normal file
185
ObjectListView/DragDrop/OLVDataObject.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
165
ObjectListView/FastDataListView.cs
Normal 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
|
||||
}
|
||||
}
|
||||
419
ObjectListView/FastObjectListView.cs
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
125
ObjectListView/Filtering/Cluster.cs
Normal 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
|
||||
}
|
||||
}
|
||||
189
ObjectListView/Filtering/ClusteringStrategy.cs
Normal 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
|
||||
}
|
||||
}
|
||||
70
ObjectListView/Filtering/ClustersFromGroupsStrategy.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
187
ObjectListView/Filtering/DateTimeClusteringStrategy.cs
Normal 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
|
||||
}
|
||||
}
|
||||
369
ObjectListView/Filtering/FilterMenuBuilder.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
481
ObjectListView/Filtering/Filters.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
160
ObjectListView/Filtering/FlagClusteringStrategy.cs
Normal 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
|
||||
}
|
||||
}
|
||||
56
ObjectListView/Filtering/ICluster.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
80
ObjectListView/Filtering/IClusteringStrategy.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
629
ObjectListView/Filtering/TextMatchFilter.cs
Normal 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
|
||||
}
|
||||
}
|
||||
1261
ObjectListView/FullClassDiagram.cd
Normal file
335
ObjectListView/Implementation/Attributes.cs
Normal 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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
315
ObjectListView/Implementation/Comparers.cs
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
630
ObjectListView/Implementation/DataSourceAdapter.cs
Normal 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
|
||||
}
|
||||
}
|
||||
168
ObjectListView/Implementation/Delegates.cs
Normal 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
|
||||
}
|
||||
407
ObjectListView/Implementation/DragSource.cs
Normal 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);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
1398
ObjectListView/Implementation/DropSink.cs
Normal file
104
ObjectListView/Implementation/Enums.cs
Normal 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
|
||||
*
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
2425
ObjectListView/Implementation/Events.cs
Normal file
175
ObjectListView/Implementation/GroupingParameters.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
747
ObjectListView/Implementation/Groups.cs
Normal 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
|
||||
|
||||
}
|
||||
}
|
||||
559
ObjectListView/Implementation/Munger.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
1226
ObjectListView/Implementation/NativeMethods.cs
Normal file
87
ObjectListView/Implementation/NullableDictionary.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
321
ObjectListView/Implementation/OLVListItem.cs
Normal 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
|
||||
}
|
||||
}
|
||||
173
ObjectListView/Implementation/OLVListSubItem.cs
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
388
ObjectListView/Implementation/OlvListViewHitTestInfo.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
262
ObjectListView/Implementation/TreeDataSourceAdapter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
352
ObjectListView/Implementation/VirtualGroups.cs
Normal 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
|
||||
}
|
||||
}
|
||||
343
ObjectListView/Implementation/VirtualListDataSource.cs
Normal 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
536
ObjectListView/ObjectListView.DesignTime.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
ObjectListView/ObjectListView.NetCore.csproj
Normal file
@@ -0,0 +1,42 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.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>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Drawing.Common" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
11861
ObjectListView/ObjectListView.cs
Normal file
37
ObjectListView/Package.nuspec
Normal 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>
|
||||
22
ObjectListView/Properties/AssemblyInfo.cs
Normal 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")]
|
||||
113
ObjectListView/Properties/Resources.Designer.cs
generated
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
137
ObjectListView/Properties/Resources.resx
Normal 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>
|
||||
743
ObjectListView/Rendering/Adornments.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
820
ObjectListView/Rendering/Decorations.cs
Normal 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
|
||||
}
|
||||
}
|
||||
302
ObjectListView/Rendering/Overlays.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
3832
ObjectListView/Rendering/Renderers.cs
Normal file
400
ObjectListView/Rendering/Styles.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
309
ObjectListView/Rendering/TreeRenderer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
ObjectListView/Resources/clear-filter.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
ObjectListView/Resources/coffee.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
ObjectListView/Resources/filter-icons3.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
ObjectListView/Resources/filter.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
ObjectListView/Resources/sort-ascending.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
ObjectListView/Resources/sort-descending.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
459
ObjectListView/SubControls/GlassPanelForm.cs
Normal 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
|
||||
|
||||
}
|
||||
}
|
||||
1336
ObjectListView/SubControls/HeaderControl.cs
Normal file
189
ObjectListView/SubControls/ToolStripCheckedListBox.cs
Normal 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
|
||||
}
|
||||
}
|
||||
701
ObjectListView/SubControls/ToolTipControl.cs
Normal file
@@ -0,0 +1,701 @@
|
||||
/*
|
||||
* 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>
|
||||
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
2204
ObjectListView/TreeListView.cs
Normal file
190
ObjectListView/Utilities/ColumnSelectionForm.Designer.cs
generated
Normal 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;
|
||||
}
|
||||
}
|
||||
259
ObjectListView/Utilities/ColumnSelectionForm.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
120
ObjectListView/Utilities/ColumnSelectionForm.resx
Normal 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=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>
|
||||
</root>
|
||||
563
ObjectListView/Utilities/Generator.cs
Normal file
@@ -0,0 +1,563 @@
|
||||
/*
|
||||
* Generator - Utility methods that generate columns or methods
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 15/08/2009 22:37
|
||||
*
|
||||
* Change log:
|
||||
* 2015-06-17 JPP - Columns without [OLVColumn] now auto size
|
||||
* 2012-08-16 JPP - Generator now considers [OLVChildren] and [OLVIgnore] attributes.
|
||||
* 2012-06-14 JPP - Allow columns to be generated even if they are not marked with [OLVColumn]
|
||||
* - Converted class from static to instance to allow it to be subclassed.
|
||||
* Also, added IGenerator to allow it to be completely reimplemented.
|
||||
* v2.5.1
|
||||
* 2010-11-01 JPP - DisplayIndex is now set correctly for columns that lack that attribute
|
||||
* v2.4.1
|
||||
* 2010-08-25 JPP - Generator now also resets sort columns
|
||||
* v2.4
|
||||
* 2010-04-14 JPP - Allow Name property to be set
|
||||
* - Don't double set the Text property
|
||||
* 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;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// An object that implements the IGenerator interface provides the ability
|
||||
/// to dynamically create columns
|
||||
/// for an ObjectListView based on the characteristics of a given collection
|
||||
/// of model objects.
|
||||
/// </summary>
|
||||
public interface IGenerator {
|
||||
/// <summary>
|
||||
/// Generate columns into the given ObjectListView that come from the given
|
||||
/// model object type.
|
||||
/// </summary>
|
||||
/// <param name="olv">The ObjectListView to modify</param>
|
||||
/// <param name="type">The model type whose attributes will be considered.</param>
|
||||
/// <param name="allProperties">Will columns be generated for properties that are not marked with [OLVColumn].</param>
|
||||
void GenerateAndReplaceColumns(ObjectListView olv, Type type, bool allProperties);
|
||||
|
||||
/// <summary>
|
||||
/// Generate a list of OLVColumns based on the attributes of the given type
|
||||
/// If allProperties to true, all public properties will have a matching column generated.
|
||||
/// If allProperties is false, only properties that have a OLVColumn attribute will have a column generated.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="allProperties">Will columns be generated for properties that are not marked with [OLVColumn].</param>
|
||||
/// <returns>A collection of OLVColumns matching the attributes of Type that have OLVColumnAttributes.</returns>
|
||||
IList<OLVColumn> GenerateColumns(Type type, bool allProperties);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Generator class provides methods to dynamically create columns
|
||||
/// for an ObjectListView based on the characteristics of a given collection
|
||||
/// of model objects.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>For a given type, a Generator can create columns to match the public properties
|
||||
/// of that type. The generator can consider all public properties or only those public properties marked with
|
||||
/// [OLVColumn] attribute.</para>
|
||||
/// </remarks>
|
||||
public class Generator : IGenerator {
|
||||
#region Static convenience methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the actual generator used by the static convinence methods.
|
||||
/// </summary>
|
||||
/// <remarks>If you subclass the standard generator or implement IGenerator yourself,
|
||||
/// you should install an instance of your subclass/implementation here.</remarks>
|
||||
public static IGenerator Instance {
|
||||
get { return Generator.instance ?? (Generator.instance = new Generator()); }
|
||||
set { Generator.instance = value; }
|
||||
}
|
||||
private static IGenerator instance;
|
||||
|
||||
/// <summary>
|
||||
/// Replace all columns of the given ObjectListView with columns generated
|
||||
/// from the first member of the given enumerable. If the enumerable is
|
||||
/// empty or null, the ObjectListView will be cleared.
|
||||
/// </summary>
|
||||
/// <param name="olv">The ObjectListView to modify</param>
|
||||
/// <param name="enumerable">The collection whose first element will be used to generate columns.</param>
|
||||
static public void GenerateColumns(ObjectListView olv, IEnumerable enumerable) {
|
||||
Generator.GenerateColumns(olv, enumerable, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace all columns of the given ObjectListView with columns generated
|
||||
/// from the first member of the given enumerable. If the enumerable is
|
||||
/// empty or null, the ObjectListView will be cleared.
|
||||
/// </summary>
|
||||
/// <param name="olv">The ObjectListView to modify</param>
|
||||
/// <param name="enumerable">The collection whose first element will be used to generate columns.</param>
|
||||
/// <param name="allProperties">Will columns be generated for properties that are not marked with [OLVColumn].</param>
|
||||
static public void GenerateColumns(ObjectListView olv, IEnumerable enumerable, bool allProperties) {
|
||||
// Generate columns based on the type of the first model in the collection and then quit
|
||||
if (enumerable != null) {
|
||||
foreach (object model in enumerable) {
|
||||
Generator.Instance.GenerateAndReplaceColumns(olv, model.GetType(), allProperties);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If we reach here, the collection was empty, so we clear the list
|
||||
Generator.Instance.GenerateAndReplaceColumns(olv, null, allProperties);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate columns into the given ObjectListView that come from the public properties of the given
|
||||
/// model object type.
|
||||
/// </summary>
|
||||
/// <param name="olv">The ObjectListView to modify</param>
|
||||
/// <param name="type">The model type whose attributes will be considered.</param>
|
||||
static public void GenerateColumns(ObjectListView olv, Type type) {
|
||||
Generator.Instance.GenerateAndReplaceColumns(olv, type, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate columns into the given ObjectListView that come from the public properties of the given
|
||||
/// model object type.
|
||||
/// </summary>
|
||||
/// <param name="olv">The ObjectListView to modify</param>
|
||||
/// <param name="type">The model type whose attributes will be considered.</param>
|
||||
/// <param name="allProperties">Will columns be generated for properties that are not marked with [OLVColumn].</param>
|
||||
static public void GenerateColumns(ObjectListView olv, Type type, bool allProperties) {
|
||||
Generator.Instance.GenerateAndReplaceColumns(olv, type, allProperties);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a list of OLVColumns based on the public properties of the given type
|
||||
/// that have a OLVColumn attribute.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns>A collection of OLVColumns matching the attributes of Type that have OLVColumnAttributes.</returns>
|
||||
static public IList<OLVColumn> GenerateColumns(Type type) {
|
||||
return Generator.Instance.GenerateColumns(type, false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public interface
|
||||
|
||||
/// <summary>
|
||||
/// Generate columns into the given ObjectListView that come from the given
|
||||
/// model object type.
|
||||
/// </summary>
|
||||
/// <param name="olv">The ObjectListView to modify</param>
|
||||
/// <param name="type">The model type whose attributes will be considered.</param>
|
||||
/// <param name="allProperties">Will columns be generated for properties that are not marked with [OLVColumn].</param>
|
||||
public virtual void GenerateAndReplaceColumns(ObjectListView olv, Type type, bool allProperties) {
|
||||
IList<OLVColumn> columns = this.GenerateColumns(type, allProperties);
|
||||
TreeListView tlv = olv as TreeListView;
|
||||
if (tlv != null)
|
||||
this.TryGenerateChildrenDelegates(tlv, type);
|
||||
this.ReplaceColumns(olv, columns);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a list of OLVColumns based on the attributes of the given type
|
||||
/// If allProperties to true, all public properties will have a matching column generated.
|
||||
/// If allProperties is false, only properties that have a OLVColumn attribute will have a column generated.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="allProperties">Will columns be generated for properties that are not marked with [OLVColumn].</param>
|
||||
/// <returns>A collection of OLVColumns matching the attributes of Type that have OLVColumnAttributes.</returns>
|
||||
public virtual IList<OLVColumn> GenerateColumns(Type type, bool allProperties) {
|
||||
List<OLVColumn> columns = new List<OLVColumn>();
|
||||
|
||||
// Sanity
|
||||
if (type == null)
|
||||
return columns;
|
||||
|
||||
// Iterate all public properties in the class and build columns from those that have
|
||||
// an OLVColumn attribute and that are not ignored.
|
||||
foreach (PropertyInfo pinfo in type.GetProperties()) {
|
||||
if (Attribute.GetCustomAttribute(pinfo, typeof(OLVIgnoreAttribute)) != null)
|
||||
continue;
|
||||
|
||||
OLVColumnAttribute attr = Attribute.GetCustomAttribute(pinfo, typeof(OLVColumnAttribute)) as OLVColumnAttribute;
|
||||
if (attr == null) {
|
||||
if (allProperties)
|
||||
columns.Add(this.MakeColumnFromPropertyInfo(pinfo));
|
||||
} else {
|
||||
columns.Add(this.MakeColumnFromAttribute(pinfo, attr));
|
||||
}
|
||||
}
|
||||
|
||||
// How many columns have DisplayIndex specifically set?
|
||||
int countPositiveDisplayIndex = 0;
|
||||
foreach (OLVColumn col in columns) {
|
||||
if (col.DisplayIndex >= 0)
|
||||
countPositiveDisplayIndex += 1;
|
||||
}
|
||||
|
||||
// Give columns that don't have a DisplayIndex an incremental index
|
||||
int columnIndex = countPositiveDisplayIndex;
|
||||
foreach (OLVColumn col in columns)
|
||||
if (col.DisplayIndex < 0)
|
||||
col.DisplayIndex = (columnIndex++);
|
||||
|
||||
columns.Sort(delegate(OLVColumn x, OLVColumn y) {
|
||||
return x.DisplayIndex.CompareTo(y.DisplayIndex);
|
||||
});
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Replace all the columns in the given listview with the given list of columns.
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <param name="columns"></param>
|
||||
protected virtual void ReplaceColumns(ObjectListView olv, IList<OLVColumn> columns) {
|
||||
olv.Reset();
|
||||
|
||||
// Are there new columns to add?
|
||||
if (columns == null || columns.Count == 0)
|
||||
return;
|
||||
|
||||
// Setup the columns
|
||||
olv.AllColumns.AddRange(columns);
|
||||
this.PostCreateColumns(olv);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Post process columns after creating them and adding them to the AllColumns collection.
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
public virtual void PostCreateColumns(ObjectListView olv) {
|
||||
if (olv.AllColumns.Exists(delegate(OLVColumn x) { return x.CheckBoxes; }))
|
||||
olv.UseSubItemCheckBoxes = true;
|
||||
if (olv.AllColumns.Exists(delegate(OLVColumn x) { return x.Index > 0 && (x.ImageGetter != null || !String.IsNullOrEmpty(x.ImageAspectName)); }))
|
||||
olv.ShowImagesOnSubItems = true;
|
||||
olv.RebuildColumns();
|
||||
olv.AutoSizeColumns();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a column from the given PropertyInfo and OLVColumn attribute
|
||||
/// </summary>
|
||||
/// <param name="pinfo"></param>
|
||||
/// <param name="attr"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual OLVColumn MakeColumnFromAttribute(PropertyInfo pinfo, OLVColumnAttribute attr) {
|
||||
return MakeColumn(pinfo.Name, DisplayNameToColumnTitle(pinfo.Name), pinfo.CanWrite, pinfo.PropertyType, attr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make a column from the given PropertyInfo
|
||||
/// </summary>
|
||||
/// <param name="pinfo"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual OLVColumn MakeColumnFromPropertyInfo(PropertyInfo pinfo) {
|
||||
return MakeColumn(pinfo.Name, DisplayNameToColumnTitle(pinfo.Name), pinfo.CanWrite, pinfo.PropertyType, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make a column from the given PropertyDescriptor
|
||||
/// </summary>
|
||||
/// <param name="pd"></param>
|
||||
/// <returns></returns>
|
||||
public virtual OLVColumn MakeColumnFromPropertyDescriptor(PropertyDescriptor pd) {
|
||||
OLVColumnAttribute attr = pd.Attributes[typeof(OLVColumnAttribute)] as OLVColumnAttribute;
|
||||
return MakeColumn(pd.Name, DisplayNameToColumnTitle(pd.DisplayName), !pd.IsReadOnly, pd.PropertyType, attr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a column with all the given information
|
||||
/// </summary>
|
||||
/// <param name="aspectName"></param>
|
||||
/// <param name="title"></param>
|
||||
/// <param name="editable"></param>
|
||||
/// <param name="propertyType"></param>
|
||||
/// <param name="attr"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual OLVColumn MakeColumn(string aspectName, string title, bool editable, Type propertyType, OLVColumnAttribute attr) {
|
||||
|
||||
OLVColumn column = this.MakeColumn(aspectName, title, attr);
|
||||
column.Name = (attr == null || String.IsNullOrEmpty(attr.Name)) ? aspectName : attr.Name;
|
||||
this.ConfigurePossibleBooleanColumn(column, propertyType);
|
||||
|
||||
if (attr == null) {
|
||||
column.IsEditable = editable;
|
||||
column.Width = -1; // Auto size
|
||||
return column;
|
||||
}
|
||||
|
||||
column.AspectToStringFormat = attr.AspectToStringFormat;
|
||||
if (attr.IsCheckBoxesSet)
|
||||
column.CheckBoxes = attr.CheckBoxes;
|
||||
column.DisplayIndex = attr.DisplayIndex;
|
||||
column.FillsFreeSpace = attr.FillsFreeSpace;
|
||||
if (attr.IsFreeSpaceProportionSet)
|
||||
column.FreeSpaceProportion = attr.FreeSpaceProportion;
|
||||
column.GroupWithItemCountFormat = attr.GroupWithItemCountFormat;
|
||||
column.GroupWithItemCountSingularFormat = attr.GroupWithItemCountSingularFormat;
|
||||
column.Hyperlink = attr.Hyperlink;
|
||||
column.ImageAspectName = attr.ImageAspectName;
|
||||
column.IsEditable = attr.IsEditableSet ? attr.IsEditable : editable;
|
||||
column.IsTileViewColumn = attr.IsTileViewColumn;
|
||||
column.IsVisible = attr.IsVisible;
|
||||
column.MaximumWidth = attr.MaximumWidth;
|
||||
column.MinimumWidth = attr.MinimumWidth;
|
||||
column.Tag = attr.Tag;
|
||||
if (attr.IsTextAlignSet)
|
||||
column.TextAlign = attr.TextAlign;
|
||||
column.ToolTipText = attr.ToolTipText;
|
||||
if (attr.IsTriStateCheckBoxesSet)
|
||||
column.TriStateCheckBoxes = attr.TriStateCheckBoxes;
|
||||
column.UseInitialLetterForGroup = attr.UseInitialLetterForGroup;
|
||||
column.Width = attr.Width;
|
||||
if (attr.GroupCutoffs != null && attr.GroupDescriptions != null)
|
||||
column.MakeGroupies(attr.GroupCutoffs, attr.GroupDescriptions);
|
||||
return column;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a column.
|
||||
/// </summary>
|
||||
/// <param name="aspectName"></param>
|
||||
/// <param name="title"></param>
|
||||
/// <param name="attr"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual OLVColumn MakeColumn(string aspectName, string title, OLVColumnAttribute attr) {
|
||||
string columnTitle = (attr == null || String.IsNullOrEmpty(attr.Title)) ? title : attr.Title;
|
||||
return new OLVColumn(columnTitle, aspectName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a property name to a displayable title.
|
||||
/// </summary>
|
||||
/// <param name="displayName"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual string DisplayNameToColumnTitle(string displayName) {
|
||||
string title = displayName.Replace("_", " ");
|
||||
// Put a space between a lower-case letter that is followed immediately by an upper case letter
|
||||
title = Regex.Replace(title, @"(\p{Ll})(\p{Lu})", @"$1 $2");
|
||||
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(title);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure the given column to show a checkbox if appropriate
|
||||
/// </summary>
|
||||
/// <param name="column"></param>
|
||||
/// <param name="propertyType"></param>
|
||||
protected virtual void ConfigurePossibleBooleanColumn(OLVColumn column, Type propertyType) {
|
||||
if (propertyType != typeof(bool) && propertyType != typeof(bool?) && propertyType != typeof(CheckState))
|
||||
return;
|
||||
|
||||
column.CheckBoxes = true;
|
||||
column.TextAlign = HorizontalAlignment.Center;
|
||||
column.Width = 32;
|
||||
column.TriStateCheckBoxes = (propertyType == typeof(bool?) || propertyType == typeof(CheckState));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If this given type has an property marked with [OLVChildren], make delegates that will
|
||||
/// traverse that property as the children of an instance of the model
|
||||
/// </summary>
|
||||
/// <param name="tlv"></param>
|
||||
/// <param name="type"></param>
|
||||
protected virtual void TryGenerateChildrenDelegates(TreeListView tlv, Type type) {
|
||||
foreach (PropertyInfo pinfo in type.GetProperties()) {
|
||||
OLVChildrenAttribute attr = Attribute.GetCustomAttribute(pinfo, typeof(OLVChildrenAttribute)) as OLVChildrenAttribute;
|
||||
if (attr != null) {
|
||||
this.GenerateChildrenDelegates(tlv, pinfo);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate CanExpand and ChildrenGetter delegates from the given property.
|
||||
/// </summary>
|
||||
/// <param name="tlv"></param>
|
||||
/// <param name="pinfo"></param>
|
||||
protected virtual void GenerateChildrenDelegates(TreeListView tlv, PropertyInfo pinfo) {
|
||||
Munger childrenGetter = new Munger(pinfo.Name);
|
||||
tlv.CanExpandGetter = delegate(object x) {
|
||||
try {
|
||||
IEnumerable result = childrenGetter.GetValueEx(x) as IEnumerable;
|
||||
return !ObjectListView.IsEnumerableEmpty(result);
|
||||
}
|
||||
catch (MungerException ex) {
|
||||
System.Diagnostics.Debug.WriteLine(ex);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
tlv.ChildrenGetter = delegate(object x) {
|
||||
try {
|
||||
return childrenGetter.GetValueEx(x) as IEnumerable;
|
||||
}
|
||||
catch (MungerException ex) {
|
||||
System.Diagnostics.Debug.WriteLine(ex);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
/*
|
||||
#region Dynamic methods
|
||||
|
||||
/// <summary>
|
||||
/// Generate methods so that reflection is not needed.
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <param name="type"></param>
|
||||
public static void GenerateMethods(ObjectListView olv, Type type) {
|
||||
foreach (OLVColumn column in olv.Columns) {
|
||||
GenerateColumnMethods(column, type);
|
||||
}
|
||||
}
|
||||
|
||||
public static void GenerateColumnMethods(OLVColumn column, Type type) {
|
||||
if (column.AspectGetter == null && !String.IsNullOrEmpty(column.AspectName))
|
||||
column.AspectGetter = Generator.GenerateAspectGetter(type, column.AspectName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates an aspect getter method dynamically. The method will execute
|
||||
/// the given dotted chain of selectors against a model object given at runtime.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of model object to be passed to the generated method</param>
|
||||
/// <param name="path">A dotted chain of selectors. Each selector can be the name of a
|
||||
/// field, property or parameter-less method.</param>
|
||||
/// <returns>A typed delegate</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If you have an AspectName of "Owner.Address.Postcode", this will generate
|
||||
/// the equivilent of: <code>this.AspectGetter = delegate (object x) {
|
||||
/// return x.Owner.Address.Postcode;
|
||||
/// }
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private static AspectGetterDelegate GenerateAspectGetter(Type type, string path) {
|
||||
DynamicMethod getter = new DynamicMethod(String.Empty, typeof(Object), new Type[] { type }, type, true);
|
||||
Generator.GenerateIL(type, path, getter.GetILGenerator());
|
||||
return (AspectGetterDelegate)getter.CreateDelegate(typeof(AspectGetterDelegate));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method generates the actual IL for the method.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="il"></param>
|
||||
private static void GenerateIL(Type modelType, string path, ILGenerator il) {
|
||||
// Push our model object onto the stack
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
OpCodes.Castclass
|
||||
// Generate the IL to access each part of the dotted chain
|
||||
Type type = modelType;
|
||||
string[] parts = path.Split('.');
|
||||
for (int i = 0; i < parts.Length; i++) {
|
||||
type = Generator.GeneratePart(il, type, parts[i], (i == parts.Length - 1));
|
||||
if (type == null)
|
||||
break;
|
||||
}
|
||||
|
||||
// If the object to be returned is a value type (e.g. int, bool), it
|
||||
// must be boxed, since the delegate returns an Object
|
||||
if (type != null && type.IsValueType && !modelType.IsValueType)
|
||||
il.Emit(OpCodes.Box, type);
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
private static Type GeneratePart(ILGenerator il, Type type, string pathPart, bool isLastPart) {
|
||||
// TODO: Generate check for null
|
||||
|
||||
// Find the first member with the given nam that is a field, property, or parameter-less method
|
||||
List<MemberInfo> infos = new List<MemberInfo>(type.GetMember(pathPart));
|
||||
MemberInfo info = infos.Find(delegate(MemberInfo x) {
|
||||
if (x.MemberType == MemberTypes.Field || x.MemberType == MemberTypes.Property)
|
||||
return true;
|
||||
if (x.MemberType == MemberTypes.Method)
|
||||
return ((MethodInfo)x).GetParameters().Length == 0;
|
||||
else
|
||||
return false;
|
||||
});
|
||||
|
||||
// If we couldn't find anything with that name, pop the current result and return an error
|
||||
if (info == null) {
|
||||
il.Emit(OpCodes.Pop);
|
||||
il.Emit(OpCodes.Ldstr, String.Format("'{0}' is not a parameter-less method, property or field of type '{1}'", pathPart, type.FullName));
|
||||
return null;
|
||||
}
|
||||
|
||||
// Generate the correct IL to access the member. We remember the type of object that is going to be returned
|
||||
// so that we can do a method lookup on it at the next iteration
|
||||
Type resultType = null;
|
||||
switch (info.MemberType) {
|
||||
case MemberTypes.Method:
|
||||
MethodInfo mi = (MethodInfo)info;
|
||||
if (mi.IsVirtual)
|
||||
il.Emit(OpCodes.Callvirt, mi);
|
||||
else
|
||||
il.Emit(OpCodes.Call, mi);
|
||||
resultType = mi.ReturnType;
|
||||
break;
|
||||
case MemberTypes.Property:
|
||||
PropertyInfo pi = (PropertyInfo)info;
|
||||
il.Emit(OpCodes.Call, pi.GetGetMethod());
|
||||
resultType = pi.PropertyType;
|
||||
break;
|
||||
case MemberTypes.Field:
|
||||
FieldInfo fi = (FieldInfo)info;
|
||||
il.Emit(OpCodes.Ldfld, fi);
|
||||
resultType = fi.FieldType;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the method returned a value type, and something is going to call a method on that value,
|
||||
// we need to load its address onto the stack, rather than the object itself.
|
||||
if (resultType.IsValueType && !isLastPart) {
|
||||
LocalBuilder lb = il.DeclareLocal(resultType);
|
||||
il.Emit(OpCodes.Stloc, lb);
|
||||
il.Emit(OpCodes.Ldloca, lb);
|
||||
}
|
||||
|
||||
return resultType;
|
||||
}
|
||||
|
||||
#endregion
|
||||
*/
|
||||
}
|
||||
}
|
||||
277
ObjectListView/Utilities/OLVExporter.cs
Normal file
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* OLVExporter - Export the contents of an ObjectListView into various text-based formats
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 7 August 2012, 10:35pm
|
||||
*
|
||||
* Change log:
|
||||
* 2012-08-07 JPP Initial code
|
||||
*
|
||||
* 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;
|
||||
using System.Text;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
/// <summary>
|
||||
/// An OLVExporter converts a collection of rows from an ObjectListView
|
||||
/// into a variety of textual formats.
|
||||
/// </summary>
|
||||
public class OLVExporter {
|
||||
|
||||
/// <summary>
|
||||
/// What format will be used for exporting
|
||||
/// </summary>
|
||||
public enum ExportFormat {
|
||||
|
||||
/// <summary>
|
||||
/// Tab separated values, according to http://www.iana.org/assignments/media-types/text/tab-separated-values
|
||||
/// </summary>
|
||||
TabSeparated = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Alias for TabSeparated
|
||||
/// </summary>
|
||||
TSV = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Comma separated values, according to http://www.ietf.org/rfc/rfc4180.txt
|
||||
/// </summary>
|
||||
CSV,
|
||||
|
||||
/// <summary>
|
||||
/// HTML table, according to me
|
||||
/// </summary>
|
||||
HTML
|
||||
}
|
||||
|
||||
#region Life and death
|
||||
|
||||
/// <summary>
|
||||
/// Create an empty exporter
|
||||
/// </summary>
|
||||
public OLVExporter() {}
|
||||
|
||||
/// <summary>
|
||||
/// Create an exporter that will export all the rows of the given ObjectListView
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
public OLVExporter(ObjectListView olv) : this(olv, olv.Objects) {}
|
||||
|
||||
/// <summary>
|
||||
/// Create an exporter that will export all the given rows from the given ObjectListView
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <param name="objectsToExport"></param>
|
||||
public OLVExporter(ObjectListView olv, IEnumerable objectsToExport) {
|
||||
if (olv == null) throw new ArgumentNullException("olv");
|
||||
if (objectsToExport == null) throw new ArgumentNullException("objectsToExport");
|
||||
|
||||
this.ListView = olv;
|
||||
this.ModelObjects = ObjectListView.EnumerableToArray(objectsToExport, true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether hidden columns will also be included in the textual
|
||||
/// representation. If this is false (the default), only visible columns will
|
||||
/// be included.
|
||||
/// </summary>
|
||||
public bool IncludeHiddenColumns {
|
||||
get { return includeHiddenColumns; }
|
||||
set { includeHiddenColumns = value; }
|
||||
}
|
||||
private bool includeHiddenColumns;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether column headers will also be included in the text
|
||||
/// and HTML representation. Default is true.
|
||||
/// </summary>
|
||||
public bool IncludeColumnHeaders {
|
||||
get { return includeColumnHeaders; }
|
||||
set { includeColumnHeaders = value; }
|
||||
}
|
||||
private bool includeColumnHeaders = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ObjectListView that is being used as the source of the data
|
||||
/// to be exported
|
||||
/// </summary>
|
||||
public ObjectListView ListView {
|
||||
get { return objectListView; }
|
||||
set { objectListView = value; }
|
||||
}
|
||||
private ObjectListView objectListView;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the model objects that are to be placed in the data object
|
||||
/// </summary>
|
||||
public IList ModelObjects {
|
||||
get { return modelObjects; }
|
||||
set { modelObjects = value; }
|
||||
}
|
||||
private IList modelObjects = new ArrayList();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Commands
|
||||
|
||||
/// <summary>
|
||||
/// Export the nominated rows from the nominated ObjectListView.
|
||||
/// Returns the result in the expected format.
|
||||
/// </summary>
|
||||
/// <param name="format"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>This will perform only one conversion, even if called multiple times with different formats.</remarks>
|
||||
public string ExportTo(ExportFormat format) {
|
||||
if (results == null)
|
||||
this.Convert();
|
||||
|
||||
return results[format];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert
|
||||
/// </summary>
|
||||
public void Convert() {
|
||||
|
||||
IList<OLVColumn> columns = this.IncludeHiddenColumns ? this.ListView.AllColumns : this.ListView.ColumnsInDisplayOrder;
|
||||
|
||||
StringBuilder sbText = new StringBuilder();
|
||||
StringBuilder sbCsv = new StringBuilder();
|
||||
StringBuilder sbHtml = new StringBuilder("<table>");
|
||||
|
||||
// Include column headers
|
||||
if (this.IncludeColumnHeaders) {
|
||||
List<string> strings = new List<string>();
|
||||
foreach (OLVColumn col in columns)
|
||||
strings.Add(col.Text);
|
||||
|
||||
WriteOneRow(sbText, strings, "", "\t", "", null);
|
||||
WriteOneRow(sbHtml, strings, "<tr><td>", "</td><td>", "</td></tr>", HtmlEncode);
|
||||
WriteOneRow(sbCsv, strings, "", ",", "", CsvEncode);
|
||||
}
|
||||
|
||||
foreach (object modelObject in this.ModelObjects) {
|
||||
List<string> strings = new List<string>();
|
||||
foreach (OLVColumn col in columns)
|
||||
strings.Add(col.GetStringValue(modelObject));
|
||||
|
||||
WriteOneRow(sbText, strings, "", "\t", "", null);
|
||||
WriteOneRow(sbHtml, strings, "<tr><td>", "</td><td>", "</td></tr>", HtmlEncode);
|
||||
WriteOneRow(sbCsv, strings, "", ",", "", CsvEncode);
|
||||
}
|
||||
sbHtml.AppendLine("</table>");
|
||||
|
||||
results = new Dictionary<ExportFormat, string>();
|
||||
results[ExportFormat.TabSeparated] = sbText.ToString();
|
||||
results[ExportFormat.CSV] = sbCsv.ToString();
|
||||
results[ExportFormat.HTML] = sbHtml.ToString();
|
||||
}
|
||||
|
||||
private delegate string StringToString(string str);
|
||||
|
||||
private void WriteOneRow(StringBuilder sb, IEnumerable<string> strings, string startRow, string betweenCells, string endRow, StringToString encoder) {
|
||||
sb.Append(startRow);
|
||||
bool first = true;
|
||||
foreach (string s in strings) {
|
||||
if (!first)
|
||||
sb.Append(betweenCells);
|
||||
sb.Append(encoder == null ? s : encoder(s));
|
||||
first = false;
|
||||
}
|
||||
sb.AppendLine(endRow);
|
||||
}
|
||||
|
||||
private Dictionary<ExportFormat, string> results;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Encoding
|
||||
|
||||
/// <summary>
|
||||
/// Encode a string such that it can be used as a value in a CSV file.
|
||||
/// This basically means replacing any quote mark with two quote marks,
|
||||
/// and enclosing the whole string in quotes.
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
private static string CsvEncode(string text) {
|
||||
if (text == null)
|
||||
return null;
|
||||
|
||||
const string DOUBLEQUOTE = @""""; // one double quote
|
||||
const string TWODOUBEQUOTES = @""""""; // two double quotes
|
||||
|
||||
StringBuilder sb = new StringBuilder(DOUBLEQUOTE);
|
||||
sb.Append(text.Replace(DOUBLEQUOTE, TWODOUBEQUOTES));
|
||||
sb.Append(DOUBLEQUOTE);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HTML-encodes a string and returns the encoded string.
|
||||
/// </summary>
|
||||
/// <param name="text">The text string to encode. </param>
|
||||
/// <returns>The HTML-encoded text.</returns>
|
||||
/// <remarks>Taken from http://www.west-wind.com/weblog/posts/2009/Feb/05/Html-and-Uri-String-Encoding-without-SystemWeb</remarks>
|
||||
private static string HtmlEncode(string text) {
|
||||
if (text == null)
|
||||
return null;
|
||||
|
||||
StringBuilder sb = new StringBuilder(text.Length);
|
||||
|
||||
int len = text.Length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
switch (text[i]) {
|
||||
case '<':
|
||||
sb.Append("<");
|
||||
break;
|
||||
case '>':
|
||||
sb.Append(">");
|
||||
break;
|
||||
case '"':
|
||||
sb.Append(""");
|
||||
break;
|
||||
case '&':
|
||||
sb.Append("&");
|
||||
break;
|
||||
default:
|
||||
if (text[i] > 159) {
|
||||
// decimal numeric entity
|
||||
sb.Append("&#");
|
||||
sb.Append(((int)text[i]).ToString(CultureInfo.InvariantCulture));
|
||||
sb.Append(";");
|
||||
} else
|
||||
sb.Append(text[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
555
ObjectListView/Utilities/TypedObjectListView.cs
Normal file
@@ -0,0 +1,555 @@
|
||||
/*
|
||||
* TypedObjectListView - A wrapper around an ObjectListView that provides type-safe delegates.
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 27/09/2008 9:15 AM
|
||||
*
|
||||
* Change log:
|
||||
* v2.6
|
||||
* 2012-10-26 JPP - Handle rare case where a null model object was passed into aspect getters.
|
||||
* v2.3
|
||||
* 2009-03-31 JPP - Added Objects property
|
||||
* 2008-11-26 JPP - Added tool tip getting methods
|
||||
* 2008-11-05 JPP - Added CheckState handling methods
|
||||
* 2008-10-24 JPP - Generate dynamic methods MkII. This one handles value types
|
||||
* 2008-10-21 JPP - Generate dynamic methods
|
||||
* 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.Text;
|
||||
using System.Windows.Forms;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// A TypedObjectListView is a type-safe wrapper around an ObjectListView.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>VCS does not support generics on controls. It can be faked to some degree, but it
|
||||
/// cannot be completely overcome. In our case in particular, there is no way to create
|
||||
/// the custom OLVColumn's that we need to truly be generic. So this wrapper is an
|
||||
/// experiment in providing some type-safe access in a way that is useful and available today.</para>
|
||||
/// <para>A TypedObjectListView is not more efficient than a normal ObjectListView.
|
||||
/// Underneath, the same name of casts are performed. But it is easier to use since you
|
||||
/// do not have to write the casts yourself.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The class of model object that the list will manage</typeparam>
|
||||
/// <example>
|
||||
/// To use a TypedObjectListView, you write code like this:
|
||||
/// <code>
|
||||
/// TypedObjectListView<Person> tlist = new TypedObjectListView<Person>(this.listView1);
|
||||
/// tlist.CheckStateGetter = delegate(Person x) { return x.IsActive; };
|
||||
/// tlist.GetColumn(0).AspectGetter = delegate(Person x) { return x.Name; };
|
||||
/// ...
|
||||
/// </code>
|
||||
/// To iterate over the selected objects, you can write something elegant like this:
|
||||
/// <code>
|
||||
/// foreach (Person x in tlist.SelectedObjects) {
|
||||
/// x.GrantSalaryIncrease();
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <remarks>
|
||||
/// Create a typed wrapper around the given list.
|
||||
/// </remarks>
|
||||
/// <param name="olv">The listview to be wrapped</param>
|
||||
public class TypedObjectListView<T>(ObjectListView olv) where T : class
|
||||
{
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Properties
|
||||
|
||||
/// <summary>
|
||||
/// Return the model object that is checked, if only one row is checked.
|
||||
/// If zero rows are checked, or more than one row, null is returned.
|
||||
/// </summary>
|
||||
public virtual T CheckedObject {
|
||||
get { return (T)this.olv.CheckedObject; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the list of all the checked model objects
|
||||
/// </summary>
|
||||
public virtual IList<T> CheckedObjects {
|
||||
get {
|
||||
IList checkedObjects = this.olv.CheckedObjects;
|
||||
List<T> objects = new List<T>(checkedObjects.Count);
|
||||
foreach (object x in checkedObjects)
|
||||
objects.Add((T)x);
|
||||
|
||||
return objects;
|
||||
}
|
||||
set { this.olv.CheckedObjects = (IList)value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ObjectListView that is being wrapped
|
||||
/// </summary>
|
||||
public virtual ObjectListView ListView {
|
||||
get { return olv; }
|
||||
set { olv = value; }
|
||||
}
|
||||
private ObjectListView olv = olv;
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the list of all model objects
|
||||
/// </summary>
|
||||
public virtual IList<T> Objects {
|
||||
get {
|
||||
List<T> objects = new List<T>(this.olv.GetItemCount());
|
||||
for (int i = 0; i < this.olv.GetItemCount(); i++)
|
||||
objects.Add(this.GetModelObject(i));
|
||||
|
||||
return objects;
|
||||
}
|
||||
set { this.olv.SetObjects(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the model object that is selected, if only one row is selected.
|
||||
/// If zero rows are selected, or more than one row, null is returned.
|
||||
/// </summary>
|
||||
public virtual T SelectedObject {
|
||||
get { return (T)this.olv.SelectedObject; }
|
||||
set { this.olv.SelectedObject = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The list of model objects that are selected.
|
||||
/// </summary>
|
||||
public virtual IList<T> SelectedObjects {
|
||||
get {
|
||||
List<T> objects = new List<T>(this.olv.SelectedIndices.Count);
|
||||
foreach (int index in this.olv.SelectedIndices)
|
||||
objects.Add((T)this.olv.GetModelObject(index));
|
||||
|
||||
return objects;
|
||||
}
|
||||
set { this.olv.SelectedObjects = (IList)value; }
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Accessors
|
||||
|
||||
/// <summary>
|
||||
/// Return a typed wrapper around the column at the given index
|
||||
/// </summary>
|
||||
/// <param name="i">The index of the column</param>
|
||||
/// <returns>A typed column or null</returns>
|
||||
public virtual TypedColumn<T> GetColumn(int i) {
|
||||
return new TypedColumn<T>(this.olv.GetColumn(i));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a typed wrapper around the column with the given name
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the column</param>
|
||||
/// <returns>A typed column or null</returns>
|
||||
public virtual TypedColumn<T> GetColumn(string name) {
|
||||
return new TypedColumn<T>(this.olv.GetColumn(name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the model object at the given index
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the model object</param>
|
||||
/// <returns>The model object or null</returns>
|
||||
public virtual T GetModelObject(int index) {
|
||||
return (T)this.olv.GetModelObject(index);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Delegates
|
||||
|
||||
/// <summary>
|
||||
/// CheckStateGetter
|
||||
/// </summary>
|
||||
/// <param name="rowObject"></param>
|
||||
/// <returns></returns>
|
||||
public delegate CheckState TypedCheckStateGetterDelegate(T rowObject);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the check state getter
|
||||
/// </summary>
|
||||
public virtual TypedCheckStateGetterDelegate CheckStateGetter {
|
||||
get { return checkStateGetter; }
|
||||
set {
|
||||
this.checkStateGetter = value;
|
||||
if (value == null)
|
||||
this.olv.CheckStateGetter = null;
|
||||
else
|
||||
this.olv.CheckStateGetter = delegate(object x) {
|
||||
return this.checkStateGetter((T)x);
|
||||
};
|
||||
}
|
||||
}
|
||||
private TypedCheckStateGetterDelegate checkStateGetter;
|
||||
|
||||
/// <summary>
|
||||
/// BooleanCheckStateGetter
|
||||
/// </summary>
|
||||
/// <param name="rowObject"></param>
|
||||
/// <returns></returns>
|
||||
public delegate bool TypedBooleanCheckStateGetterDelegate(T rowObject);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the boolean check state getter
|
||||
/// </summary>
|
||||
public virtual TypedBooleanCheckStateGetterDelegate BooleanCheckStateGetter {
|
||||
set {
|
||||
if (value == null)
|
||||
this.olv.BooleanCheckStateGetter = null;
|
||||
else
|
||||
this.olv.BooleanCheckStateGetter = delegate(object x) {
|
||||
return value((T)x);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CheckStatePutter
|
||||
/// </summary>
|
||||
/// <param name="rowObject"></param>
|
||||
/// <param name="newValue"></param>
|
||||
/// <returns></returns>
|
||||
public delegate CheckState TypedCheckStatePutterDelegate(T rowObject, CheckState newValue);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the check state putter delegate
|
||||
/// </summary>
|
||||
public virtual TypedCheckStatePutterDelegate CheckStatePutter {
|
||||
get { return checkStatePutter; }
|
||||
set {
|
||||
this.checkStatePutter = value;
|
||||
if (value == null)
|
||||
this.olv.CheckStatePutter = null;
|
||||
else
|
||||
this.olv.CheckStatePutter = delegate(object x, CheckState newValue) {
|
||||
return this.checkStatePutter((T)x, newValue);
|
||||
};
|
||||
}
|
||||
}
|
||||
private TypedCheckStatePutterDelegate checkStatePutter;
|
||||
|
||||
/// <summary>
|
||||
/// BooleanCheckStatePutter
|
||||
/// </summary>
|
||||
/// <param name="rowObject"></param>
|
||||
/// <param name="newValue"></param>
|
||||
/// <returns></returns>
|
||||
public delegate bool TypedBooleanCheckStatePutterDelegate(T rowObject, bool newValue);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the boolean check state putter
|
||||
/// </summary>
|
||||
public virtual TypedBooleanCheckStatePutterDelegate BooleanCheckStatePutter {
|
||||
set {
|
||||
if (value == null)
|
||||
this.olv.BooleanCheckStatePutter = null;
|
||||
else
|
||||
this.olv.BooleanCheckStatePutter = delegate(object x, bool newValue) {
|
||||
return value((T)x, newValue);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ToolTipGetter
|
||||
/// </summary>
|
||||
/// <param name="column"></param>
|
||||
/// <param name="modelObject"></param>
|
||||
/// <returns></returns>
|
||||
public delegate String TypedCellToolTipGetterDelegate(OLVColumn column, T modelObject);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the cell tooltip getter
|
||||
/// </summary>
|
||||
public virtual TypedCellToolTipGetterDelegate CellToolTipGetter {
|
||||
set {
|
||||
if (value == null)
|
||||
this.olv.CellToolTipGetter = null;
|
||||
else
|
||||
this.olv.CellToolTipGetter = delegate(OLVColumn col, Object x) {
|
||||
return value(col, (T)x);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the header tool tip getter
|
||||
/// </summary>
|
||||
public virtual HeaderToolTipGetterDelegate HeaderToolTipGetter {
|
||||
get { return this.olv.HeaderToolTipGetter; }
|
||||
set { this.olv.HeaderToolTipGetter = value; }
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Commands
|
||||
|
||||
/// <summary>
|
||||
/// This method will generate AspectGetters for any column that has an AspectName.
|
||||
/// </summary>
|
||||
public virtual void GenerateAspectGetters() {
|
||||
for (int i = 0; i < this.ListView.Columns.Count; i++)
|
||||
this.GetColumn(i).GenerateAspectGetter();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A type-safe wrapper around an OLVColumn
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <remarks>
|
||||
/// Creates a TypedColumn
|
||||
/// </remarks>
|
||||
/// <param name="column"></param>
|
||||
public class TypedColumn<T>(OLVColumn column) where T : class
|
||||
{
|
||||
private OLVColumn column = column;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="rowObject"></param>
|
||||
/// <returns></returns>
|
||||
public delegate Object TypedAspectGetterDelegate(T rowObject);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="rowObject"></param>
|
||||
/// <param name="newValue"></param>
|
||||
public delegate void TypedAspectPutterDelegate(T rowObject, Object newValue);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="rowObject"></param>
|
||||
/// <returns></returns>
|
||||
public delegate Object TypedGroupKeyGetterDelegate(T rowObject);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="rowObject"></param>
|
||||
/// <returns></returns>
|
||||
public delegate Object TypedImageGetterDelegate(T rowObject);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public TypedAspectGetterDelegate AspectGetter {
|
||||
get { return this.aspectGetter; }
|
||||
set {
|
||||
this.aspectGetter = value;
|
||||
if (value == null)
|
||||
this.column.AspectGetter = null;
|
||||
else
|
||||
this.column.AspectGetter = delegate(object x) {
|
||||
return x == null ? null : this.aspectGetter((T)x);
|
||||
};
|
||||
}
|
||||
}
|
||||
private TypedAspectGetterDelegate aspectGetter;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public TypedAspectPutterDelegate AspectPutter {
|
||||
get { return aspectPutter; }
|
||||
set {
|
||||
this.aspectPutter = value;
|
||||
if (value == null)
|
||||
this.column.AspectPutter = null;
|
||||
else
|
||||
this.column.AspectPutter = delegate(object x, object newValue) {
|
||||
this.aspectPutter((T)x, newValue);
|
||||
};
|
||||
}
|
||||
}
|
||||
private TypedAspectPutterDelegate aspectPutter;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public TypedImageGetterDelegate ImageGetter {
|
||||
get { return imageGetter; }
|
||||
set {
|
||||
this.imageGetter = value;
|
||||
if (value == null)
|
||||
this.column.ImageGetter = null;
|
||||
else
|
||||
this.column.ImageGetter = delegate(object x) {
|
||||
return this.imageGetter((T)x);
|
||||
};
|
||||
}
|
||||
}
|
||||
private TypedImageGetterDelegate imageGetter;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public TypedGroupKeyGetterDelegate GroupKeyGetter {
|
||||
get { return groupKeyGetter; }
|
||||
set {
|
||||
this.groupKeyGetter = value;
|
||||
if (value == null)
|
||||
this.column.GroupKeyGetter = null;
|
||||
else
|
||||
this.column.GroupKeyGetter = delegate(object x) {
|
||||
return this.groupKeyGetter((T)x);
|
||||
};
|
||||
}
|
||||
}
|
||||
private TypedGroupKeyGetterDelegate groupKeyGetter;
|
||||
|
||||
#region Dynamic methods
|
||||
|
||||
/// <summary>
|
||||
/// Generate an aspect getter that does the same thing as the AspectName,
|
||||
/// except without using reflection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If you have an AspectName of "Owner.Address.Postcode", this will generate
|
||||
/// the equivilent of: <code>this.AspectGetter = delegate (object x) {
|
||||
/// return x.Owner.Address.Postcode;
|
||||
/// }
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If AspectName is empty, this method will do nothing, otherwise
|
||||
/// this will replace any existing AspectGetter.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public void GenerateAspectGetter() {
|
||||
if (!String.IsNullOrEmpty(this.column.AspectName))
|
||||
this.AspectGetter = this.GenerateAspectGetter(typeof(T), this.column.AspectName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates an aspect getter method dynamically. The method will execute
|
||||
/// the given dotted chain of selectors against a model object given at runtime.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of model object to be passed to the generated method</param>
|
||||
/// <param name="path">A dotted chain of selectors. Each selector can be the name of a
|
||||
/// field, property or parameter-less method.</param>
|
||||
/// <returns>A typed delegate</returns>
|
||||
private TypedAspectGetterDelegate GenerateAspectGetter(Type type, string path) {
|
||||
DynamicMethod getter = new DynamicMethod(String.Empty,
|
||||
typeof(Object), new Type[] { type }, type, true);
|
||||
this.GenerateIL(type, path, getter.GetILGenerator());
|
||||
return (TypedAspectGetterDelegate)getter.CreateDelegate(typeof(TypedAspectGetterDelegate));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method generates the actual IL for the method.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="il"></param>
|
||||
private void GenerateIL(Type type, string path, ILGenerator il) {
|
||||
// Push our model object onto the stack
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
|
||||
// Generate the IL to access each part of the dotted chain
|
||||
string[] parts = path.Split('.');
|
||||
for (int i = 0; i < parts.Length; i++) {
|
||||
type = this.GeneratePart(il, type, parts[i], (i == parts.Length - 1));
|
||||
if (type == null)
|
||||
break;
|
||||
}
|
||||
|
||||
// If the object to be returned is a value type (e.g. int, bool), it
|
||||
// must be boxed, since the delegate returns an Object
|
||||
if (type != null && type.IsValueType && !typeof(T).IsValueType)
|
||||
il.Emit(OpCodes.Box, type);
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
private Type GeneratePart(ILGenerator il, Type type, string pathPart, bool isLastPart) {
|
||||
// TODO: Generate check for null
|
||||
|
||||
// Find the first member with the given nam that is a field, property, or parameter-less method
|
||||
List<MemberInfo> infos = new List<MemberInfo>(type.GetMember(pathPart));
|
||||
MemberInfo info = infos.Find(delegate(MemberInfo x) {
|
||||
if (x.MemberType == MemberTypes.Field || x.MemberType == MemberTypes.Property)
|
||||
return true;
|
||||
if (x.MemberType == MemberTypes.Method)
|
||||
return ((MethodInfo)x).GetParameters().Length == 0;
|
||||
else
|
||||
return false;
|
||||
});
|
||||
|
||||
// If we couldn't find anything with that name, pop the current result and return an error
|
||||
if (info == null) {
|
||||
il.Emit(OpCodes.Pop);
|
||||
if (Munger.IgnoreMissingAspects)
|
||||
il.Emit(OpCodes.Ldnull);
|
||||
else
|
||||
il.Emit(OpCodes.Ldstr, String.Format("'{0}' is not a parameter-less method, property or field of type '{1}'", pathPart, type.FullName));
|
||||
return null;
|
||||
}
|
||||
|
||||
// Generate the correct IL to access the member. We remember the type of object that is going to be returned
|
||||
// so that we can do a method lookup on it at the next iteration
|
||||
Type resultType = null;
|
||||
switch (info.MemberType) {
|
||||
case MemberTypes.Method:
|
||||
MethodInfo mi = (MethodInfo)info;
|
||||
if (mi.IsVirtual)
|
||||
il.Emit(OpCodes.Callvirt, mi);
|
||||
else
|
||||
il.Emit(OpCodes.Call, mi);
|
||||
resultType = mi.ReturnType;
|
||||
break;
|
||||
case MemberTypes.Property:
|
||||
PropertyInfo pi = (PropertyInfo)info;
|
||||
il.Emit(OpCodes.Call, pi.GetGetMethod());
|
||||
resultType = pi.PropertyType;
|
||||
break;
|
||||
case MemberTypes.Field:
|
||||
FieldInfo fi = (FieldInfo)info;
|
||||
il.Emit(OpCodes.Ldfld, fi);
|
||||
resultType = fi.FieldType;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the method returned a value type, and something is going to call a method on that value,
|
||||
// we need to load its address onto the stack, rather than the object itself.
|
||||
if (resultType.IsValueType && !isLastPart) {
|
||||
LocalBuilder lb = il.DeclareLocal(resultType);
|
||||
il.Emit(OpCodes.Stloc, lb);
|
||||
il.Emit(OpCodes.Ldloca, lb);
|
||||
}
|
||||
|
||||
return resultType;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
1242
ObjectListView/VirtualObjectListView.cs
Normal file
13
README.md
@@ -20,8 +20,8 @@
|
||||
<a href="https://twitter.com/mremoteng">
|
||||
<img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/mremoteng?color=%231DA1F2&label=Twitter&logo=Twitter&style=flat-square">
|
||||
</a>
|
||||
<a href="https://gitter.im/mRemoteNG/PublicChat">
|
||||
<img alt="Gitter" src="https://img.shields.io/gitter/room/mRemoteNG/PublicChat?label=Join%20the%20Chat&logo=Gitter&style=flat-square">
|
||||
<a href="https://app.element.io/#/room/#mremoteng:matrix.org">
|
||||
<img alt="Element" src="https://img.shields.io/matrix/mremoteng:matrix.org?label=Join%20to%20chat%20about%20mRemoteNG&logo=element&style=social&link=https://app.element.io/#/room/#mremoteng:matrix.org">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
@@ -43,6 +43,9 @@
|
||||
<a href='https://mremoteng.readthedocs.io/en/latest/?badge=latest'>
|
||||
<img src='https://readthedocs.org/projects/mremoteng/badge/?version=latest' alt='Documentation Status' />
|
||||
</a>
|
||||
<a href="https://gurubase.io/g/mremoteng">
|
||||
<img alt="Gurubase" src="https://img.shields.io/badge/Gurubase-Ask%20mRemoteNG%20Guru-006BFF?style=flat-square">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
@@ -51,7 +54,7 @@
|
||||
| ---------------|--------------|-----------|
|
||||
| Stable |  | [](https://github.com/mRemoteNG/mRemoteNG/releases/tag/v1.76.20) |
|
||||
| Preview |  | [](https://github.com/mRemoteNG/mRemoteNG/releases/tag/v1.77.1) |
|
||||
| Nightly |  | [](https://github.com/mRemoteNG/mRemoteNG/releases/tag/2022.01.07-1.77.2-nb) |
|
||||
| Nightly |  | [](https://github.com/mRemoteNG/mRemoteNG/releases/tag/2023.03.03-v1.77.3-nb) |
|
||||
|
||||
## Features
|
||||
|
||||
@@ -88,12 +91,14 @@ You will need to compile it yourself using Visual Studio.
|
||||
### Minimum Requirements
|
||||
|
||||
* [Microsoft Visual C++ Redistributable for Visual Studio 2015 - 2022](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads)
|
||||
* [Microsoft .NET 6.0](https://dotnet.microsoft.com/download/dotnet/6.0)
|
||||
* [Microsoft .NET 6.0 Desktop Runtime](https://dotnet.microsoft.com/download/dotnet/6.0)
|
||||
* Microsoft Terminal Service Client 6.0 or later
|
||||
* Needed if you use RDP. mstscax.dll and/or msrdp.ocx be registered.
|
||||
|
||||
### Download
|
||||
|
||||
> :star: Starting Windows 11 you can use winget to install mRemoteNG. Just run `winget install -e --id mRemoteNG.mRemoteNG`
|
||||
|
||||
mRemoteNG is available as a redistributable MSI package or as a portable ZIP package and can be downloaded from the following locations:
|
||||
* [GitHub](https://github.com/mRemoteNG/mRemoteNG/releases)
|
||||
* [Project Website](https://mremoteng.org/download)
|
||||
|
||||
38
Tools/cert/CodeSigning_Cert_mRemoteNG_Certum.enc
Normal file
@@ -0,0 +1,38 @@
|
||||
U2FsdGVkX1/uEeToOEIrunpoPNl7NUYNQfI+ixMzGgKX0DlHZxa/PonAVd9NoAE+
|
||||
dqWegkN8fa/M2HcW8moN5sN0yS88amG8cfwRZNRSgRB4IxDdYPjM/iX7y8FjUh9R
|
||||
OZnGhq1rAkqcf7ASAdZfQDSFOAEDPQgByN4IB2j/G3ueqV3jf5sWWiM0ielcorWX
|
||||
jXuEL7uIk9diI3Qh3BVqmrotzErzqTTLB4DWoL1aWqRRYH+exKahfcMT3C9r+tul
|
||||
ETH6o1im3kdYzFgHl58FmihbHa+h1+c+DWVGGXRxJPw1DZfg8/+ldIy8J75aWimz
|
||||
2MX+PiBVQj+wYlSSkQLj2EdbpSERlinE1O55ldwpbnMPAlYCgFRdO3/hiB6LiLLt
|
||||
n9s8f32HLNG20Mk1oxXdN9VPOw+RwQpUf6Zfcyps6e/9EFKBLnwq4cYwM/dHRuez
|
||||
w48EJX5wKzpukHn2UFg5aCRGYU/4NyUn+AlIjjCMBWY1R0uBhC340cl6YqoPFN12
|
||||
clzbS6JdQS+ktusCeaKcsj2iknVqQrGY81LKrTaQZdfehGwAn9pMWE3iWRX80yzO
|
||||
s08lpmHfPK4uhtlyIbdReLn4n7hvB8KRVa7Foms+4wpEwKrL8KF/8CYc89Tm3PSD
|
||||
LCrLr7rzCHh51ncJXHAwZqY2ZRLnQBVwhIRsFYyZ595b89tuDY00sYpWnTKzWubK
|
||||
UD993CmYKg1cA+mj6NR6iSmecawsUz68nI/nmHM2APE5cCCHSK3lz40e9Z9Hmy5Y
|
||||
kVS6c86a+gwNyn4F9t3iISdj2vIOwGMVWQLeY+nwpiKnnOuI0XPtIMjNbhoaDToT
|
||||
kph2ZVjqbpmYHgTSX71v8Y16Stl+p/dZn0dE2d4JhxOJxDN//H6y7mOwg8DYX9GP
|
||||
rFBMIEWyEQyEFYTmWgztQ5KU89w0lV1qiWpaiWR/IDkGbUzHR8ELp4NHYPbZnuYa
|
||||
FFXooJGPsQ02ioXPm18Lkhloo63lANgyBE0jc1x9hPrI0oVn1wTkKdeW13IbHzrn
|
||||
VsGXeZz06bB46QChXUPmQ49sNkAXcMDnfNFM9ayUa8eVmNx9fWabtBy8qs7rpdv0
|
||||
1y/2ud4Da9t7SAUvxwI65+b2Ytk05pLFLmQYb01g4BpBDxbJW3yw3CqKgMnLoZ3t
|
||||
s2kiUpn7FEQU8jbpbFV+UB4DdJgm8S7o1gbTEWYTscZ7l+oLCmQgMYoi/EJDDIa2
|
||||
cCSdGy4p7kLlLUoO438Mz36+FDf6qX2B86ezVdNnXQb3UPljjDxiOFM6NkI3HTpl
|
||||
RqxjBg8JdrwoQ1UMha6ucDKhPXq1xkg0dpO9QyjxywssG3krgQ5Py64s5Xu7IgQm
|
||||
AzmwGTZ6gFZlDTr9SpJkiucF9vexCo6JHHkF8OjhS49FanqB8otJvg5JclVugP51
|
||||
LcqqvuMkAsFago261SNcOhgtR6nV5B9QgHyr66c6YnTlwt07T1Qq3S1lw2x0Eccs
|
||||
PKbJDVU5rAHiM71QYmwsuoC8qkYPTtVPIoUTs+5u5aLywVoLejr1dE5twNXy5PYY
|
||||
fDwubg0YG3kchvv85N3epZ1h50ADq3W3msU9bWDKWwdwIbpGq+dwjkLssBQmjVtI
|
||||
R8rGbt1DgL7xtRNF9ESnWVkfHJvJu/5oD6wGAU/oIfBxVON0VYb1evc8wRdQTbDH
|
||||
Dnt+aKwcSPYdyGVRKfRtBGvEZ8rB5hzCXQnS795L0imdfXjBSJn7Pnl9VwpcB3Pr
|
||||
aZ6s0GcB8kYDEXzjv+o7JF6k5i2I+sVGwvFVGIoVd/Igq2ysrk/GfWVov0SUu78A
|
||||
JeHYdtRuKwXOdZw2cjZQ72bvFaHOuoXrQnyKyZDWRyu0NB5HLW75v/YEbr4msIm9
|
||||
E+3HFwRvKSTfUx/M4NgVKrgsHDeBRD4tLNx/SerQvqaplunM7OfAtULucveUhwSo
|
||||
vT6uNK3URe1qDgX554cz8c6+KrkglTLFMuKfNWj3q/uSM0BxTTD9QgorNdlMmErH
|
||||
TfV/ZpZACpuMFRbC5xQRkCG1x4U12pdPbtIkGtVBJROEXhP1aw3BDWwrIN7zSgfj
|
||||
8I4OF4fbj/rSuvI4klzi1zlUMQQnenlRURE+7DKRxtWipJhW9vtDI0LXN7gGfmbR
|
||||
73uR3YUny6zUJ0svqaWj4Eo6t3g99nmk4D2hm/622dRksv2HqEQiq29jJxlcbdZB
|
||||
PU96wOp54s/BzgodyI5dh+xL06Obr9AltLV9vw3iK0VBZqquj9FuhvWC1tYlZQJF
|
||||
AOgVDOGUZzmAJXftI7gaohFBwsT5tAHQtBuY7tB3b1hrnfrFb9FTNxGZJKcIH3p4
|
||||
dOAvedsfuq+2/lU9iM4tX9fjSzfnGRZRuKCGDSdhE6EDik9/f2kSCoY9z0zJwdZH
|
||||
324TpGbZNbgcwgHDL9i5Nsnaua5yxtr0/Fr1We1tvn0=
|
||||
@@ -1,17 +1,27 @@
|
||||
#Requires -Version 4.0
|
||||
param (
|
||||
[string]
|
||||
[Parameter(Mandatory=$true)]
|
||||
$WebsiteTargetOwner,
|
||||
|
||||
[string]
|
||||
[Parameter(Mandatory=$true)]
|
||||
$WebsiteTargetRepository,
|
||||
|
||||
[string]
|
||||
[Parameter(Mandatory=$false)]
|
||||
$PreTagName = "",
|
||||
|
||||
[string]
|
||||
[Parameter(Mandatory=$true)]
|
||||
$TagName,
|
||||
|
||||
[string]
|
||||
[Parameter(Mandatory=$true)]
|
||||
[ValidateSet("Stable","Beta","Development")]
|
||||
$UpdateChannel
|
||||
$ProjectName
|
||||
)
|
||||
|
||||
|
||||
|
||||
function New-MsiUpdateFileContent {
|
||||
param (
|
||||
[System.IO.FileInfo]
|
||||
@@ -25,7 +35,7 @@ function New-MsiUpdateFileContent {
|
||||
|
||||
$version = $MsiFile.BaseName -replace "[a-zA-Z-]*"
|
||||
$certThumbprint = (Get-AuthenticodeSignature -FilePath $MsiFile).SignerCertificate.Thumbprint
|
||||
$hash = Get-FileHash -Algorithm SHA512 $MsiFile | % { $_.Hash }
|
||||
$hash = Get-FileHash -Algorithm SHA512 $MsiFile | ForEach-Object { $_.Hash }
|
||||
|
||||
$fileContents = `
|
||||
"Version: $version
|
||||
@@ -49,7 +59,7 @@ function New-ZipUpdateFileContent {
|
||||
)
|
||||
|
||||
$version = $ZipFile.BaseName -replace "[a-zA-Z-]*"
|
||||
$hash = Get-FileHash -Algorithm SHA512 $ZipFile | % { $_.Hash }
|
||||
$hash = Get-FileHash -Algorithm SHA512 $ZipFile | ForEach-Object { $_.Hash }
|
||||
|
||||
$fileContents = `
|
||||
"Version: $version
|
||||
@@ -64,7 +74,7 @@ function Resolve-UpdateCheckFileName {
|
||||
param (
|
||||
[string]
|
||||
[Parameter(Mandatory=$true)]
|
||||
[ValidateSet("Stable","Beta","Development")]
|
||||
[ValidateSet("Stable","Preview","Nightly")]
|
||||
$UpdateChannel,
|
||||
|
||||
[string]
|
||||
@@ -75,8 +85,8 @@ function Resolve-UpdateCheckFileName {
|
||||
|
||||
$fileName = ""
|
||||
|
||||
if ($UpdateChannel -eq "Beta") { $fileName += "beta-" }
|
||||
elseif ($UpdateChannel -eq "Development") { $fileName += "dev-" }
|
||||
if ($UpdateChannel -eq "Preview") { $fileName += "preview-" }
|
||||
elseif ($UpdateChannel -eq "Nightly") { $fileName += "nightly-" }
|
||||
|
||||
$fileName += "update"
|
||||
|
||||
@@ -87,23 +97,72 @@ function Resolve-UpdateCheckFileName {
|
||||
Write-Output $fileName
|
||||
}
|
||||
|
||||
Write-Output ""
|
||||
Write-Output "===== Begin $($PSCmdlet.MyInvocation.MyCommand) ====="
|
||||
|
||||
# determine update channel
|
||||
if ($env:APPVEYOR_PROJECT_NAME -match "(Nightly)") {
|
||||
Write-Output "UpdateChannel = Nightly"
|
||||
$UpdateChannel = "Nightly"
|
||||
$ModifiedTagName = "$PreTagName-$TagName-NB"
|
||||
} elseif ($env:APPVEYOR_PROJECT_NAME -match "(Preview)") {
|
||||
Write-Output "UpdateChannel = Preview"
|
||||
$UpdateChannel = "Preview"
|
||||
$ModifiedTagName = "v$TagName-PB"
|
||||
} elseif ($env:APPVEYOR_PROJECT_NAME -match "(Stable)") {
|
||||
Write-Output "UpdateChannel = Stable"
|
||||
$UpdateChannel = "Stable"
|
||||
$ModifiedTagName = "v" + $TagName.Split("-")[0]
|
||||
} else {
|
||||
$UpdateChannel = ""
|
||||
}
|
||||
|
||||
#$buildFolder = Join-Path -Path $PSScriptRoot -ChildPath "..\mRemoteNG\bin\x64\Release" -Resolve -ErrorAction Ignore
|
||||
$ReleaseFolder = Join-Path -Path $PSScriptRoot -ChildPath "..\Release" -Resolve
|
||||
|
||||
if ($UpdateChannel -ne "" -and $ReleaseFolder -ne "" -and $WebsiteTargetOwner -and $WebsiteTargetRepository) {
|
||||
|
||||
$releaseFolder = Join-Path -Path $PSScriptRoot -ChildPath "..\Release" -Resolve
|
||||
$msiFile = Get-ChildItem -Path "$ReleaseFolder\*.msi" -Exclude "*-symbols-*.zip" | Sort-Object LastWriteTime | Select-Object -last 1
|
||||
if (![string]::IsNullOrEmpty($msiFile)) {
|
||||
$msiUpdateContents = New-MsiUpdateFileContent -MsiFile $msiFile -TagName $ModifiedTagName
|
||||
$msiUpdateFileName = Resolve-UpdateCheckFileName -UpdateChannel $UpdateChannel -Type Normal
|
||||
Write-Output "`n`nMSI Update Check File Contents ($msiUpdateFileName)`n------------------------------"
|
||||
Tee-Object -InputObject $msiUpdateContents -FilePath "$ReleaseFolder\$msiUpdateFileName"
|
||||
|
||||
# commit msi update txt file
|
||||
if ($env:WEBSITE_UPDATE_ENABLED.ToLower() -eq "true") {
|
||||
if ((Test-Path -Path "$ReleaseFolder\$msiUpdateFileName") -and (-not [string]::IsNullOrEmpty($WebsiteTargetRepository))) {
|
||||
Write-Output "Publish Update File $msiUpdateFileName to $WebsiteTargetRepository"
|
||||
$update_file_content_string = Get-Content "$ReleaseFolder\$msiUpdateFileName" | Out-String
|
||||
Set-GitHubContent -OwnerName $WebsiteTargetOwner -RepositoryName $WebsiteTargetRepository -Path $msiUpdateFileName -CommitMessage "Build $ModifiedTagName" -Content $update_file_content_string -BranchName main
|
||||
} else {
|
||||
Write-Warning "WARNING: Update file does not exist: $ReleaseFolder\$msiUpdateFileName"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# build msi update file
|
||||
$msiFile = Get-ChildItem -Path "$releaseFolder\*.msi" | sort LastWriteTime | select -last 1
|
||||
$msiUpdateContents = New-MsiUpdateFileContent -MsiFile $msiFile -TagName $TagName
|
||||
$msiUpdateFileName = Resolve-UpdateCheckFileName -UpdateChannel $UpdateChannel -Type Normal
|
||||
Write-Output "`n`nMSI Update Check File Contents ($msiUpdateFileName)`n------------------------------"
|
||||
Tee-Object -InputObject $msiUpdateContents -FilePath "$releaseFolder\$msiUpdateFileName"
|
||||
# build zip update file
|
||||
$zipFile = Get-ChildItem -Path "$ReleaseFolder\*.zip" -Exclude "*-symbols-*.zip" | Sort-Object LastWriteTime | Select-Object -last 1
|
||||
if (![string]::IsNullOrEmpty($zipFile)) {
|
||||
$zipUpdateContents = New-ZipUpdateFileContent -ZipFile $zipFile -TagName $ModifiedTagName
|
||||
$zipUpdateFileName = Resolve-UpdateCheckFileName -UpdateChannel $UpdateChannel -Type Portable
|
||||
Write-Output "`n`nZip Update Check File Contents ($zipUpdateFileName)`n------------------------------"
|
||||
Tee-Object -InputObject $zipUpdateContents -FilePath "$ReleaseFolder\$zipUpdateFileName"
|
||||
|
||||
# commit zip update txt file
|
||||
if ($env:WEBSITE_UPDATE_ENABLED.ToLower() -eq "true") {
|
||||
if ((Test-Path -Path "$ReleaseFolder\$zipUpdateFileName") -and (-not [string]::IsNullOrEmpty($WebsiteTargetRepository))) {
|
||||
Write-Output "Publish Update File $zipUpdateFileName to $WebsiteTargetRepository"
|
||||
$update_file_content_string = Get-Content "$ReleaseFolder\$zipUpdateFileName" | Out-String
|
||||
Set-GitHubContent -OwnerName $WebsiteTargetOwner -RepositoryName $WebsiteTargetRepository -Path $zipUpdateFileName -CommitMessage "Build $ModifiedTagName" -Content $update_file_content_string -BranchName main
|
||||
} else {
|
||||
Write-Warning "WARNING: Update file does not exist: $ReleaseFolder\$zipUpdateFileName"
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Output "ReleaseFolder not found"
|
||||
}
|
||||
|
||||
|
||||
# build zip update file
|
||||
$zipFile = Get-ChildItem -Path "$releaseFolder\*.zip" | sort LastWriteTime | select -last 1
|
||||
$zipUpdateContents = New-ZipUpdateFileContent -ZipFile $zipFile -TagName $TagName
|
||||
$zipUpdateFileName = Resolve-UpdateCheckFileName -UpdateChannel $UpdateChannel -Type Portable
|
||||
Write-Output "`n`nZip Update Check File Contents ($zipUpdateFileName)`n------------------------------"
|
||||
Tee-Object -InputObject $zipUpdateContents -FilePath "$releaseFolder\$zipUpdateFileName"
|
||||
Write-Output "End $($PSCmdlet.MyInvocation.MyCommand)"
|
||||
Write-Output ""
|
||||
|
||||