I'm currently developing a Visual Studio plugin (VSPackage) which finally should be able to visualize call relations. In order to represent them I want to use the Graph# library which manages the graph (avoiding overlapping edges etc.).
Unfortunately I get the following error message at runtime in my XAML:
XamlParseException: The method or operation is not implemented.
The error pops up on the <graph:CallRelationGraphLayout Graph="{Binding RelationGraph}"/> tag.
<UserControl x:Class="Biocoder.InteractiveExploration.View.ExplorationControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:graphsharp="clr-namespace:GraphSharp.Controls;assembly=GraphSharp.Controls"
xmlns:zoom="clr-namespace:WPFExtensions.Controls;assembly=WPFExtensions"
xmlns:graph="clr-namespace:Biocoder.InteractiveExploration.Graph"
xmlns:viewmodels="clr-namespace:Biocoder.InteractiveExploration.ViewModel"
xmlns:controls="clr-namespace:Biocoder.InteractiveExploration.Controls" mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<viewmodels:ExplorationToolViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<zoom:ZoomControl Grid.Row="1"
Zoom="0.2"
ZoomBoxOpacity="0.5"
Background="Yellow">
<graph:CallRelationGraphLayout Graph="{Binding RelationGraph}"/>
</zoom:ZoomControl>
</Grid>
</UserControl>
I also created own vertex, edge and graph layout classes. My graph should finally represent call relations (edges) between methods (vertices).
MethodVertex.cs
public class MethodVertex
{
public string ID { get; private set; }
public bool IsMale { get; private set; }
public MethodVertex(string id, bool isMale)
{
ID = id;
IsMale = isMale;
}
public override string ToString()
{
return string.Format("{0}-{1}", ID, IsMale);
}
}
RelationEdge.cs
public class RelationEdge : Edge<MethodVertex>
{
public string Id { get; private set; }
public RelationEdge(string id, MethodVertex source, MethodVertex target)
: base(source, target)
{
Id = id;
}
}
CallRelationGraphLayout.cs
public class CallRelationGraphLayout : GraphLayout<MethodVertex, RelationEdge, CallRelationGraph>
{}
CallRelationGraph.cs
public class CallRelationGraph : BidirectionalGraph<MethodVertex, RelationEdge>
{
public CallRelationGraph()
{}
public CallRelationGraph(bool allowParallelEdges)
: base(allowParallelEdges)
{ }
public CallRelationGraph(bool allowParallelEdges, int vertexCapacity)
: base(allowParallelEdges, vertexCapacity)
{}
}
In the ExplorationToolViewModel I declared the RelationGraph as follows:
private CallRelationGraph _relationGraph;
public CallRelationGraph RelationGraph
{
get { return _relationGraph; }
set
{
if (value != _relationGraph)
{
_relationGraph = value;
NotifyPropertyChanged("RelationGraph");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
What I maybe also should mention is that I have the following error displayed sometimes but the project compiles and runs.
GenericArguments[1 ], 'Biocoder.InteractiveExploration.Graph.RelationEdge', on 'GraphSharp.Algorithms.Layout.ILayoutAlgorithm`3[TVertex,TEdge,TGraph]' violates the constraint of type 'TEdge'.
Maybe its the source of the problem but I ignored it so far since it compiled and I did it corresponding to this tutorial.
The strange thing is that it actually works in a normal WPF application using the DLLs provided by Graph#. When I leave the Graph-property out the error doesn't show up so I guess it has to do with the Graph property. Any hints about how to solve this?
Thank you very much in advance!
I've experienced the same issue when using Graph# in a VSPackage. I was able to overcome the issue by not using Bindings for the graph, but by assigning the Graph property in CodeBehind.
This led to an exception that the WPFExtensions assembly could not be loaded when assigning the Graph property. I suspect that the reason for that is that in GraphSharp.Controls, the assembly is used in XAML, but the reference is not added when compiling as there are no references in code. I was able to fix this by adding the following line before assigning the Graph property:
var a = System.Reflection.Assembly.Load("WPFExtensions, Version=1.0.3437.34043, Culture=neutral, PublicKeyToken=null");
This line loads the WPFExtensions library before WPF tries to load it based on the reference in the XAML. Afterwards, the graph was shown.
In my case an assembly wasn't copied to the output folder because copy local was set to false. Setting copy local to true solved it.
(An assembly A was dependent on assembly B which didn't have the copy local flag.)
Related
Consider a public class LogItem and a derived generic class LogItem<T> as shown below. After some wasted time searching for the correct syntax to specify the generic type in XAML, the editor was finally happy. Yet I got the error below trying to build the project. I am using VS 2017 Preview but I am guessing VS 2017 would barf just the same. If I omit the (system:Int32) part, everything goes as planned. I am using NET 4.6.2.
error MC3050: Cannot find the type 'modules:LogItem(system:Int32)'.
Note that type names are case sensitive.
namespace App.Modules
{
public class LogItem
{
public string Info { get; set; }
}
public class LogItem<T> : LogItem
{
public T Content { get; set; }
}
}
xmlns:modules="clr-namespace:App.Modules"
xmlns:system="clr-namespace:System;assembly=mscorlib"
<DataTemplate DataType="{x:Type modules:LogItem(system:Int32)}">
So I looked at this link before:
http://blogs.msdn.com/b/delay/archive/2009/10/26/creating-something-from-nothing-developer-friendly-virtual-file-implementation-for-net.aspx
The class works flawlessly for Files, but it doesn't support directory's etc, does anyone have any idea how I can change the class to support it, I"m no pinvoke whiz. I've tried a million different things, overriding some code to do File Copy and Directory creation of my drop source into the TEMP directory and attempting to trigger a FileDrop, but this locks up the app entirely.
This leads me to believe that there must be a better way to enable directory structure creation as well.
The main part of the Drag and Drop operation is the DragDrop.DoDragDrop method. From the DragDrop.DoDragDrop Method page on MSDN:
public static DragDropEffects DoDragDrop(
DependencyObject dragSource,
Object data,
DragDropEffects allowedEffects
)
Of particular interest is the data parameter:
A data object that contains the data being dragged.
Notice how this parameter is of type Object, so it's completely up to you as to what object you use in the operation. Now I'm not sure what code you found from the page that you linked to, but if I were trying to drag and drop files and folders, I wouldn't need special classes to do it for me.
The simplest way to do that is to just pass the file and/or folder paths instead of the actual data. The control that the data is dropped on can access the data using the file paths just as easily as the drag source. You should be able to locate the DragDrop.DoDragDrop method from your code and easily adapt that code.
If you want to do Drag and Drop operations in the correct way, then I'd recommend that you take a look at the Drag and Drop Overview page on MSDN. It fully explains what to do and provides several code examples.
To implement drag and drop in MVVM without much experience in WPF, you can refer to 5 steps; I will outline these...
Step 1: Attached Behaviours
Add a new class to your project called Behaviours. It should look like this...
public static class Behaviours
{
#region DandBehaviour
public static readonly DependencyProperty DandBehaviourProperty =
DependencyProperty.RegisterAttached("DandBehaviour", typeof(ICommand), typeof(Behaviours),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.None,
OnDandBehaviourChanged));
public static ICommand GetDandBehaviour(DependencyObject d)
{
return (ICommand)d.GetValue(DandBehaviourProperty);
}
public static void SetDandBehaviour(DependencyObject d, ICommand value)
{
d.SetValue(DandBehaviourProperty, value);
}
private static void OnDandBehaviourChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Grid g = d as Grid;
if (g != null)
{
g.Drop += (s, a) =>
{
ICommand iCommand = GetDandBehaviour(d);
if (iCommand != null)
{
if (iCommand.CanExecute(a.Data))
{
iCommand.Execute(a.Data);
}
}
};
}
else
{
throw new ApplicationException("Non grid");
}
}
#endregion
}
This class implements an attached dependency property which can be reached from both your Xaml and your View Model. It hooks the "Drop" event and invokes a command on it.
Step 2: Instrument the Xaml
In this step you need to add the name space to the Xaml so that it can find the behaviours class in Step 1. It looks something like this...
xmlns:b="clr-namespace:DdMvvm"
This statement assigns the alias 'b' to the behaviours. Then you tell the WPF root window to accept drops...
AllowDrop="true"
Then you can add the behaviour to your logical tree thusly...
<Grid AllowDrop="True" b:Behaviours.DandBehaviour="{Binding DandCommand}">
<DockPanel Background="Bisque" AllowDrop="True"/>
</Grid>
Step 3: Add command support
Download Josh Smith's 'Relay Command' from http://msdn.microsoft.com/en-us/magazine/dd419663.aspx For completeness purposes, it is given here as...
public class RelayCommand : ICommand
{ //http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
}
Step 4: Write the View Model
A View Model looks like this...
public class ViewModel : INotifyPropertyChanged
{
public ICommand DandCommand { get; set; }
public ViewModel()
{
DandCommand = new RelayCommand(ExecuteDandCommand, CanExecuteDandCommand);
}
private void ExecuteDandCommand(object obj)
{
if (obj != null)
{
IDataObject ido = obj as IDataObject;
if (ido != null)
{
var fileDrop = ido.GetData(DataFormats.FileDrop, true);
var filesOrDirectories = fileDrop as String[];
if (filesOrDirectories != null && filesOrDirectories.Length > 0)
{
foreach (string fullPath in filesOrDirectories)
{
if (Directory.Exists(fullPath))
{
Console.WriteLine(#"{0} is a directory", fullPath);
}
else if (File.Exists(fullPath))
{
Console.WriteLine(#"{0} is a file", fullPath);
}
else
{
Console.WriteLine(#"{0} is not a file and not a directory", fullPath);
}
}
}
}
}
}
private bool CanExecuteDandCommand(object obj)
{
return true;
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
var handler = System.Threading.Interlocked.CompareExchange(ref PropertyChanged, null, null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
This VM implements a command (called DandCommand) which will be fired by the attached behaviour (remember Step 1?). The 'juicy part' of the VM is the bit which dereferences the drag-and-drop payload. In this particular VM, the code dereferences the data and finds out if it is a file or a directory. It then prints a diagnostic to the console. You can change this part to load images, or internet links, or what-ever can be dropped.
Step 5: Wiring the data context
This is done by different developers in different ways (for industrial apps, lots of people like to use Prism and Unity, but that's ott for a simple how-to like this post). The most straight-forward approach is to change your View to look like this...
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
This code sets the window's data context to your VM so that the binding can take place.
Those steps give you a good starting point for MVVM and drag-and-drop and getting the whole thing to work. The big payback in using MVVM for these things is the clean separation and compartmentalisation that you get. I.e., the VM can be unit-tested without having to instantiate the WPF binding engine.
Hi I build a simple test solution in VS 2012 with two projects.
One is a Class library and the other a WPF application.
Both use .NET 4.5.
In the class library I add an EF element (DataFirst) which maps a simple table.
Next I reference this project in my WPF project.
Add EF from Nuget and using a very simple MVVM like pattern I add a class which looks like this (please note - this is not production code - it's just to reproduce the problem).
public class Class1 {
public static Helper TheHelper { get; set; }
public Class1() {
TheHelper = new Helper();
}
}
public class Helper {
public Helper() {
Nam = "aaa";
}
string connectionString = "metadata=res://*/Mod.csdl|res://*/Mod.ssdl|res://*/Mod.msl;provider=System.Data.SqlClient;provider connection string=\";data source=.\\sqlx8r2;initial catalog=FCdata;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework\"";
public string Nam { get; set; }
#region PCs
private List<PC> m_lPCs;
public List<PC> PCs {
get {
if(m_lPCs == null) {
try {
using(FCdataEntities dE = new FCdataEntities(connectionString)) {
m_lPCs = dE.PCs.ToList();
}
}
catch(Exception eX) {
m_lPCs = new List<PC>();
m_lPCs.Add(new PC() { Description = eX.Message });
}
}
return m_lPCs;
}
set {
if(m_lPCs != value) {
m_lPCs = value;
//RaisePropertyChanged(() => PCs);
}
}
}
#endregion
I also extended the context class like this:
public partial class FCdataEntities : DbContext {
public FCdataEntities(string strCon) : base(strCon) {
}
}
So I can pass the connection string which I copy from app.config.
In my main window I do a simple binding like this:
xmlns:local="clr-namespace:EFTest"
Title="MainWindow" Height="350" Width="1525">
<Window.Resources>
<local:Class1 x:Key="dG" />
</Window.Resources>
<Grid DataContext="{Binding Path=TheHelper, Source={StaticResource dG} }">
<Grid.RowDefinitions>
<RowDefinition Height="17*"/>
<RowDefinition Height="143*"/>
</Grid.RowDefinitions>
<TextBox Text="{Binding Nam}" />
<ListBox ItemsSource="{Binding PCs}" Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
The solution works fine at runtime.
But in the VS Designer I get an exception shown in my "dummy obect" which I create in the catch block of the property.
Could not load file or assembly 'Windows, Version=255.255.255.255, Culture=neutral, ContentType=WindowsRuntime' or one of its dependencies. Operation is not supported. (Exception from HRESULT: 0x80131515)
What I need is data at design time - and no "dummy data" - instead I want to get it from the DB - avoiding the use of EF (and use linq2sql for an example) works like a charm.
Did I make a mistake with the connection string (or so) - or is there simply a problem in EF 5.0?
This particular error is a known issue in Entity Framework 5 with the Visual Studio & Expression designers and has been fixed in the Entity Framework 6 source code. However there are other design time errors which I haven't been able to resolve which prevent Entity Framework code from running in the designer.
I have a simple problem with the designer.. I'm trying to bind the ItemsSource of a DataGridComboBoxColumn to a list of Enum values. It works, the code compiles and executes just fine. However, the designer says "Problem loading" and will not load properly. If I click "Reload the designer" it shows an error in the Error List. I'm using VS2010.
<ObjectDataProvider x:Key="myEnum"
MethodName="GetValues"
ObjectType="{x:Type core:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type Type="data:ParentClass+MyEnum" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
This works fine when the application executes. However, in the designer it says:
Error 5 Type 'data:ParentClass+MyEnum' was not found.
I'm not sure where I ever came across the Class+Subclass syntax (instead of Class.Subclass) for XAML or why it is necessary, but it would seem the designer should work if the code works?! This kills all my design-time support for my entire window, which is not good if I want to see what changes look like at design time
Update
Alright, some more information: First off, the + syntax comes from the Type.GetType(String) method and you can see its formats there.
However, the System.Windows.Markup.TypeExtension uses the IXamlTypeResolver service to resolve the type. From reflector, we can see this:
IXamlTypeResolver service = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;
this._type = service.Resolve(this._typeName);
And from what I understand, the designer uses an entirely different implementation of this service than the runtime?! I haven't located the implementations.
I believe that I could write my own "TypeExtension" class and just do return Type.GetType(typeName). I'm still curious if this is just a bug or a way to make it work.
Update2
I created my own TypeExtension class but it did not help. For some reason Type.GetType() fails to resolve my Enum through the designer (but not runtime)
public class CreateTypeExtension : TypeExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (this.Type == null)
{
if (this.TypeName == null)
{
throw new InvalidOperationException();
}
this.Type = Type.GetType(TypeName);
if (this.Type == null)
{
throw new InvalidOperationException("Bad type name");
}
}
return this.Type;
}
}
I was passing it
<comm:CreateType TypeName="Company.Product.Class+Enum,Company.Assembly" />
Again, works at runtime and is fully qualified yet it doesn't work at design time.
Alright, I realized that when it didn't work after Update2 that the error message was much more specific than what I was throwing. Therefore, it wasn't using my override. I modified it to not extend TypeExtension and instead MarkupExtension and now it works.
public class CreateTypeExtension : MarkupExtension
{
public Type Type { get; set; }
public String TypeName { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (this.Type == null)
{
if (this.TypeName == null)
{
throw new InvalidOperationException();
}
this.Type = Type.GetType(TypeName);
if (this.Type == null)
{
throw new InvalidOperationException("Bad type name");
}
}
return this.Type;
}
}
And you can use any format available in the documentation for Type.GetType, but you can no longer use the XAML prefixes (unless you implement this yourself)
<comm:CreateType TypeName="Company.Product.Class+Enum,Company.Assembly" />
I am having problems getting validation to work properly in the designer for my custom activity. The simplest sample to reproduce the behavior is as follows:
I have a custom WF4 activity with a dynamic collection of arguments stored in a dictionary:
[Designer(typeof(DictionaryActivityDesigner))]
public class DictionaryActivity : NativeActivity
{
[Browsable(false)]
public Dictionary<string, InArgument> Arguments { get; set; }
public InArgument<string> StringArg { get; set; }
public DictionaryActivity()
{
Arguments = new Dictionary<string, InArgument>();
}
protected override void Execute(NativeActivityContext context)
{ }
}
In the designer I dinamically create expression text boxes for editing these arguments. The user has the possibility to define the arguments and their types in a separate modal window, but for the sake of simplicity I have fixed the arguments in this sample:
public partial class DictionaryActivityDesigner
{
private Dictionary<string, Type> definition;
public DictionaryActivityDesigner()
{
definition = new Dictionary<string, Type>
{
{ "String Arg", typeof(string) },
{ "Int Arg", typeof(int) }
};
InitializeComponent();
}
public void InitializeGrid(Dictionary<string, Type> arguments)
{
ArgumentsGrid.RowDefinitions.Clear();
ArgumentsGrid.Children.Clear();
int gridRow = 0;
foreach (var arg in arguments)
{
ArgumentsGrid.RowDefinitions.Add(new RowDefinition());
var label = new Label()
{
Content = arg.Key + ":"
};
Grid.SetRow(label, gridRow);
Grid.SetColumn(label, 0);
ArgumentsGrid.Children.Add(label);
var textbox = new ExpressionTextBox()
{
ExpressionType = arg.Value,
OwnerActivity = ModelItem,
UseLocationExpression = false
};
var binding = new Binding()
{
Mode = BindingMode.TwoWay,
Converter = new ArgumentToExpressionConverter(),
ConverterParameter = "In",
Path = new PropertyPath("ModelItem.Arguments[(0)]", arg.Key)
};
textbox.SetBinding(ExpressionTextBox.ExpressionProperty, binding);
Grid.SetRow(textbox, gridRow);
Grid.SetColumn(textbox, 1);
ArgumentsGrid.Children.Add(textbox);
gridRow++;
}
}
private void ActivityDesigner_Loaded(object sender, RoutedEventArgs e)
{
InitializeGrid(definition);
}
}
Below is the XAML for the designer:
<sap:ActivityDesigner x:Class="ActivityValidation.DictionaryActivityDesigner"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
xmlns:sapc="clr-namespace:System.Activities.Presentation.Converters;assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation"
Loaded="ActivityDesigner_Loaded">
<sap:ActivityDesigner.Resources>
<ResourceDictionary>
<sapc:ArgumentToExpressionConverter x:Key="ArgumentToExpressionConverter" />
</ResourceDictionary>
</sap:ActivityDesigner.Resources>
<StackPanel Orientation="Vertical">
<Grid Name="ArgumentsGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition MinWidth="250" />
</Grid.ColumnDefinitions>
</Grid>
<sapv:ExpressionTextBox ExpressionType="s:String"
OwnerActivity="{Binding ModelItem}"
Expression="{Binding ModelItem.StringArg, Mode=TwoWay, Converter={StaticResource ArgumentToExpressionConverter}, ConverterParameter=In}" />
</StackPanel>
</sap:ActivityDesigner>
The InitializeGrid method adds the expression text boxes for the arguments to the ArgumentGrid. Under it I have a separate statically defined expression text box for a fixed argument in the activity to demonstrate the (almost) desired behavior.
Now for the problems:
Invalid expressions for the dynamic arguments only cause the error icon to appear beside the text box but it doesn't propagate to the top bar of the designer as it does if there is an error in the statically defined text box.
If I close the designer in such invalid state (and save the definition), the eror icon correctly propagates to the top bar even if the error is only in the dynamic text box. Though the behavior gets even more strange afterwards. After changing the values for the arguments, now even the error icon beside the text box doesn't work consistently any more.
If I delete the contents of a dynamic text box completely, the value in the dictionary gets set to null which manifests in the workflow definition as <x:Null x:Key="String Arg" /> instead of <InArgument x:TypeArguments="x:String" x:Key="String Arg">["a"]</InArgument> or just ommiting the entry as is the case before editing the expression for the first time. If I reopen such a workflow even the statically created text box doesn't work properly any more (the error icon is only visible when text box is focused and it doesn't propagate to the top any more).
It seems obvious that I am doing something wrong when creating the dynamic text boxes. What would be the correct way of doing it? Is there any example available for creating a designer for a custom activity with dynamic number of arguments?
EDIT:
For those interested:
There was some more discussion on MSDN Forums where I have also posted the issue.
As a result of that discussion, I've also filed a report on Microsoft Connect.
I encountered the problem I described here while trying to create a designer for a dynamic collection of arguments in an activity. I managed to work around the problem by using the built-in DynamicArgumentDialog window. I had to restructure my activity to accept a single collection of both input and output arguments:
public Dictionary<string, Argument> Arguments { get; set; }
instead of two separate collections I was using before:
public Dictionary<string, InArgument> InArguments { get; set; }
public Dictionary<string, OutArgument> OutArguments { get; set; }
I found the Custom Activity to Invoke XAML Based Child Workflows very helpful when making this work.