Browse Source

add ILSpy analyzer

Conflicts:

	ILSpy/MainWindow.xaml.cs
pull/70/head
Siegfried Pammer 15 years ago
parent
commit
34fa07cbb8
  1. 6
      ILSpy/ILSpy.csproj
  2. 1
      ILSpy/Images/Images.cs
  3. 14
      ILSpy/MainWindow.xaml
  4. 30
      ILSpy/MainWindow.xaml.cs
  5. 69
      ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs
  6. 92
      ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs
  7. 28
      ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs
  8. 28
      ILSpy/TreeNodes/DerivedTypesTreeNode.cs
  9. 2
      ILSpy/TreeNodes/ILSpyTreeNode.cs
  10. 58
      ILSpy/TreeNodes/MethodTreeNode.cs
  11. 34
      ILSpy/TreeNodes/ThreadingSupport.cs
  12. 4
      ILSpy/themes/generic.xaml

6
ILSpy/ILSpy.csproj

@ -133,6 +133,9 @@ @@ -133,6 +133,9 @@
<Compile Include="TextView\ReferenceElementGenerator.cs" />
<Compile Include="TextView\AvalonEditTextOutput.cs" />
<Compile Include="TextView\UIElementGenerator.cs" />
<Compile Include="TreeNodes\Analyzer\AnalyzedMethodTreeNode.cs" />
<Compile Include="TreeNodes\Analyzer\AnalyzedMethodUsedByTreeNode.cs" />
<Compile Include="TreeNodes\Analyzer\AnalyzerTreeNode.cs" />
<Compile Include="TreeNodes\AssemblyListTreeNode.cs" />
<Compile Include="TreeNodes\AssemblyReferenceTreeNode.cs" />
<Compile Include="TreeNodes\AssemblyTreeNode.cs" />
@ -148,7 +151,7 @@ @@ -148,7 +151,7 @@
<Compile Include="TreeNodes\ReferenceFolderTreeNode.cs" />
<Compile Include="TreeNodes\ResourceEntryNode.cs" />
<Compile Include="TreeNodes\ResourceListTreeNode.cs" />
<Compile Include="TreeNodes\ThreadedTreeNode.cs" />
<Compile Include="TreeNodes\ThreadingSupport.cs" />
<Compile Include="TreeNodes\TypeTreeNode.cs" />
<EmbeddedResource Include="TextView\ILAsm-Mode.xshd" />
</ItemGroup>
@ -233,6 +236,7 @@ @@ -233,6 +236,7 @@
<Folder Include="Controls" />
<Folder Include="TreeNodes" />
<Folder Include="TextView" />
<Folder Include="TreeNodes\Analyzer" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.Targets" />
</Project>

1
ILSpy/Images/Images.cs

@ -67,5 +67,6 @@ namespace ICSharpCode.ILSpy @@ -67,5 +67,6 @@ namespace ICSharpCode.ILSpy
public static readonly BitmapImage ProtectedStruct = LoadBitmap("ProtectedStruct");
public static readonly BitmapImage Delete = LoadBitmap("Delete");
public static readonly BitmapImage Search = LoadBitmap("Search");
}
}

14
ILSpy/MainWindow.xaml

@ -13,11 +13,7 @@ @@ -13,11 +13,7 @@
FocusManager.FocusedElement="{Binding ElementName=treeView}"
>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Controls/SearchBoxStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<Window.CommandBindings>
<CommandBinding
@ -65,6 +61,7 @@ @@ -65,6 +61,7 @@
<Image Width="16" Height="16" Source="Images/PrivateInternal.png" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Show _analyzer" Name="showAnalyzer" IsCheckable="True" />
</MenuItem>
<MenuItem Header="_Help">
<MenuItem Header="_About" Click="AboutClick" />
@ -160,6 +157,13 @@ @@ -160,6 +157,13 @@
</DockPanel>
</Border>
<Border BorderBrush="Gray" BorderThickness="1" DockPanel.Dock="Bottom" Name="analyzerPanel" Visibility="{Binding IsChecked, ElementName=showAnalyzer, Converter={StaticResource BooleanToVisibilityConverter}}" MinHeight="100" MaxHeight="300">
<DockPanel>
<Label DockPanel.Dock="Top">Analyzer</Label>
<tv:SharpTreeView Name="analyzerTree" ShowRoot="False" />
</DockPanel>
</Border>
<textView:DecompilerTextView x:Name="decompilerTextView" />
</DockPanel>
</Grid>

30
ILSpy/MainWindow.xaml.cs

