/
PropPageUserControlBase.vb
4160 lines (3645 loc) · 205 KB
/
PropPageUserControlBase.vb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
' Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.
Imports System.Collections.Specialized
Imports System.ComponentModel
Imports System.ComponentModel.Design
Imports System.Drawing
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Windows.Forms
Imports System.Windows.Forms.Design
Imports Microsoft.VisualStudio.Editors.AppDesInterop
Imports Microsoft.VisualStudio.Editors.AppDesInterop.NativeMethods
Imports Microsoft.VisualStudio.Editors.AppDesInterop.Win32Constant
Imports Microsoft.VisualStudio.ManagedInterfaces.ProjectDesigner
Imports Microsoft.VisualStudio.Shell
Imports Microsoft.VisualStudio.Shell.Interop
Imports Common = Microsoft.VisualStudio.Editors.AppDesCommon
Imports VB = Microsoft.VisualBasic
Imports VSITEMID = Microsoft.VisualStudio.Editors.VSITEMIDAPPDES
Namespace Microsoft.VisualStudio.Editors.PropertyPages
''' <summary>
''' Base class for managed property pages internal used in VisualStudio
''' </summary>
<ComVisible(False)>
Public Class PropPageUserControlBase
Inherits UserControl
Implements IPropertyPageInternal
Implements IVsProjectDesignerPage
Implements IVsDebuggerEvents
Implements IVsBroadcastMessageEvents
#Region " Windows Form Designer generated code "
Private _uiShell5Service As IVsUIShell5
Public Sub New()
Me.New(Nothing)
End Sub
Protected Sub New(serviceProvider As ServiceProvider)
MyBase.New()
SuspendLayout()
Text = My.Resources.Designer.PPG_PropertyPageControlName
AccessibleRole = AccessibleRole.PropertyPage
_serviceProvider = serviceProvider
'This call is required by the Windows Form Designer.
InitializeComponent()
'Ensure we set out colors based on the current theme
OnThemeChanged()
'Add any initialization after the InitializeComponent() call
AddToRunningTable()
ResumeLayout(False) 'False: layout will happen later, not needed here
End Sub
'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(disposing As Boolean)
'Release unmanaged resources
If disposing Then
CleanupCOMReferences()
If IsInProjectCheckoutSection Then
'It is possible for a source code checkout operation to cause a project reload (and thus the closing/disposal of
' the project designer and all property pages) during an attempt to change a property's value. It is difficult
' for WinForms to gracefully recover from the disposal of the property page and controls in the middle of any apply,
' so we delay the main Dispose() until after the apply is finished.
'We do go ahead and get rid of COM references and do general clean-up, though. This includes removing our
' listening to events from the environment, etc.
'We do *not* call in to the base's Dispose(), because that will get rid of the controls, and that's what we're
' trying to avoid right now.
Trace.WriteLine("***** PropPageUserControlBase.Dispose(): Being forcibly deactivated during an checkout. Disposal of controls will be delayed until after the current callstack is finished.")
_projectReloadedDuringCheckout = True
For Each page As PropPageUserControlBase In _childPages.Values
Dim HostDialog As PropPageHostDialog = GetPropPageHostDialog(page)
' Notify child pages that the project reloaded happened if they are
' listening for such a change
If HostDialog IsNot Nothing AndAlso
HostDialog.PropPage IsNot Nothing AndAlso
HostDialog.PropPage.IsInProjectCheckoutSection Then
HostDialog.PropPage._projectReloadedDuringCheckout = True
End If
Next page
Else
Debug.Assert(_suspendPropertyChangeListeningDispIds.Count = 0, "Missing a ResumePropertyChangeListening() call?")
RemoveFromRunningTable()
If _components IsNot Nothing Then
_components.Dispose()
End If
'Dispose all child pages. A child page normally stays open if an exception
' occurs during the OK button click, so we have to force it closed.
For Each page As PropPageUserControlBase In _childPages.Values
Dim HostDialog As PropPageHostDialog = GetPropPageHostDialog(page)
If HostDialog IsNot Nothing Then
If HostDialog.PropPage IsNot Nothing Then
HostDialog.Dispose()
End If
HostDialog.Dispose()
End If
Next page
_childPages.Clear()
MyBase.Dispose(disposing)
End If 'If m_fIsApplying...
End If 'If disposing...
End Sub
'Required by the Windows Form Designer
Private ReadOnly _components As IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<DebuggerStepThrough()> Private Sub InitializeComponent()
SuspendLayout()
'
'PropPageUserControlBase
'
Name = "PropPageUserControlBase"
Size = New Size(528, 296)
AutoSize = False
ResumeLayout(False)
PerformLayout()
End Sub
#End Region
''' <summary>
''' Specifies the source or cause of a property change notification
''' </summary>
Public Enum PropertyChangeSource
Direct 'This property is being changed directly through a PropertyControlData on the page. Normally should be ignored, the UI will be updated automatically after the change.
Indirect 'Change was caused indirectly when a PropertyControlData changed a separate property on the page. For instance, changing the StartupObject property causes the project system to modify several other properties.
External 'The property was changed externally to the page (via another page or through the DTE or other separate UI), or else it was not changed through a PropertyControlData.
End Enum
'Used for caching property change notification until after Apply is done
Private NotInheritable Class PropertyChange
Public ReadOnly DispId As Integer
Public ReadOnly Source As PropertyChangeSource
Public Sub New(DispId As Integer, Source As PropertyChangeSource)
Me.DispId = DispId
Me.Source = Source
End Sub
End Class
#Disable Warning IDE1006 ' Naming Styles (Compat)
'True iff the property page is currently in initialization code
Public m_fInsideInit As Boolean
'Property page is currently dirty
Protected m_IsDirty As Boolean
'Backs the CanApplyNow property
Private _canApplyNow As Boolean = True
'The site passed in to SetPageSite. May be Nothing if not currently sited.
Private _site As IPropertyPageSiteInternal
'May be used by derived property pages to cache their PropertyControlData in their ControlData property override.
' Not used directly by the architecture, which should always make a get property call to ControlData.
Protected m_ControlData As PropertyControlData()
'The set of objects that were passed in to SetObjects. These are the objects whose properties are displayed in the property page.
' NOTE: Normally you should *not* use this property directly, but rather should call RawPropertiesObjects from the
' associated PropertyControlData, as it may override the set of objects to use.
Public m_Objects As Object()
'An array of (extended) property descriptor collections pulled from each of the objects passed in
' to SetObjects.
Public m_ObjectsPropertyDescriptorsArray() As PropertyDescriptorCollection
'The set of extended objects based on the objects that were passed in to SetObjects.
' NOTE: Normally you should *not* use this property directly, but rather should call ExtendedPropertiesObjects from the
' associated PropertyControlData, as it may override the set of objects to use.
Public m_ExtendedObjects As Object()
'A collection of (extended) property descriptors which have been collected from the project itself. These
' properties were *not* passed in to us through SetObjects, and are not configuration-dependent. Some
' configuration-dependent pages need to display certain non-configuration properties, which can be
' found in this collection. Note that for a non-configuration page, this set of property descriptors
' will be the same as the set that was passed in to us through SetObjects.
Public m_CommonPropertyDescriptors As PropertyDescriptorCollection
Protected m_ScalingCompleted As Boolean
#Enable Warning IDE1006 ' Naming Styles
'The DTE object associated with the objects passed to SetObjects. If there is none, this is Nothing.
Private _dte As EnvDTE.DTE
'The EnvDTE.Project object associated with the objects passed to SetObjects. If there is none, this is Nothing.
Private _dteProject As EnvDTE.Project
' Hook up to build events so we can enable/disable the property
' page while building
Private WithEvents _buildEvents As EnvDTE.BuildEvents
'The ProjectProperties object from a VSLangProj-based project (VB, C#). Used for querying
' common project properties in these types of projects. Note that this object was *not* passed
' in through SetObjects, rather we obtained it by querying for the project. It is used
' for common property handling. See comments for m_CommonPropertyDescriptors.
Private _projectPropertiesObject As VSLangProj.ProjectProperties
'Cached result from RawPropertiesObjectsOfAllProperties
Private _cachedRawPropertiesSuperset As Object()
Private _serviceProvider As ServiceProvider 'Cached service provider
Private _projectHierarchy As IVsHierarchy 'The IVsHierarchy for the current project
' Cached IVsDebugger from shell in case we don't have a service provider at
' shutdown so we can undo our event handler
Private _vsDebugger As IVsDebugger
Private _vsDebuggerEventsCookie As UInteger
'True iff multiple projects are currently selected
Private _multiProjectSelect As Boolean
Private _uiShellService As IVsUIShell
Private _uiShell2Service As IVsUIShell2
Private Shared ReadOnly s_runningPropertyPages As New ArrayList
'Child property pages that have been shown are cached here
Private ReadOnly _childPages As New Dictionary(Of Type, PropPageUserControlBase)
Private _pageRequiresScaling As Boolean = True
' When true, the dialog is not scaled automatically
' Currently only used by the Publish page because it isn't a normal page
Private _manualPageScaling As Boolean
'Backcolor for all property pages
<Obsolete("Colors should be retrieved directly from the theming service")>
Public Shared ReadOnly PropPageBackColor As Color = SystemColors.Control
Private _activated As Boolean = True
Private _inDelayValidation As Boolean
'Saves whether the page should be enabled or disabled, according to the page's subclass.
' However, this state is only honored while the project is not running, etc.
Private _pageEnabledState As Boolean = True
'A list of all connected property listeners
Private ReadOnly _propertyListeners As New ArrayList '(Of PropertyListener)
'Whether the page should be enabled based only on the debug state of the application
Private _pageEnabledPerDebugMode As Boolean = True
'Whether the page should be enabled based on if we are building or not
Private _pageEnabledPerBuildMode As Boolean = True
'True iff this property page is configuration-specific (like the compile page) instead of
' configuration-independent (like the application pages)
Private _fConfigurationSpecificPage As Boolean
'Dispids which the page is changing manually, and which are to be ignored while listening for
' property changes
Private ReadOnly _suspendPropertyChangeListeningDispIds As New List(Of Integer)
'DISPID_UNKNOWN - This is part of the public API so can't be removed, but be careful to ensure its value matches the Win32 value (-1)
Public DISPID_UNKNOWN As Integer = Win32Constant.DISPID_UNKNOWN
'Cookie for use with IVsShell.{Advise,Unadvise}BroadcastMessages
Private _cookieBroadcastMessages As UInteger
Private _vsShellForUnadvisingBroadcastMessages As IVsShell
'True if we're in the middle of an Apply
Private _fIsApplying As Boolean
'A list of properties from which we received a property change notification
' while another property on the page while being changed or an apply was
' in progress. They will be sent off after the change/apply is done.
Private _cachedPropertyChanges As List(Of PropertyChange)
'True if the property page was forcibly closed (by SCC) during an apply. In this case, we want to
' delay disposing our controls, and exit the apply and events as soon as possible to avoid possible
' problems since the project may have been closed down from under us.
Private _projectReloadedDuringCheckout As Boolean
'When positive, the property page is in a project checkout section, which means that the project
' file might get checked out, which means that it is possible the checkout will cause a reload
' of the project.
Private _checkoutSectionCount As Integer
''' <summary>
''' Return a set of control groups. We validate the editing inside a control group when the focus moves out of it.
''' There is one exception: the delay validation only works for warning messages, fatal errors are still reported immediately when the change is applied
''' </summary>
''' <remarks>Only pages supporting delay-validation need return value</remarks>
Protected Overridable ReadOnly Property ValidationControlGroups As Control()()
Get
Return Nothing
End Get
End Property
''' <summary>
''' Return a boolean value that indicates whether or not the control supports the color theming service
''' </summary>
Public Overridable ReadOnly Property SupportsTheming As Boolean
Get
Return False
End Get
End Property
Public ReadOnly Property ProjectHierarchy As IVsHierarchy
Get
Return _projectHierarchy
End Get
End Property
''' <summary>
''' Determines if the page should be scaled automatically.
''' WARNING: It is recommended to turn this off and use AutoScaleMode.Font.
''' </summary>
''' <value>True if the page should not be scaled automatically</value>
Protected Property PageRequiresScaling As Boolean 'CONSIDER: This should be opt-in, not opt-out
Get
Return _pageRequiresScaling
End Get
Set
_pageRequiresScaling = Value
End Set
End Property
''' <summary>
''' Determines if the page should be scaled in SetObjects.
''' </summary>
''' <value>true if the page should not be scaled</value>
''' <remarks>Used only by the Publish page for now</remarks>
Protected Property ManualPageScaling As Boolean
Get
Return _manualPageScaling
End Get
Set
_manualPageScaling = Value
End Set
End Property
''' <summary>
''' Return true if the page can be resized...
''' </summary>
Public Overridable ReadOnly Property PageResizable As Boolean
Get
Return False
End Get
End Property
''' <summary>
''' Determines whether the property page is enabled or not. However, it also takes into
''' consideration other states (e.g., whether the application is running or in break mode),
''' and keeps the page disabled during break mode. Once the app stops, the page is set
''' to the state requested here.
''' </summary>
Protected Shadows Property Enabled As Boolean
Get
Return _pageEnabledState
End Get
Set
_pageEnabledState = Value
SetEnabledState()
End Set
End Property
''' <summary>
''' Determines whether the property page is activated
''' </summary>
Protected ReadOnly Property IsActivated As Boolean
Get
Return _activated
End Get
End Property
''' <summary>
''' Sets whether the page is actually enabled or disabled, based on the protected Enabled
''' property plus internal state, such as whether the project is in run mode.
''' </summary>
Private Sub SetEnabledState()
Dim ShouldBeEnabled As Boolean = _pageEnabledState AndAlso _pageEnabledPerDebugMode AndAlso _pageEnabledPerBuildMode AndAlso m_Objects IsNot Nothing
MyBase.Enabled = ShouldBeEnabled
End Sub
Private Sub AddToRunningTable()
SyncLock s_runningPropertyPages
s_runningPropertyPages.Add(Me)
End SyncLock
End Sub
Private Sub RemoveFromRunningTable()
SyncLock s_runningPropertyPages
s_runningPropertyPages.Remove(Me)
End SyncLock
End Sub
''' <summary>
''' Enumerates the running property pages to find the requested property value.
''' </summary>
''' <param name="dispid">DISPID of property value being requested.</param>
''' <param name="obj">Value of property being requested.</param>
''' <returns>True if property value returned, False if property not found.</returns>
''' <remarks>If multiple pages host a property, this will return the first value found.</remarks>
Protected Shared Function GetPropertyFromRunningPages(SourcePage As PropPageUserControlBase, dispid As Integer, ByRef obj As Object) As Boolean
Debug.Assert(SourcePage.CommonPropertiesObject IsNot Nothing)
SyncLock s_runningPropertyPages
For Each page As PropPageUserControlBase In s_runningPropertyPages
'We must restrict the set of pages that we inspect to those running against the same project.
' Therefore we check for a match against CommonPropertiesObject. Note that checking against
' DTEProject is not okay because not all project types support that.
If page.CommonPropertiesObject IsNot Nothing AndAlso page.CommonPropertiesObject Is SourcePage.CommonPropertiesObject Then
If page.GetProperty(dispid, obj) Then
Return True
End If
End If
Next
End SyncLock
Return False
End Function
''' <summary>
''' Returns the actual site (IPropertyPageSite, rather than IPropertyPageSiteInternal) for the property page
''' </summary>
Protected ReadOnly Property PropertyPageSite As OLE.Interop.IPropertyPageSite
Get
If _site IsNot Nothing Then
Dim OleSite As OLE.Interop.IPropertyPageSite =
DirectCast(_site.GetService(GetType(OLE.Interop.IPropertyPageSite)), OLE.Interop.IPropertyPageSite)
Debug.Assert(OleSite IsNot Nothing, "IPropertyPageSiteInternal didn't provide an IPropertyPageSite through GetService")
Return OleSite
End If
Return Nothing
End Get
End Property
''' <summary>
''' Does a GetService call via the property page site
''' </summary>
Protected ReadOnly Property GetServiceFromPropertyPageSite(ServiceType As Type) As Object
Get
If _site IsNot Nothing Then
Dim OleSite As OLE.Interop.IPropertyPageSite = PropertyPageSite
Dim sp As IServiceProvider = TryCast(OleSite, IServiceProvider)
Debug.Assert(sp IsNot Nothing, "Property page site didn't provide a managed service provider")
If sp IsNot Nothing Then
Return sp.GetService(ServiceType)
End If
End If
Return Nothing
End Get
End Property
''' <summary>
''' Retrieves the object to be used for querying for "common" property values. The object
''' used may vary, depending on the project type and what it supports.
''' </summary>
''' <remarks>
''' See "About 'common' properties" in PropertyControlData for information on "common" properties.
''' </remarks>
Public ReadOnly Property CommonPropertiesObject As Object
Get
If _projectPropertiesObject IsNot Nothing Then
'Used by VB and C#-based projects
Return _projectPropertiesObject
Else
'C++ projects.
If _dteProject IsNot Nothing Then
Debug.Assert(_dteProject.Object IsNot Nothing)
Return _dteProject.Object
Else
Return Nothing 'This is possible if we've already been cleaned up
End If
End If
End Get
End Property
''' <summary>
''' Returns the raw set of objects in use by this property page. This will generally be the set of objects
''' passed in to the page through SetObjects. However, it may be modified by subclasses to contain a superset
''' or subset for special purposes.
''' </summary>
Protected Function RawPropertiesObjects(Data As PropertyControlData) As Object()
Return Data.RawPropertiesObjects
End Function
''' <summary>
''' Returns the extended objects created from the raw set of objects in use by this property page. This will generally be
''' based on the set of objects passed in to the page through SetObjects. However, it may be modified by subclasses to
''' contain a superset or subset for special purposes.
''' </summary>
Protected Function ExtendedPropertiesObjects(Data As PropertyControlData) As Object()
Return Data.ExtendedPropertiesObjects
End Function
''' <summary>
''' True iff this property page is configuration-specific (like the compile page) instead of
''' configuration-independent (like the application pages)
''' </summary>
Public ReadOnly Property IsConfigurationSpecificPage As Boolean
Get
Return _fConfigurationSpecificPage
End Get
End Property
''' <summary>
''' Causes listening to property changes to be suspended until an equal number of
''' ResumePropertyChangeListening calls have been made
''' </summary>
''' <param name="DispId">The DISPID to ignore changes from. If DISPID_UNKNOWN, then all property changes will be ignored.</param>
Public Sub SuspendPropertyChangeListening(DispId As Integer)
_suspendPropertyChangeListeningDispIds.Add(DispId)
End Sub
''' <summary>
''' Causes listening to property changes to be resumed after an equal number of
''' SuspendPropertyChangeListening/ResumeropertyChangeListening pairs have been made
''' </summary>
Public Sub ResumePropertyChangeListening(DispId As Integer)
_suspendPropertyChangeListeningDispIds.Remove(DispId)
CheckPlayCachedPropertyChanges()
End Sub
''' <summary>
''' Returns true if any property on the current page is currently being changed.
''' </summary>
Private Function PropertyOnPageBeingChanged() As Boolean
Return _suspendPropertyChangeListeningDispIds.Count > 0
End Function
''' <summary>
''' Gets the list of all raw properties objects from all properties hosted on this page
''' </summary>
''' <remarks>
''' This may be a superset of the objects passed in to SetObjects because some properties can override their
''' behavior and display a different set of objects. These are cached after the first call and not updated
''' again until another SetObjects call.
''' </remarks>
Private ReadOnly Property RawPropertiesObjectsOfAllProperties As Object()
Get
If _cachedRawPropertiesSuperset Is Nothing Then
If m_Objects Is Nothing Then
Debug.Fail("m_Objects is nothing")
Return Array.Empty(Of Object)
End If
Dim Superset As New Hashtable(m_Objects.Length)
For Each Data As PropertyControlData In ControlData
Dim RawObjects As Object() = Data.RawPropertiesObjects
Debug.Assert(RawObjects IsNot Nothing)
If RawObjects IsNot Nothing Then
For Each Obj As Object In RawObjects
If Not Superset.ContainsKey(Obj) Then
Superset.Add(Obj, Obj)
End If
Next
End If
Next
ReDim _cachedRawPropertiesSuperset(Superset.Count - 1)
Superset.Values.CopyTo(_cachedRawPropertiesSuperset, 0)
End If
Return _cachedRawPropertiesSuperset
End Get
End Property
''' <summary>
''' Attempts to get a property's current value, without doing any type conversion. Returns
''' True on success and False if the property is not found.
''' </summary>
''' <param name="dispid">The property's DISPID to look up.</param>
''' <param name="obj">[out] Returns the property's value, if found.</param>
''' <returns>True on success and False if the property is not found.</returns>
Protected Overridable Function GetProperty(dispid As Integer, ByRef obj As Object) As Boolean
obj = Nothing
For Each _controlData As PropertyControlData In ControlData
If _controlData.DispId = dispid Then
If _controlData.IsMissing Then
Return False
End If
'Debug.Assert(Not _controlData.IsConfigurationSpecificProperty) 'CONSIDER: probably convert this function into GetCommonProperty, we're okay if it's a common property (then there's only one extender to work with) - NOTE: we hit this on the C# build page when you check the XML Documentation File checkbox if the textbox is empty (querying for OutputPath)
obj = _controlData.GetPropertyValueNative(m_ExtendedObjects(0)) 'CONSIDER: This is what we've been doing (passing in first extender object), but it looks wrong
Return True
End If
Next
Return False
End Function
''' <summary>
''' Attempts to retrieve the current value of a property as currently stored in any page
''' in the project designer, even if that property has not yet been persisted (non-immediate
''' apply mode). It does this by first trying to locate the property in the PropertyControlData
''' of all other pages, then by checking if it's a common property, then finally by checking the
''' page's own PropertyControlData info.
''' </summary>
''' <param name="dispid">The DISPID of the property to search for</param>
''' <param name="PropertyName">The property name of the property to search for. Required for a common properties
''' look-up if this property is not defined as a PropertyControlData on the calling page. Otherwise it's optional.</param>
''' <param name="obj"></param>
''' <remarks>
''' The property name and DISPIDs must both refer to the same property.
''' </remarks>
Protected Function GetCurrentProperty(dispid As Integer, PropertyName As String, ByRef obj As Object) As Boolean
PropertyName = Common.NothingToEmptyString(PropertyName) 'Nothing not allowed in GetCommonPropertyDescriptor()
'Check current property pages
If GetPropertyFromRunningPages(Me, dispid, obj) Then
#If DEBUG Then
'Since we don't know which source the property value will come from, let's ensure that enough
' information was given that it *could* come from any source. Otherwise the function may
' sometimes succeed (if it's found in an open property page) and sometimes not (when that other
' page isn't open).
Dim IsCommonProperty As Boolean = GetCommonPropertyDescriptor(PropertyName) IsNot Nothing
Dim IsPropertyOnThisPage As Boolean = False
For Each _controlData As PropertyControlData In ControlData
If _controlData.DispId = dispid Then
Debug.Assert(Not _controlData.IsMissing, "How could this property get successfully retrieved from one page but be IsMissing in another?")
Debug.Assert(_controlData.PropertyName.Equals(PropertyName, StringComparison.Ordinal), "GetCurrentProperty: PropertyName doesn't match DISPID")
IsPropertyOnThisPage = True
Exit For
End If
Next
Debug.Assert(IsCommonProperty OrElse IsPropertyOnThisPage,
"GetCurrentProperty: Property was found in an open page, so this time the function will succeed. However, the property was not " _
& "found as a common property or a property on this page, so the same query would fail if the other page were not open. This probably " _
& "indicates an error in the caller.")
#End If
'Property value successfully retrieved
Return True
End If
'If it's not available on an open page, try the common properties
Dim prop As PropertyDescriptor
prop = GetCommonPropertyDescriptor(PropertyName)
If prop IsNot Nothing Then
obj = GetCommonPropertyValueNative(prop)
Return True
End If
'Try getting the value from a PropertyControlData on the current page.
If GetProperty(dispid, obj) Then
Return True
End If
Return False
End Function
''' <summary>
''' Restore the control's current value from the InitialValue (used when the user cancels the dialog)
''' </summary>
Public Overridable Sub RestoreInitialValues()
Dim InsideInitSave As Boolean = m_fInsideInit
m_fInsideInit = True
Try
For Each _controlData As PropertyControlData In ControlData
_controlData.RestoreInitialValue()
Next _controlData
Finally
m_fInsideInit = InsideInitSave
'Update current dirty state for the page
IsDirty = IsAnyPropertyDirty()
End Try
End Sub
''' <summary>
''' Updates all properties so that they refresh their UI from the property
''' store's current values
''' </summary>
Public Overridable Sub RefreshPropertyValues()
Common.Switches.TracePDProperties(TraceLevel.Warning, "*** [" & [GetType].Name & "] Refreshing all property values")
Dim InsideInitSave As Boolean = m_fInsideInit
m_fInsideInit = True
Try
For Each Data As PropertyControlData In ControlData
Data.RefreshValue()
Next
Finally
m_fInsideInit = InsideInitSave
IsDirty = IsAnyPropertyDirty()
End Try
End Sub
''' <summary>
''' Indicates if the user has selected multiple projects in the Solution Explorer
''' </summary>
''' <remarks>When the user selects multiple projects, certain functionality is disabled</remarks>
<DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)>
Public ReadOnly Property MultiProjectSelect As Boolean
Get
Return _multiProjectSelect
End Get
End Property
<DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)>
Protected ReadOnly Property ServiceProvider As ServiceProvider
Get
If _serviceProvider Is Nothing Then
Dim isp As OLE.Interop.IServiceProvider = Nothing
If _site IsNot Nothing Then
isp = TryCast(_site.GetService(GetType(OLE.Interop.IServiceProvider)), OLE.Interop.IServiceProvider)
End If
If isp Is Nothing AndAlso DTE IsNot Nothing Then
isp = TryCast(DTE, OLE.Interop.IServiceProvider)
End If
If isp IsNot Nothing Then
_serviceProvider = New ServiceProvider(isp)
End If
End If
Return _serviceProvider
End Get
End Property
''' <summary>
''' Returns the DTE object associated with the objects passed to SetObjects. If there is none, returns Nothing.
''' </summary>
<DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)>
Protected ReadOnly Property DTE As EnvDTE.DTE
Get
Return _dte
End Get
End Property
''' <summary>
''' Returns the EnvDTE.Project object associated with the objects passed to SetObjects. If there is none, returns Nothing.
''' </summary>
<DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)>
Public ReadOnly Property DTEProject As EnvDTE.Project
Get
Return _dteProject
End Get
End Property
''' <summary>
''' Returns the ProjectProperties object from a VSLangProj-based project (VB and C#). Used for querying
''' common project properties in these types of projects. Should only be used if you are certain of the
''' project type. Otherwise, use CommonPropertiesObject.
''' </summary>
<DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)>
Public ReadOnly Property ProjectProperties As VSLangProj.ProjectProperties
Get
Return _projectPropertiesObject
End Get
End Property
'Enables or disables the given control on the page. However, if the control is associated with
' a property on the page, and that property is hidden or read-only, the enabled state of the control
' will not be changed.
Protected Sub EnableControl(control As Control, enabled As Boolean)
If control Is Nothing Then
Debug.Fail("control is nothing")
Return
End If
For Each pcd As PropertyControlData In ControlData
If pcd.FormControl Is control OrElse
(pcd.AssociatedControls IsNot Nothing AndAlso Array.IndexOf(pcd.AssociatedControls, control) >= 0) Then
'The control is associated with this property control data
pcd.EnableAssociatedControl(control, enabled)
Return
End If
Next
'If it wasn't associated with a PropertyControlData, we can go ahead and enable/disable it directly
control.Enabled = enabled
End Sub
#Region "Rude checkout support"
''' <summary>
'''Before any code which may check out the project file, a property page must call this function. This
''' alerts the page to the fact that we might get an unexpected Dispose() during this period, and if so,
''' to interpret it as meaning that the project file was checked out and updated, causing a project
''' reload.
''' </summary>
Protected Sub EnterProjectCheckoutSection()
Debug.Assert(_checkoutSectionCount >= 0, "Bad m_CheckoutCriticalSectionCount count")
_checkoutSectionCount += 1
End Sub
''' <summary>
'''After any code which may check out the project file, a property page must call this function. This
''' alerts the page to the fact that the code which might cause a project checkout is finished running.
''' If a Dispose occurred during the interval between the EnterProjectCheckoutSection and
''' LeaveProjectCheckoutSection calls, the disposal of the controls on the property page will be delayed
''' (but CleanUpCOMReferences *will* get called immediately) by via a PostMessage() call to allow
''' the property page to more easily recover from this situation. The flag ReloadedDuringCheckout
''' will be set to true. After the project file checkout is successful, derived property pages should
''' check this flag and exit as soon as possible if it is true. If it's true, the project file probably
''' has been zombied, and the latest changes to the page made by the user will be lost, so there will be
''' no need to attempt to save properties.
'''
''' Expected coding pattern:
'''
''' EnterProjectCheckoutSection()
''' Try
''' ...
''' CallMethodWhichMayCauseProjectFileCheckout
''' If ReloadedDuringCheckout Then
''' Return
''' End If
''' ...
''' Finally
''' LeaveProjectCheckoutSection()
''' End Try
''' </summary>
Protected Sub LeaveProjectCheckoutSection()
_checkoutSectionCount -= 1
Debug.Assert(_checkoutSectionCount >= 0, "Mismatched EnterProjectCheckoutSection/LeaveProjectCheckoutSection calls")
If _checkoutSectionCount = 0 AndAlso _projectReloadedDuringCheckout Then
Try
Trace.WriteLine("**** Deactivate happened during a checkout. Now that Apply is finished, queueing a delayed Dispose() call for the page.")
If Not IsHandleCreated Then
CreateHandle()
End If
Debug.Assert(IsHandleCreated AndAlso Not Handle.Equals(IntPtr.Zero), "We should have a handle still. Without it, BeginInvoke will fail.")
BeginInvoke(New MethodInvoker(AddressOf DelayedDispose))
Catch ex As Exception When Common.ReportWithoutCrash(ex, "Failed to queue a delayed Dispose for the property page", NameOf(PropPageUserControlBase))
' At this point, all we can do is to avoid crashing the shell.
End Try
End If
End Sub
''' <summary>
''' If true, the project has been reloaded between a call to EnterProjectCheckoutSection and
''' LeaveProjectCheckoutSection. See EnterProjectCheckoutSection() for more information.
''' </summary>
Public ReadOnly Property ProjectReloadedDuringCheckout As Boolean
Get
Return _projectReloadedDuringCheckout
End Get
End Property
''' <summary>
''' If true, a call to EnterProjectCheckoutSection has been made, and the matching LeaveProjectCheckoutSection
''' call has not yet been made.
''' </summary>
Protected ReadOnly Property IsInProjectCheckoutSection As Boolean
Get
Return _checkoutSectionCount > 0
End Get
End Property
''' <summary>
''' Called in a delayed fashion (via PostMessage) after a LeaveProjectCheckoutSection call if the
''' project was forcibly reloaded during the project checkout section.
''' </summary>
Private Sub DelayedDispose()
'Set this flag back to false so that subclasses which override Dispose() know when it's
' safe to Dispose of their controls.
_projectReloadedDuringCheckout = False
Dispose()
End Sub
''' <summary>
''' Checks out the project file. After calling this function, the caller should check
''' the ProjectReloaded flag and exit if it's true.
''' </summary>
Public Sub CheckoutProjectFile(ByRef ProjectReloaded As Boolean)
Dim SccManager As New AppDesDesignerFramework.SourceCodeControlManager(ServiceProvider, ProjectHierarchy)
EnterProjectCheckoutSection()
Try
Common.Switches.TracePDProperties(TraceLevel.Warning, "Making sure the project file is checked out...")
SccManager.ManageFile(DTEProject.FullName)
SccManager.EnsureFilesEditable()
Finally
ProjectReloaded = ProjectReloadedDuringCheckout
LeaveProjectCheckoutSection()
End Try
End Sub
#End Region
#Region "IPropertyPageInternal"
''' <summary>
''' Calls apply method on the page and child pages
''' Notifies class after completion by calling OnApplyComplete
''' </summary>
''' <remarks>Called by ComClass wrapper which maps IPropertyPage2::Apply to here</remarks>
Private Sub IPropertyPageInternal_Apply() Implements IPropertyPageInternal.Apply
Apply()
End Sub
''' <summary>
''' Provides keyword for help system lookup.
''' </summary>
Protected Overridable Function GetF1HelpKeyword() As String
Return Nothing
End Function
''' <summary>
''' Implements IPropertyPageInternal.Help
''' </summary>
''' <param name="HelpDir">Not used.</param>
Private Sub IProperyPageInternal_Help(HelpDir As String) Implements IPropertyPageInternal.Help
AppDesDesignerFramework.DesignUtil.DisplayTopicFromF1Keyword(ServiceProvider, GetF1HelpKeyword)
End Sub
''' <summary>
''' Brings up help for the given help topic
''' </summary>
''' <param name="HelpTopic">The help string that identifiers the help topic.</param>
Public Overridable Sub Help(HelpTopic As String)
AppDesDesignerFramework.DesignUtil.DisplayTopicFromF1Keyword(ServiceProvider, HelpTopic)
End Sub
Protected Overridable Function IsPageDirty() As Boolean Implements IPropertyPageInternal.IsPageDirty
If IsDirty Then
Return True
End If
'Check child pages for any dirty state
For Each page As PropPageUserControlBase In _childPages.Values
If page.IsPageDirty() Then
Return True
End If
Next page
Return False
End Function
''' <summary>
''' Removes references to anything that was passed in to SetObjects
''' </summary>
Protected Overridable Sub CleanupCOMReferences()
Dim i As Integer
If m_Objects IsNot Nothing Then
For i = 0 To m_Objects.Length - 1
m_Objects(i) = Nothing
Next i
End If
m_Objects = Nothing
If m_ExtendedObjects IsNot Nothing Then
For i = 0 To m_ExtendedObjects.Length - 1
m_ExtendedObjects(i) = Nothing
Next
End If
m_ExtendedObjects = Nothing
DisconnectPropertyNotify()
DisconnectDebuggerEvents()
DisconnectBroadcastMessages()
DisconnectBuildEvents()
_dteProject = Nothing
_dte = Nothing
_projectPropertiesObject = Nothing
_serviceProvider = Nothing
_site = Nothing
_cachedRawPropertiesSuperset = Nothing
'Ask all child pages to clean themselves up
For Each page As PropPageUserControlBase In _childPages.Values
page.SetObjects(Nothing)
Next page
End Sub
''' <param name="objects"></param>
Private Sub CheckMultipleProjectsSelected(objects() As Object)
If objects Is Nothing OrElse objects.Length <= 1 Then
'Cannot be multiple projects
Else
Dim NextProj, FirstProj As IVsHierarchy
FirstProj = GetProjectHierarchyFromObject(objects(0))
For i As Integer = 1 To objects.Length - 1
NextProj = GetProjectHierarchyFromObject(objects(i))
If NextProj Is Nothing OrElse NextProj IsNot FirstProj Then
_multiProjectSelect = True
Return
End If
Next
End If
_multiProjectSelect = False