forked from dotnet/project-system
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathResourceFile.vb
1721 lines (1455 loc) · 79.3 KB
/
ResourceFile.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.
Option Explicit On
Option Strict On
Option Compare Binary
Imports System.CodeDom.Compiler
Imports System.ComponentModel.Design
Imports System.IO
Imports System.Resources
Imports System.Windows.Forms
Imports System.Xml
Imports Microsoft.VisualStudio.Designer.Interfaces
Imports Microsoft.VisualStudio.Editors.Common
Imports Microsoft.VisualStudio.Shell
Imports Microsoft.VisualStudio.Shell.Interop
Imports Microsoft.VSDesigner
Imports Microsoft.Win32
Namespace Microsoft.VisualStudio.Editors.ResourceEditor
''' <summary>
''' A representation of a resx file (essentially a ResourceCollection). Wraps the
''' reading and writing of the file, plus the management of the resources
''' (instances of the Resource class) within it.
''' </summary>
Friend Class ResourceFile
Implements IDisposable
Implements ResourceTypeEditor.IResourceContentFile
#Region "Fields"
'A pointer to the host's IComponentChangeService. We use this to get notified
' when components (Resource instances) are added/removed from the collection
' (both when we do it manually and when Undo/Redo does it for us), etc.
Private WithEvents _componentChangeService As IComponentChangeService
'Our set of resources
Private ReadOnly _resources As Dictionary(Of String, Resource)
'Metadata from the resource file so we can write them back out when saving the file.
Private ReadOnly _resourceFileMetadata As List(Of DictionaryEntry)
'The root component for the resource editor. Cannot be Nothing.
Private ReadOnly _rootComponent As ResourceEditorRootComponent
'A pointer to the task provider service. Gives us access to the VS task list.
Private _errorListProvider As ErrorListProvider
'The main thread we're running on. Used just to verify that idle time processing
' is always on the main thread.
Private ReadOnly _mainThread As System.Threading.Thread
'Holds a set of tasks for each Resource that has any task list entries.
Private ReadOnly _resourceTaskSets As New Dictionary(Of Resource, ResourceTaskSet)
'A set of resources that need to be checked for errors during idle-time
' processing.
Private ReadOnly _resourcesToDelayCheckForErrors As New HashSet(Of Resource)
' Indicate whether we should suspend delay checking temporary...
Private _delayCheckSuspended As Boolean
'True iff we're in the middle of adding or removing a Resource through AddResource or RemoveResource. If not,
' and we get notified of an add by component changing service, it means an external source has added/removed
' the resource (i.e., Undo/Redo).
Private _addingRemovingResourcesInternally As Boolean
'The base path to use for resolving relative paths in the resx file. This should be the
' directory where the resx file lives.
Private ReadOnly _basePath As String
' We get ResourceWrite from this environment service
' the reason is some projects (device project) need write the resource file in v1.x format, but other projects write in 2.0 format.
Private ReadOnly _resxService As IResXResourceService
'True if the original file bases on alphabetized order, we will keep this style...
Private _alphabetizedOrder As Boolean = True
'The service provider provided by the designer host
Private ReadOnly _serviceProvider As IServiceProvider
' Asynchronous flush & run custom tool already posted?
Private _delayFlushAndRunCustomToolQueued As Boolean
' It is true, when we are loading a new file.
' CONSIDER: Some behaviors in the designer are different when we are loading the file. For example, we don't dirty the file, adding undo/redo...
' We should consider to make it to be a part of the global state of the designer, but not within one object.
Private _isLoadingResourceFile As Boolean
' If it is true, we are adding a collection of resources to the file
Private _inBatchAdding As Boolean
Private ReadOnly _multiTargetService As MultiTargetService
Private ReadOnly _allowMOTW As Boolean
#End Region
#Region "Constructors/Destructors"
''' <summary>
''' Constructor.
''' </summary>
''' <param name="RootComponent">The root component for this ResourceFile</param>
''' <param name="ServiceProvider">The service provider provided by the designer host</param>
''' <param name="BasePath">The base path to use for resolving relative paths in the resx file.</param>
Public Sub New(mtsrv As MultiTargetService, RootComponent As ResourceEditorRootComponent, ServiceProvider As IServiceProvider, BasePath As String)
Debug.Assert(RootComponent IsNot Nothing)
Debug.Assert(ServiceProvider IsNot Nothing)
_resources = New Dictionary(Of String, Resource)(StringComparers.ResourceNames)
_resourceFileMetadata = New List(Of DictionaryEntry)
_rootComponent = RootComponent
_serviceProvider = ServiceProvider
_basePath = BasePath
_componentChangeService = DirectCast(ServiceProvider.GetService(GetType(IComponentChangeService)), IComponentChangeService)
If ComponentChangeService Is Nothing Then
Throw New Package.InternalException
End If
_multiTargetService = mtsrv
Dim hierarchy As IVsHierarchy = DirectCast(ServiceProvider.GetService(GetType(IVsHierarchy)), IVsHierarchy)
If hierarchy IsNot Nothing Then
Dim project As IVsProject = DirectCast(hierarchy, IVsProject)
Dim sp As OLE.Interop.IServiceProvider = Nothing
Dim hr As Integer = project.GetItemContext(VSITEMID.ROOT, sp) '0xFFFFFFFE VSITEMID_ROOT
If Interop.NativeMethods.Succeeded(hr) Then
Dim pUnk As IntPtr
Dim g As Guid = GetType(IResXResourceService).GUID
Dim g2 As Guid = New Guid("00000000-0000-0000-C000-000000000046") 'IUnKnown
hr = sp.QueryService(g, g2, pUnk)
If Interop.NativeMethods.Succeeded(hr) AndAlso Not pUnk = IntPtr.Zero Then
_resxService = DirectCast(System.Runtime.InteropServices.Marshal.GetObjectForIUnknown(pUnk), IResXResourceService)
System.Runtime.InteropServices.Marshal.Release(pUnk)
End If
End If
End If
_mainThread = System.Threading.Thread.CurrentThread
Try
Dim allowUntrustedFiles As Object = Registry.GetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\SDK", "AllowProcessOfUntrustedResourceFiles", Nothing)
Dim untrustedFiles = TryCast(allowUntrustedFiles, String)
If untrustedFiles IsNot Nothing Then
_allowMOTW = untrustedFiles.Equals("true", StringComparison.OrdinalIgnoreCase)
End If
Catch ex As Exception
' Deliberately empty
End Try
End Sub
''' <summary>
''' IDisposable.Dispose()
''' </summary>
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
End Sub
''' <summary>
''' Dispose.
''' </summary>
''' <param name="Disposing">If True, we're disposing. If false, we're finalizing.</param>
Protected Sub Dispose(Disposing As Boolean)
If Disposing Then
'Stop listening to component removing events - we want to just tear down in peace.
ComponentChangeService = Nothing
'Stop delay-checking resources and remove ourselves from idle-time processing (very important)
StopDelayingCheckingForErrors()
'Remove all task list entries
If _errorListProvider IsNot Nothing Then
_errorListProvider.Tasks.Clear()
End If
If _resources IsNot Nothing Then
'Note: The designer host disposing any Resources of ours that have been
' added as components. However, we do it now anyway, in case there are some
' that didn't make into into the host container, or in case we later do the
' optimization of delay-adding Resources as components. The second dispose
' won't hurt the Resource.
For Each resource In _resources.Values
resource.Dispose()
Next
_resources.Clear()
End If
If _resourceFileMetadata IsNot Nothing Then
_resourceFileMetadata.Clear()
End If
End If
End Sub
#End Region
#Region "Properties"
''' <summary>
''' The service provider provided by the designer host
''' </summary>
Public ReadOnly Property ServiceProvider As IServiceProvider
Get
Return _serviceProvider
End Get
End Property
''' <summary>
''' Returns/gets the ComponentChangeService used by this ResourceFile. To have this class stop listening to
''' change events, set this property to Nothing. It does not need to be set up initially - it gets it
''' automatically from the service provider passed in.
''' </summary>
Public Property ComponentChangeService As IComponentChangeService
Get
Return _componentChangeService
End Get
Set
_componentChangeService = Value
End Set
End Property
''' <summary>
''' Gets the ResourceEditorView associated with this ResourceFile.
''' </summary>
''' <remarks>Overridable for unit testing.</remarks>
Public Overridable ReadOnly Property View As ResourceEditorView
Get
Return RootComponent.RootDesigner.GetView()
End Get
End Property
''' <summary>
''' Gets the root component associated with this resource file.
''' </summary>
Public ReadOnly Property RootComponent As ResourceEditorRootComponent
Get
Return _rootComponent
End Get
End Property
''' <summary>
''' Retrieves the designer host for the resource editor
''' </summary>
Private ReadOnly Property DesignerHost As IDesignerHost
Get
If RootComponent.RootDesigner Is Nothing Then
Debug.Fail("No root designer")
Throw New Package.InternalException
End If
Dim Host As IDesignerHost = RootComponent.RootDesigner.DesignerHost
Debug.Assert(Host IsNot Nothing)
Return Host
End Get
End Property
''' <summary>
''' Returns the resources from this resource file
''' </summary>
Friend ReadOnly Property Resources As Dictionary(Of String, Resource)
Get
Return _resources
End Get
End Property
''' <summary>
''' The base path to use for resolving relative paths in the resx file. This should be the
''' directory where the resx file lives.
''' </summary>
Public ReadOnly Property BasePath As String
Get
Return _basePath
End Get
End Property
''' <summary>
''' Get the taskProvider
''' directory where the resx file lives.
''' </summary>
Private ReadOnly Property ErrorListProvider As ErrorListProvider
Get
If _errorListProvider Is Nothing Then
If RootComponent.RootDesigner IsNot Nothing Then
_errorListProvider = RootComponent.RootDesigner.GetErrorListProvider()
End If
Debug.Assert(_errorListProvider IsNot Nothing, "ErrorListProvider can not be found")
End If
Return _errorListProvider
End Get
End Property
''' <summary>
''' Whether the resource item belongs to a device project
''' </summary>
Public ReadOnly Property IsInsideDeviceProject As Boolean Implements ResourceTypeEditor.IResourceContentFile.IsInsideDeviceProject
Get
Return RootComponent IsNot Nothing AndAlso RootComponent.IsInsideDeviceProject()
End Get
End Property
''' <summary>
''' Returns whether the provided type is supported in the project containing this resource file
''' </summary>
Public Function IsSupportedType(Type As Type) As Boolean Implements ResourceTypeEditor.IResourceContentFile.IsSupportedType
' The type is considered supported unless the MultiTargetService says otherwise (MultiTargetService checks
' in the project's target framework).
If _multiTargetService IsNot Nothing Then
Return _multiTargetService.IsSupportedType(Type)
Else
Return True
End If
End Function
#End Region
#Region "Resource Naming and look-up"
''' <summary>
''' Gets a suggested name for a new Resource which is not used by any resource currently in this ResourceFile.
''' </summary>
Public Function GetUniqueName(TypeEditor As ResourceTypeEditor) As String
Dim UniqueNamePrefix As String
Try
UniqueNamePrefix = TypeEditor.GetSuggestedNamePrefix().Trim()
If UniqueNamePrefix = "" OrElse UniqueNamePrefix.IndexOf(" "c) >= 0 Then
Debug.Fail("Bad unique name prefix - localization bug?")
UniqueNamePrefix = ""
End If
Catch ex As Exception When ReportWithoutCrash(ex, NameOf(GetUniqueName), NameOf(ResourceFile))
UniqueNamePrefix = ""
End Try
If UniqueNamePrefix = "" Then
'Use a default prefix if there's trouble
UniqueNamePrefix = "id"
End If
Dim UniqueNameFormat As String = UniqueNamePrefix & "{0:0}"
Return GetUniqueName(UniqueNameFormat)
End Function
''' <summary>
''' Gets a suggested name for a new Resource which is not used by any resource currently in this ResourceFile.
''' </summary>
''' <param name="NameFormat">A format to use for String.Format which indicates how to format the integer portion of the name. Must contain a single {0} parameter.</param>
Public Function GetUniqueName(NameFormat As String) As String
Debug.Assert(NameFormat.IndexOf("{") >= 0 AndAlso NameFormat.IndexOf("}") >= 2,
"NameFormat must contain a replacement arg")
Dim SuffixInteger As Integer = 1
Do
Dim NewName As String = String.Format(NameFormat, SuffixInteger)
If Not Contains(NewName) Then
Return NewName
End If
SuffixInteger += 1
Loop
End Function
''' <summary>
''' Determines if a resource with a given name (case-insensitive) exists in this ResourceFile.
''' </summary>
''' <param name="Name">The resource name to look for (case insensitive)</param>
Public Function Contains(Name As String) As Boolean
Return FindResource(Name) IsNot Nothing
End Function
''' <summary>
''' Determines if a particular resource is in this ResourceFile (by reference)
''' </summary>
''' <param name="Resource"></param>
Public Function Contains(Resource As Resource) As Boolean
Return _resources.ContainsValue(Resource)
End Function
''' <summary>
''' Searches for a resource with a given name (case-insensitive) in this ResourceFile.
''' </summary>
''' <param name="Name">The resource name to look for (case insensitive)</param>
''' <returns>The found Resource, or Nothing if not found.</returns>
Public Function FindResource(Name As String) As Resource
If Name = "" Then
Return Nothing
End If
Dim Resource As Resource = Nothing
If _resources.TryGetValue(Name, Resource) Then
Debug.Assert(Resource.ParentResourceFile Is Me)
End If
Return Resource
End Function
''' <summary>
''' Searches for a resource with a given file link (case-insensitive) in this ResourceFile.
''' </summary>
''' <param name="FileFullPath">The full path name of the linked file to look for (case insensitive)</param>
''' <returns>The found Resource, or Nothing if not found.</returns>
''' <remarks> We should be careful there, because there could be different path pointing to a same file</remarks>
Public Function FindLinkResource(FileFullPath As String) As Resource
Dim fileInfo As New FileInfo(FileFullPath)
FileFullPath = fileInfo.FullName
For Each Resource In _resources.Values
If Resource.IsLink Then
Dim linkFileInfo As New FileInfo(Resource.AbsoluteLinkPathAndFileName)
If String.Equals(FileFullPath, linkFileInfo.FullName, StringComparison.OrdinalIgnoreCase) Then
Return Resource
End If
End If
Next
Return Nothing
End Function
#End Region
#Region "Adding/removing/renaming resources"
''' <summary>
''' Add a collection of resources to the ResourceFile
''' </summary>
''' <param name="NewResources">A collection of resource items to add</param>
Public Sub AddResources(NewResources As ICollection(Of Resource))
Debug.Assert(NewResources IsNot Nothing, "Invalid Resources collection")
_inBatchAdding = True
Try
For Each Resource In NewResources
AddResource(Resource)
Next
If Not _isLoadingResourceFile Then
AddNecessaryReferenceToProject(NewResources)
End If
Finally
_inBatchAdding = False
End Try
End Sub
''' <summary>
''' Adds a new Resource to the ResourceFile.
''' </summary>
''' <param name="NewResource">The Resource to add. Must not be blank.</param>
''' <remarks>Exception throw if the name is not unique.</remarks>
Public Sub AddResource(NewResource As Resource)
If NewResource.Name = "" Then
Debug.Fail("Resource Name is blank - we shouldn't reach here with that condition")
Throw NewException(My.Resources.Microsoft_VisualStudio_Editors_Designer.RSE_Err_NameBlank, HelpIDs.Err_NameBlank)
End If
If Contains(NewResource.Name) Then
Throw NewException(My.Resources.Microsoft_VisualStudio_Editors_Designer.GetString(My.Resources.Microsoft_VisualStudio_Editors_Designer.RSE_Err_DuplicateName_1Arg, NewResource.Name), HelpIDs.Err_DuplicateName)
End If
'Set up a type resolution context for the resource in case this hasn't
' already been done (won't be done if the Resource was deserialized
' during an Undo/Redo or Drop/Paste operation, for example)
NewResource.SetTypeResolutionContext(View)
#If DEBUG Then
Dim ResourcesCountOld As Integer = _resources.Count
#End If
Dim AddingRemovingResourcesInternallySave As Boolean = _addingRemovingResourcesInternally
Try
_addingRemovingResourcesInternally = True
'Add the component to our designer's container.
'This will cause us to get notified via ComponentChangeService.ComponentAdded, which is where we
' will actually add the Resource to our internal list.
DesignerHost.Container.Add(NewResource, NewResource.Name)
Finally
_addingRemovingResourcesInternally = AddingRemovingResourcesInternallySave
End Try
#If DEBUG Then
Debug.Assert(_resources.Count = ResourcesCountOld + 1)
#End If
End Sub
''' <summary>
''' Removes the specified Resource from this ResourceFile.
''' </summary>
''' <param name="Resource">The Resource to remove. Must exist in the ResourceFile</param>
''' <param name="DisposeResource">If True, the Resource is also disposed.</param>
Public Sub RemoveResource(Resource As Resource, DisposeResource As Boolean)
Debug.Assert(Resource IsNot Nothing)
Debug.Assert(FindResource(Resource.Name) Is Resource, "RemoveResource: not found by Name")
Debug.Assert(_resources.ContainsValue(Resource), "RemoveResource: not found")
Dim ResourcesCountOld As Integer = _resources.Count
Dim AddingRemovingResourcesInternallySave As Boolean = _addingRemovingResourcesInternally
Try
_addingRemovingResourcesInternally = True
'Remove the component from our designer's container.
'This will cause us to get notified via ComponentChangeService.ComponentRemoved, which is where we
' will actually remove the Resource from our internal list
DesignerHost.Container.Remove(Resource)
Finally
_addingRemovingResourcesInternally = AddingRemovingResourcesInternallySave
End Try
Debug.Assert(_resources.Count = ResourcesCountOld - 1)
'Remove any task list entries for this resource
ClearResourceTasks(Resource)
If DisposeResource Then
Resource.Dispose()
End If
End Sub
''' <summary>
''' Called by the component change service when a new component is added to the designer host's container.
''' We get notified of this for both our own internal adding/removing and also for those done on our behalf
''' by Undo/Redo.
''' </summary>
''' <param name="sender">Event sender</param>
''' <param name="e">Event args</param>
''' <remarks>
''' Here we do the actual adding of the resource to our list.
''' </remarks>
Private Sub ComponentChangeService_ComponentAdded(sender As Object, e As ComponentEventArgs) Handles _componentChangeService.ComponentAdded
Dim ResourceObject As Object = e.Component
If TypeOf ResourceObject IsNot Resource Then
Debug.Fail("How could we be adding a component that's not a Resource?")
Exit Sub
End If
Dim Resource As Resource = DirectCast(e.Component, Resource)
If Resource Is Nothing Then
Debug.Fail("Resource shouldn't be Nothing")
Exit Sub
End If
'First thing, set the type resolution context (might not have been done yet if this component add was
' through a Undo/Redo operation)
Resource.SetTypeResolutionContext(View)
Debug.WriteLineIf(Switches.RSEAddRemoveResources.TraceVerbose, "Add/Remove Resources: Adding " & Resource.ToString())
Debug.Assert(FindResource(Resource.Name) IsNot Resource, "already a resource by that name")
Debug.Assert(Not _resources.ContainsValue(Resource), "already exists in our list")
'Add it to our list (upper-case the key to normalize for in-case-sensitive look-ups)
_resources.Add(Resource.Name, Resource)
'Set the parent
Resource.ParentResourceFile = Me
'Notify the Find feature
RootComponent.RootDesigner.InvalidateFindLoop(ResourcesAddedOrRemoved:=True)
'Update the number of resources in this resource's category
Dim Category As Category = Resource.GetCategory(View.Categories)
If Category IsNot Nothing Then
Category.ResourceCount += 1
Else
Debug.Fail("Couldn't find category for resource")
End If
'Add to our list of resources to check for errors in idle time
DelayCheckResourceForErrors(Resource)
'Notify the view that resources have been added (if they were added by someone besides us, think "Undo/Redo")
If Not _addingRemovingResourcesInternally Then
Debug.WriteLineIf(Switches.RSEAddRemoveResources.TraceVerbose, "Add/Remove Resources: (Resource was added externally)")
View.OnResourceAddedExternally(Resource)
End If
' Add Reference to the project system if necessary.
' Note: we need do this what ever it is added by an editing or undoing/redoing, but never when we are loading the file.
If Not _isLoadingResourceFile AndAlso Not _inBatchAdding Then
AddNecessaryReferenceToProject(New Resource() {Resource})
End If
'Set up a file watcher for this resource if it's a link
If View IsNot Nothing Then
Resource.AddFileWatcherEntry(View.FileWatcher)
End If
End Sub
''' <summary>
''' Called by the component change service when a Resource is removed, either by us or by an external
''' party (Undo/Redo).
''' </summary>
''' <param name="sender">Event sender</param>
''' <param name="e">Event args</param>
Private Sub ComponentChangeService_ComponentRemoved(sender As Object, e As ComponentEventArgs) Handles _componentChangeService.ComponentRemoved
Dim ResourceObject As Object = e.Component
If TypeOf ResourceObject IsNot Resource Then
Debug.Assert(TypeOf ResourceObject Is ResourceEditorRootComponent, "How could we be removing a component that's not a Resource?")
Exit Sub
End If
Dim Resource As Resource = DirectCast(e.Component, Resource)
If Resource Is Nothing Then
Debug.Fail("Resource shouldn't be Nothing")
Exit Sub
End If
Debug.WriteLineIf(Switches.RSEAddRemoveResources.TraceVerbose, "Add/Remove Resources: Removing " & Resource.ToString())
Debug.Assert(FindResource(Resource.Name) Is Resource, "not found by Name")
Debug.Assert(_resources.ContainsValue(Resource), "not found")
'Go ahead and remove from our list (keys are normalized as upper-case)
_resources.Remove(Resource.Name)
'Remove the parent pointer
Resource.ParentResourceFile = Nothing
'Notify Find
RootComponent.RootDesigner.InvalidateFindLoop(ResourcesAddedOrRemoved:=True)
'Update the number of resources in this resource's category
Dim Category As Category = Resource.GetCategory(View.Categories)
If Category IsNot Nothing Then
Category.ResourceCount -= 1
Else
Debug.Fail("Couldn't find category for resource")
End If
'Clear any task list entries
ClearResourceTasks(Resource)
'If this Resource is slated to be checked for errors at idle time, that's no longer necessary.
RemoveResourceToDelayCheckForErrors(Resource)
'Notify the view that resources have been removed (if they were removed by someone besides us, think "Undo/Redo")
If Not _addingRemovingResourcesInternally Then
Debug.WriteLineIf(Switches.RSEAddRemoveResources.TraceVerbose, "Add/Remove Resources: (Resource was removed externally)")
View.OnResourceRemovedExternally(Resource)
End If
'Remove the file watcher for this resource if it's a link (it won't be able to when the Undo/Redo engine disposes it, because
' it won't have a parent resource file then and can't get to the file watcher.
If View IsNot Nothing Then
Resource.RemoveFileWatcherEntry(View.FileWatcher)
End If
'
' Whenever we delete a resource, we have to make sure that we run our custom tool. Not doing so may cause problems if:
' step 1) Delete resource "A"
' step 2) Rename resource "B" to A
'
' Now the CodeModel gets angry because we haven't flushed the contents of the designer between step 1 & 2, which means that
' both the properties are still in the generated code, and we'd end up with code resource "A" if the operation were to succeed.
'
' Flushing & running the SFG after deletes will take care of this scenario... We also take care and post the message so that if
' we deleted multiple settings, we only do this once (perf)
'
DelayFlushAndRunCustomTool()
End Sub
''' <summary>
''' Rename a resource in the ResourceFile. This operation must come through here and not simply
''' be done directly on the Resource, because we also have to change the Resource's
''' ISite's name.
''' </summary>
''' <param name="Resource">Resource to rename</param>
''' <param name="NewName">New name. If it's not unique, an exception is thrown.</param>
''' <remarks>Caller is responsible for showing error message boxes</remarks>
Public Sub RenameResource(Resource As Resource, NewName As String)
If Contains(Resource) Then
Debug.Assert(DesignerHost.Container.Components(Resource.Name) IsNot Nothing)
If Resource.Name.Equals(NewName, StringComparison.Ordinal) Then
'Name didn't change - nothing to do
Exit Sub
End If
'Verify that the new name is unique. Note that it's okay to rename in such a
' way that only the case of the name changes (thus ExistingResource will be
' the same as Resource, since we find case-insensitively).
Dim ExistingResource As Resource = FindResource(NewName)
If ExistingResource IsNot Nothing AndAlso ExistingResource IsNot Resource Then
Throw NewException(My.Resources.Microsoft_VisualStudio_Editors_Designer.GetString(My.Resources.Microsoft_VisualStudio_Editors_Designer.RSE_Err_DuplicateName_1Arg, NewName), HelpIDs.Err_DuplicateName)
End If
'Make sure the resx file is checked out if it isn't yet. Otherwise this failure might
' happen after we've already changed our internal name but before the site's name gets
' changed (because we change our internal name in response to listening in on
' ComponentChangeService).
View.RootDesigner.DesignerLoader.ManualCheckOut()
'Rename the component's site's name. This will cause a ComponentChangeService.ComponentRename event,
' which we listen to, and from which we will change the Resource's name. (We need to do it
' this way, because Undo/Redo on a name will change the component's site's name only, and we
' need to pick up on those changes in order to reflect the change in the Resource itself.
Resource.IComponent_Site.Name = NewName
Else
Debug.Fail("Trying to rename component that's not in the resource file")
End If
End Sub
''' <summary>
''' Called by the component change service when a resource has been renamed (rather, its component
''' ISite has been renamed). We need to keep these in sync.
''' This is called both when we rename the Resource ourselves and when something external does it
''' (Undo/Redo).
''' </summary>
''' <param name="sender">Event sender</param>
''' <param name="e">Event args</param>
Private Sub ComponentChangeService_ComponentRename(sender As Object, e As ComponentRenameEventArgs) Handles _componentChangeService.ComponentRename
If TypeOf e.Component IsNot Resource Then
Debug.Fail("Got component rename event for a component that isn't a resource")
Exit Sub
End If
Dim Resource As Resource = DirectCast(e.Component, Resource)
Debug.Assert(e.OldName.Equals(Resource.Name, StringComparison.Ordinal))
Debug.WriteLineIf(Switches.RSEAddRemoveResources.TraceVerbose, "Add/Remove Resources: Renaming " & Resource.ToString() & " to """ & e.NewName & """")
If Not Contains(Resource) Then
Debug.Fail("Trying to rename component that's not in the resource file")
Exit Sub
End If
If Resource.Name.Equals(e.NewName, StringComparison.OrdinalIgnoreCase) Then
'The name hasn't changed (or differs only by case) - okay to rename
ElseIf Not Contains(e.NewName) Then
'The new name is not in use by any current resource - okay to rename
Else
'Whoops. Something's wrong.
Debug.Fail("Got a RenameComponent event to a name that's already in use - shouldn't have happened")
Throw CreateArgumentException(NameOf(e.NewName))
End If
'Go ahead and make the change
Debug.Assert(Resource.ValidateName(e.NewName, Resource.Name), "Component's Site's name was changed to an invalid name. That shouldn't have happened.")
Dim OldName As String = Resource.Name
'Since resources in the ResourceFile are placed in the dictionary using the Name as key, if
' we change the Name, the location of the resource in the dictionary will not longer be correct
' (because we just changed the key, which it uses to search the dictionary). So we have to
' remove ourself first and then re-insert ourself into the dictionary.
_resources.Remove(Resource.Name)
Try
Resource.NameRawWithoutUndo = e.NewName
Catch ex As Exception When ReportWithoutCrash(ex, "Unexpected error changing the name of the resource", NameOf(ResourceFile))
End Try
_resources.Add(Resource.Name, Resource)
'Notify the view that resources have been removed (if they were removed by someone besides us, think "Undo/Redo")
View.OnResourceTouched(Resource)
'Fix up the project to use the new name, if we're creating strongly typed resource classes
Try
View.CallGlobalRename(OldName, e.NewName)
Catch ex As Exception When ReportWithoutCrash(ex, NameOf(ComponentChangeService_ComponentRename), NameOf(ResourceFile))
RootComponent.RootDesigner.GetView().DsMsgBox(ex)
End Try
End Sub
''' <summary>
''' Called by the component change service when a resource has been changed
''' This is called both when we changed the Resource ourselves and when something external does it
''' (Undo/Redo).
''' </summary>
''' <param name="sender">Event sender</param>
''' <param name="e">Event args</param>
Private Sub ComponentChangeService_ComponentChanged(sender As Object, e As ComponentChangedEventArgs) Handles _componentChangeService.ComponentChanged
If TypeOf e.Component IsNot Resource Then
Debug.Fail("Got component rename event for a component that isn't a resource")
Exit Sub
End If
View.OnResourceTouched(DirectCast(e.Component, Resource))
End Sub
#End Region
#Region "Reading/Writing/Enumerating"
''' <summary>
''' Reads resources from a string (contents of a resx file).
''' </summary>
''' <param name="allBufferText">The TextReader to read from</param>
Public Sub ReadResources(resourceFileName As String, allBufferText As String)
If IsDangerous(resourceFileName, allBufferText) Then
View.DsMsgBox(String.Format(My.Resources.Microsoft_VisualStudio_Editors_Designer.BlockedResx, resourceFileName), MessageBoxButtons.OK, MessageBoxIcon.Error)
Return
End If
Using TextReader = New StringReader(allBufferText)
Dim ResXReader As ResXResourceReader
Dim TypeResolutionService As ITypeResolutionService = View.GetTypeResolutionService()
If TypeResolutionService IsNot Nothing Then
ResXReader = New ResXResourceReader(TextReader, TypeResolutionService)
Else
ResXReader = New ResXResourceReader(TextReader, ResourceEditorView.GetDefaultAssemblyReferences())
End If
ResXReader.BasePath = _basePath
ReadResources(ResXReader)
ResXReader.Close()
End Using
End Sub
Public Function TypeNameConverter(runtimeType As Type) As String
Debug.Assert(runtimeType IsNot Nothing, "runtimeType cannot be Nothing!")
If _multiTargetService Is Nothing Then
Return runtimeType.AssemblyQualifiedName
Else
Return _multiTargetService.TypeNameConverter(runtimeType)
End If
End Function
''' <summary>
''' Writes all resources into a TextWriter in resx format.
''' </summary>
''' <param name="TextWriter">TextWriter to write to</param>
Public Sub WriteResources(TextWriter As TextWriter)
Dim ResXWriter As IResourceWriter
If _resxService IsNot Nothing Then
ResXWriter = _resxService.GetResXResourceWriter(TextWriter, _basePath)
Else
Dim r As New ResXResourceWriter(TextWriter, AddressOf TypeNameConverter) With {
.BasePath = _basePath
}
ResXWriter = r
End If
'This call will generate the resources. We don't want to close the ResXWriter because it
' will also close the TextWriter, which closes its stream, which may not be expected by
' the caller.
WriteResources(ResXWriter)
End Sub
''' <summary>
''' Reads all resources into this ResourceFile from a ResXReader
''' </summary>
''' <param name="ResXReader">The ResXReader to read from</param>
Private Sub ReadResources(ResXReader As ResXResourceReader)
Debug.Assert(ResXReader IsNot Nothing, "ResXReader must exist!")
_isLoadingResourceFile = True
Try
Dim orderID As Integer = 0
Dim lastName As String = String.Empty
_resources.Clear()
_resourceFileMetadata.Clear()
ResXReader.UseResXDataNodes = True
Using New WaitCursor
For Each DictEntry As DictionaryEntry In ResXReader
Dim Node As ResXDataNode = DirectCast(DictEntry.Value, ResXDataNode)
Dim Resource As Resource = Nothing
Try
Resource = New Resource(Me, Node, orderID, View)
orderID += 1
'If duplicate Names are found, this function will throw an exception (which is what we want - it will keep the
' file from loading)
AddResource(Resource)
' we check whether the resource item in the original file was alphabetized, we keep the style when we save it...
If _alphabetizedOrder Then
If StringComparers.ResourceNames.Compare(lastName, Resource.Name) > 0 Then
_alphabetizedOrder = False
Else
lastName = Resource.Name
End If
End If
Catch ex As Exception When ReportWithoutCrash(ex, NameOf(ReadResources), NameOf(ResourceFile))
If Resource IsNot Nothing Then
Resource.Dispose()
End If
Throw
End Try
Next
End Using
' Read and save meta data
Dim enumerator As IDictionaryEnumerator = ResXReader.GetMetadataEnumerator()
If enumerator IsNot Nothing Then
While enumerator.MoveNext()
_resourceFileMetadata.Add(enumerator.Entry)
End While
End If
Finally
_isLoadingResourceFile = False
End Try
End Sub
''' <summary>
''' Writes all resources into a ResXResourceWriter
''' </summary>
''' <param name="ResXWriter">The ResXResourceWriter instance to use</param>
Private Sub WriteResources(ResXWriter As IResourceWriter)
Debug.Assert(ResXWriter IsNot Nothing, "ResXWriter must exist.")
If _resources IsNot Nothing Then
' NOTE: We save all meta data first... We don't have a way maintain the right order between Meta data items and resource items today.
' Keep all meta data items if it is possible...
If _resourceFileMetadata IsNot Nothing AndAlso _resourceFileMetadata.Count > 0 Then
Dim NewWriter As ResXResourceWriter = TryCast(ResXWriter, ResXResourceWriter)
If NewWriter IsNot Nothing Then
For Each entry In _resourceFileMetadata
NewWriter.AddMetadata(CStr(entry.Key), entry.Value)
Next
End If
End If
If _resources.Count > 0 Then
Dim resourceList As Resource() = New Resource(_resources.Count - 1) {}
Dim i As Integer = 0
For Each Resource In _resources.Values
resourceList(i) = Resource
i += 1
Next
Dim comparer As IComparer
If _alphabetizedOrder Then
comparer = AlphabetizedOrderComparer.Instance
Else
comparer = OriginalOrderComparer.Instance
End If
Array.Sort(resourceList, comparer)
Dim failedList As String = Nothing
Dim extraMessage As String = Nothing
For i = 0 To resourceList.Length - 1
Dim resource As Resource = resourceList(i)
Try
ResXWriter.AddResource(resource.ResXDataNode.Name, resourceList(i).ResXDataNode)
Catch ex As Exception When ReportWithoutCrash(ex, NameOf(WriteResources), NameOf(ResourceFile))
resource.SetTaskFromGetValueException(ex, ex)
If failedList IsNot Nothing Then
failedList = My.Resources.Microsoft_VisualStudio_Editors_Designer.GetString(My.Resources.Microsoft_VisualStudio_Editors_Designer.RSE_Err_NameList, failedList, resource.Name)
Else
failedList = My.Resources.Microsoft_VisualStudio_Editors_Designer.GetString(My.Resources.Microsoft_VisualStudio_Editors_Designer.RSE_Err_Name, resource.Name)
extraMessage = ex.Message
End If
End Try
Next
If failedList IsNot Nothing Then
RootComponent.RootDesigner.GetView().DsMsgBox(My.Resources.Microsoft_VisualStudio_Editors_Designer.GetString(My.Resources.Microsoft_VisualStudio_Editors_Designer.RSE_Err_CantSaveResouce_1Arg, failedList) & vbCrLf & vbCrLf & extraMessage,
MessageBoxButtons.OK, MessageBoxIcon.Error, , HelpIDs.Err_CantSaveBadResouceItem)
End If
End If
ResXWriter.Generate()
Else
Throw New Exception("Must read resources before attempting to write")
End If
End Sub
#End Region
#Region "UI"
''' <summary>
''' Invalidates this resource in the resource editor view, which causes it to be updated on the next
''' paint.
''' </summary>
''' <param name="Resource">The resource to invalidate</param>
''' <param name="InvalidateThumbnail">If True, then the Resource's thumbnail is also invalidated so it will be regenerated on the next paint.</param>
Public Sub InvalidateResourceInView(Resource As Resource, Optional InvalidateThumbnail As Boolean = False)
If RootComponent.RootDesigner IsNot Nothing AndAlso RootComponent.RootDesigner.GetView() IsNot Nothing Then
RootComponent.RootDesigner.GetView().InvalidateResource(Resource, InvalidateThumbnail)
End If
End Sub
#End Region
#Region "Task List integration"
#Region "ResourceTaskType enum"
''' <summary>
''' The types of errors which can occur for a Resource.
''' NOTE: These types are mutually exclusive. I.e., each Resource is allowed to log
''' a single task list item for *each* of the values in this enum. E.g., a resource
''' can have a task list item for bad link (CantInstantiateResource) and for
''' a bad Name, and both task items will show up in the task list.
''' </summary>
Public Enum ResourceTaskType
'ID is bad or otherwise not a good idea