@ -27,7 +27,10 @@ using System.Windows; @@ -27,7 +27,10 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.FlowAnalysis;
using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpy.TreeNodes.Analyzer;
using ICSharpCode.TreeView;
using Microsoft.Win32;
@ -45,8 +48,15 @@ namespace ICSharpCode.ILSpy @@ -45,8 +48,15 @@ namespace ICSharpCode.ILSpy
AssemblyList assemblyList;
AssemblyListTreeNode assemblyListTreeNode;
static MainWindow instance;
public static MainWindow Instance {
get { return instance; }
}
public MainWindow()
{
instance = this;
spySettings = ILSpySettings.Load();
this.sessionSettings = new SessionSettings(spySettings);
this.assemblyListManager = new AssemblyListManager(spySettings);
@ -369,6 +379,26 @@ namespace ICSharpCode.ILSpy @@ -369,6 +379,26 @@ namespace ICSharpCode.ILSpy
}
#endregion
#region Analyzer
public void Analyze(MethodTreeNode method)
{
if (analyzerTree.Root == null)
analyzerTree.Root = new SharpTreeNode();
if (analyzerPanel.Visibility != Visibility.Visible)
analyzerPanel.Visibility = Visibility.Visible;
var newNode = new AnalyzedMethodTreeNode(method.MethodDefinition) {
Language = sessionSettings.FilterSettings.Language
};
if (analyzerTree.Root.Children.Contains(newNode))
analyzerTree.Root.Children.Remove(newNode);
analyzerTree.Root.Children.Add(newNode);
}
#endregion
protected override void OnStateChanged(EventArgs e)
{
base.OnStateChanged(e);

69
ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using Mono.Cecil;
namespace ICSharpCode.ILSpy.TreeNodes.Analyzer
{
class AnalyzedMethodTreeNode : AnalyzerTreeNode
{
MethodDefinition analyzedMethod;
public AnalyzedMethodTreeNode(MethodDefinition analyzedMethod)
{
if (analyzedMethod == null)
throw new ArgumentNullException("analyzedMethod");
this.analyzedMethod = analyzedMethod;
this.LazyLoading = true;
}
public override object Icon {
get { return MethodTreeNode.GetIcon(analyzedMethod); }
}
public override object Text {
get { return MethodTreeNode.GetText(analyzedMethod, Language); }
}
protected override void LoadChildren()
{
this.Children.Add(new AnalyzedMethodUsedByTreeNode(analyzedMethod));
}
#region Equals and GetHashCode implementation
public override bool Equals(object obj)
{
AnalyzedMethodTreeNode other = obj as AnalyzedMethodTreeNode;
if (other == null)
return false;
return object.Equals(this.analyzedMethod, other.analyzedMethod);
}
public override int GetHashCode()
{
int hashCode = 0;
unchecked {
if (analyzedMethod != null)
hashCode += 1000000007 * analyzedMethod.GetHashCode();
}
return hashCode;
}
#endregion
}
}

92
ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs

@ -0,0 +1,92 @@ @@ -0,0 +1,92 @@
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Threading;
using ICSharpCode.NRefactory.Utils;
using Mono.Cecil;
using Mono.Cecil.Cil;
namespace ICSharpCode.ILSpy.TreeNodes.Analyzer
{
class AnalyzedMethodUsedByTreeNode : ILSpyTreeNode
{
MethodDefinition analyzedMethod;
ThreadingSupport threading;
public AnalyzedMethodUsedByTreeNode(MethodDefinition analyzedMethod)
{
if (analyzedMethod == null)
throw new ArgumentNullException("analyzedMethod");
this.analyzedMethod = analyzedMethod;
this.threading = new ThreadingSupport();
this.LazyLoading = true;
}
public override object Text {
get { return "Used By"; }
}
public override object Icon {
get { return Images.Search; }
}
protected override void LoadChildren()
{
threading.LoadChildren(this, FetchChildren);
}
IEnumerable<ILSpyTreeNode> FetchChildren(CancellationToken ct)
{
return FindReferences(MainWindow.Instance.AssemblyList.GetAssemblies(), ct);
}
IEnumerable<ILSpyTreeNode> FindReferences(LoadedAssembly[] assemblies, CancellationToken ct)
{
foreach (LoadedAssembly asm in assemblies) {
ct.ThrowIfCancellationRequested();
foreach (TypeDefinition type in TreeTraversal.PreOrder(asm.AssemblyDefinition.MainModule.Types, t => t.NestedTypes)) {
ct.ThrowIfCancellationRequested();
foreach (MethodDefinition method in type.Methods) {
ct.ThrowIfCancellationRequested();
bool found = false;
if (!method.HasBody)
continue;
foreach (Instruction instr in method.Body.Instructions) {
if (instr.Operand is MethodReference
&& ((MethodReference)instr.Operand).Resolve() == analyzedMethod) {
found = true;
break;
}
}
if (found)
yield return new MethodTreeNode(method);
}
}
}
}
public override void Decompile(Language language, ICSharpCode.Decompiler.ITextOutput output, DecompilationOptions options)
{
throw new NotImplementedException();
}
}
}

28
ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using ICSharpCode.TreeView;
namespace ICSharpCode.ILSpy.TreeNodes.Analyzer
{
class AnalyzerTreeNode : SharpTreeNode
{
public Language Language { get; set; }
}
}

28
ILSpy/TreeNodes/DerivedTypesTreeNode.cs

@ -15,15 +15,18 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -15,15 +15,18 @@ namespace ICSharpCode.ILSpy.TreeNodes
/// <summary>
/// Lists the super types of a class.
/// </summary>
sealed class DerivedTypesTreeNode : ThreadedTreeNode
sealed class DerivedTypesTreeNode : ILSpyTreeNode
{
readonly AssemblyList list;
readonly TypeDefinition type;
ThreadingSupport threading;
public DerivedTypesTreeNode(AssemblyList list, TypeDefinition type)
{
this.list = list;
this.type = type;
this.LazyLoading = true;
this.threading = new ThreadingSupport();
}
public override object Text {
@ -34,7 +37,12 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -34,7 +37,12 @@ namespace ICSharpCode.ILSpy.TreeNodes
get { return Images.SubTypes; }
}
protected override IEnumerable<ILSpyTreeNode> FetchChildren(CancellationToken cancellationToken)
protected override void LoadChildren()
{
threading.LoadChildren(this, FetchChildren);
}
IEnumerable<ILSpyTreeNode> FetchChildren(CancellationToken cancellationToken)
{
// FetchChildren() runs on the main thread; but the enumerator will be consumed on a background thread
var assemblies = list.GetAssemblies().Select(node => node.AssemblyDefinition).Where(asm => asm != null).ToArray();
@ -62,17 +70,24 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -62,17 +70,24 @@ namespace ICSharpCode.ILSpy.TreeNodes
{
return typeRef.FullName == type.FullName;
}
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
threading.Decompile(language, output, options, EnsureLazyChildren);
}
}
class DerivedTypesEntryNode : ThreadedTreeNode
class DerivedTypesEntryNode : ILSpyTreeNode
{
TypeDefinition def;
AssemblyDefinition[] assemblies;
ThreadingSupport threading;
public DerivedTypesEntryNode(TypeDefinition def, AssemblyDefinition[] assemblies)
{
this.def = def;
this.assemblies = assemblies;
threading = new ThreadingSupport();
}
public override bool ShowExpander {
@ -91,7 +106,12 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -91,7 +106,12 @@ namespace ICSharpCode.ILSpy.TreeNodes
}
}
protected override IEnumerable<ILSpyTreeNode> FetchChildren(CancellationToken ct)
protected override void LoadChildren()
{
threading.LoadChildren(this, FetchChildren);
}
IEnumerable<ILSpyTreeNode> FetchChildren(CancellationToken ct)
{
// FetchChildren() runs on the main thread; but the enumerator will be consumed on a background thread
return DerivedTypesTreeNode.FindDerivedTypes(def, assemblies, ct);

2
ILSpy/TreeNodes/ILSpyTreeNode.cs

@ -56,7 +56,7 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -56,7 +56,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
return FilterResult.Hidden;
}
protected object HighlightSearchMatch(string text, string suffix = null)
protected static object HighlightSearchMatch(string text, string suffix = null)
{
// TODO: implement highlighting the search match
return text + suffix;

58
ILSpy/TreeNodes/MethodTreeNode.cs

@ -18,6 +18,8 @@ @@ -18,6 +18,8 @@
using System;
using System.Text;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using ICSharpCode.Decompiler;
using Mono.Cecil;
@ -43,30 +45,41 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -43,30 +45,41 @@ namespace ICSharpCode.ILSpy.TreeNodes
public override object Text {
get {
StringBuilder b = new StringBuilder();
b.Append('(');
for (int i = 0; i < method.Parameters.Count; i++) {
if (i > 0) b.Append(", ");
b.Append(this.Language.TypeToString(method.Parameters[i].ParameterType, false, method.Parameters[i]));
}
b.Append(") : ");
b.Append(this.Language.TypeToString(method.ReturnType, false, method.MethodReturnType));
return HighlightSearchMatch(method.Name, b.ToString());
return GetText(method, Language);
}
}
public static object GetText(MethodDefinition method, Language language)
{
StringBuilder b = new StringBuilder();
b.Append('(');
for (int i = 0; i < method.Parameters.Count; i++) {
if (i > 0)
b.Append(", ");
b.Append(language.TypeToString(method.Parameters[i].ParameterType, false, method.Parameters[i]));
}
b.Append(") : ");
b.Append(language.TypeToString(method.ReturnType, false, method.MethodReturnType));
return HighlightSearchMatch(method.Name, b.ToString());
}
public override object Icon {
get {
if (method.IsSpecialName && method.Name.StartsWith("op_", StringComparison.Ordinal))
return Images.Operator;
if (method.IsStatic && method.HasCustomAttributes) {
foreach (var ca in method.CustomAttributes) {
if (ca.AttributeType.FullName == "System.Runtime.CompilerServices.ExtensionAttribute")
return Images.ExtensionMethod;
}
return GetIcon(method);
}
}
public static BitmapImage GetIcon(MethodDefinition method)
{
if (method.IsSpecialName && method.Name.StartsWith("op_", StringComparison.Ordinal))
return Images.Operator;
if (method.IsStatic && method.HasCustomAttributes) {
foreach (var ca in method.CustomAttributes) {
if (ca.AttributeType.FullName == "System.Runtime.CompilerServices.ExtensionAttribute")
return Images.ExtensionMethod;
}
return Images.Method;
}
return Images.Method;
}
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
@ -81,5 +94,16 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -81,5 +94,16 @@ namespace ICSharpCode.ILSpy.TreeNodes
else
return FilterResult.Hidden;
}
public override System.Windows.Controls.ContextMenu GetContextMenu()
{
ContextMenu menu = new ContextMenu();
MenuItem item = new MenuItem() { Header = "Analyze", Icon = new Image() { Source = Images.Search } };
item.Click += delegate { MainWindow.Instance.Analyze(this); };
menu.Items.Add(item);
return menu;
}
}
}

34
ILSpy/TreeNodes/ThreadedTreeNode.cs → ILSpy/TreeNodes/ThreadingSupport.cs

@ -11,36 +11,22 @@ using ICSharpCode.Decompiler; @@ -11,36 +11,22 @@ using ICSharpCode.Decompiler;
namespace ICSharpCode.ILSpy.TreeNodes
{
/// <summary>
/// Node that is lazy-loaded and loads its children on a background thread.
/// Adds threading support to nodes
/// </summary>
abstract class ThreadedTreeNode : ILSpyTreeNode
class ThreadingSupport
{
Task<List<ILSpyTreeNode>> loadChildrenTask;
public ThreadedTreeNode()
{
this.LazyLoading = true;
}
public void Invalidate()
{
this.LazyLoading = true;
this.Children.Clear();
loadChildrenTask = null;
}
/// <summary>
/// FetchChildren() runs on the main thread; but the enumerator is consumed on a background thread
///
/// </summary>
protected abstract IEnumerable<ILSpyTreeNode> FetchChildren(CancellationToken ct);
protected override sealed void LoadChildren()
public void LoadChildren(ILSpyTreeNode node, Func<CancellationToken, IEnumerable<ILSpyTreeNode>> fetchChildren)
{
this.Children.Add(new LoadingTreeNode());
node.Children.Add(new LoadingTreeNode());
CancellationToken ct = CancellationToken.None;
var fetchChildrenEnumerable = FetchChildren(ct);
var fetchChildrenEnumerable = fetchChildren(ct);
Task<List<ILSpyTreeNode>> thisTask = null;
thisTask = new Task<List<ILSpyTreeNode>>(
delegate {
@ -53,14 +39,14 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -53,14 +39,14 @@ namespace ICSharpCode.ILSpy.TreeNodes
// don't access "child" here the
// background thread might already be running the next loop iteration
if (loadChildrenTask == thisTask) {
this.Children.Insert(this.Children.Count - 1, newChild);
node.Children.Insert(node.Children.Count - 1, newChild);
}
}), child);
}
App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(
delegate {
if (loadChildrenTask == thisTask) {
this.Children.RemoveAt(this.Children.Count - 1); // remove 'Loading...'
node.Children.RemoveAt(node.Children.Count - 1); // remove 'Loading...'
}
}));
return result;
@ -72,11 +58,11 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -72,11 +58,11 @@ namespace ICSharpCode.ILSpy.TreeNodes
thisTask.Wait(TimeSpan.FromMilliseconds(200));
}
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
public void Decompile(Language language, ITextOutput output, DecompilationOptions options, Action ensureLazyChildren)
{
var loadChildrenTask = this.loadChildrenTask;
if (loadChildrenTask == null) {
App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(EnsureLazyChildren));
App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, ensureLazyChildren);
loadChildrenTask = this.loadChildrenTask;
}
if (loadChildrenTask != null) {

4
ILSpy/themes/generic.xaml

@ -22,4 +22,8 @@ @@ -22,4 +22,8 @@
Data = "M 5,5 L 10,10 L 15,5 L 5,5"/>
</StackPanel>
</DataTemplate>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Controls/SearchBoxStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
Loading…
Cancel
Save