Unable to use the same resource in two different Canvases - c#

I have an application with two Canvas controls. When the application starts, it loads from a DLL a UserControl which is basically Canvas with a bunch of XAML code.
I want to be able to display this control in the two Canvases, however, when I use the ContentPresenter and bind it in both Canvases to the control loaded from the DLL it shows only on one canvas and on the other it doesn't. I guess it's something that has to do with the fact that I'm actually using the same resource in two different Canvases, but since I wanted to avoid using too much memory (the control loaded from the DLL is pretty heavy) I didn't want to create two copies of the same control.
Does anyone have a better approach / solution for this situation?
This is the XAML for showing the loaded control in one of the two canvases (the other canvas uses similar code)
<Canvas Width="{Binding MapModel.MapControl.Bounds.Width}" Height="{Binding MapModel.MapControl.Bounds.Height}">
<Canvas.Background>
<VisualBrush>
<VisualBrush.Visual>
<ContentPresenter Content="{Binding MapModel.MapControl}" />
</VisualBrush.Visual>
</VisualBrush>
</Canvas.Background>
</Canvas>
And loading from the DLL is performed by:
// Load the map library assembly (Using reflection)
Assembly asm = Assembly.LoadFile(m_fileName);
Type[] tlist = asm.GetTypes();
// Find the class that represents the airport XAML drawing in the assembly, if it finds the airport class then
// set the value to be an instance of that class.
foreach (Type t in tlist)
{
if (t.Name == "Map")
{
MapControl = Activator.CreateInstance(t) as UserControl;
break;
}
}
Thanks in advance!

Set attribute x:Shared="False" to your resource.
It is 'true' so wpf creates one resource (for optimize performance) by default. When you set it 'false' wpf creates new instance per request.
There are sample of this

Related

How to show UserControl dynamically loaded from outer assembly in MVVM fashion

I'm developing a WPF MVVM application.
MainWindow VM loads a target outer assembly containing a UserControl and its ViewModel.
I want to show this UserControl in my MainWindow View.
I think I should use DataTemplates, but I can't understand how to make them work with dynamically loaded Types. I've no code to show because I've no idea on how to proceed, any suggestion is appreciated.
EDIT: here below the code used to load UC and VM from assembly
Assembly assembly = Assembly.LoadFile(testProgramPath);
var publicTypes = assembly.GetTypes().Where(t => t.IsPublic).ToArray();
TestProgramUserControl = publicTypes.Single(t => t.BaseType.FullName == "System.Windows.Controls.UserControl");
TestProgramUserControlViewModel = publicTypes.Single(t => t.GetCustomAttribute<TestProgramUserControlViewModelAttribute>() != null);
I can't make any assumption about UC or its VM, I want to display it in my MainWindow whatever it contains or does. It will be then its duty to communicate via proper messaging with suitable recipients.
My suggestions are a bit long for a comment.
Since you have "no idea" how to go about this here are some suggestions to point you in the right direction.
Managed Extensibility Framework is designed for dynamic discovery for extending applications in the way you describe. They made it for you.
https://learn.microsoft.com/en-us/dotnet/framework/mef/
As well as putting your view and viewmodel in this assembly, I recommend also putting a datatemplating resourcedictionary in there. You can use this to associate the view type with viewmodel type.
You can use mef or just a naming convention to define what this resource dictionary is.
To make a resource dictionary discoverable by mef you need to add a code behind class for it. You can then apply the correct attribute to that eg:
[Export(typeof(ResourceDictionary))]
public partial class ExternalDataTemplateResourceDictionary : ResourceDictionary
{
public ExternalDataTemplateResourceDictionary ()
{
InitializeComponent();
}
}
To connect that class up to your resourcedictionary you use a similar mechanism to that you have probably seen in windows or usercontrols. You use x:Class in it's opening tag:
<ResourceDictionary
....
x:Class="YourProject.ExternalDataTemplateResourceDictionary "
When you discover a dll, you load it's contents and merge it's templating resourcedictionary.
The parent view then doesn't need to explicitly know that your specialFoo usercontrol is associated with a superFooViewModel in this dll it's loaded. That merged datatemplate does that using the "standard" viewmodel first datatype association.
Thanks to advices here, and on SO WPF chat, I solved my problem as follows.
I added a constraint: my outer assembly can contain only one UserControl, and this user control must define a DataTemplate as a resource with a fixed name.
My main VM gets from outer UC the only resource with the aforementioned fixed name.
My main View uses this DataTemplate as ContentTemplate for a ContentPresenter.
Some simplified code for Outer User Control:
<UserControl xmlns:local="clr-namespace:MyNamespace">
<UserControl.Resources>
<ResourceDictionary>
<DataTemplate x:Key="FixedKeyTemplate"
DataType="{x:Type local:MyOuterViewModel}">
<StackPanel>
...
</StackPanel>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
</UserControl>
Main View Model:
Assembly assembly = Assembly.LoadFile(testProgramPath);
var publicTypes = assembly.GetTypes().Where(t => t.IsPublic).ToArray();
Type userControlType = publicTypes.Single(t => t.BaseType.FullName == "System.Windows.Controls.UserControl");
UserControl userControlView = Activator.CreateInstance(userControlType) as UserControl;
DataTemplate userControlDataTemplate = userControlView.Resources["TestProgramGUIDataTemplate"] as DataTemplate;
Type userControlViewModelType = publicTypes.Single(t => t.GetCustomAttribute<UserControlViewModelCustomAttribute>() != null);
object userControlViewModel = Activator.CreateInstance(userControlViewModelType);
Main View:
<ContentPresenter Content="{Binding UserControlViewModel}"
ContentTemplate="{Binding Path=DataContext.UserControlTemplate,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}}}"/>
#Andy suggestion is more "professional", but as far as I'm in control of the complete application, and I'm also the only user, I think I can be satisfied by this simpler solution.

WPF - MVVM DataTemplate load to memory for reuse

I have a window which has several different DataTemplate that are load to a ContentControl based on a RadioButton (The RadioButton sends a command to the ModelView which sets the Content property of the ContentControl.
It works well, but now several views contain a "heavy" object (Eyeshot CAD viewer).
Switching to any of these view causes a delay (at this moment there's absolutely zero logic in the whole software other than the view/view model)
Is there a way to load the view and the heavy control to memory once and then reuse it when switching to its view? (The ViewModel of that view is currently a singleton but that doesn't help)
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Height="160" Margin="0,0,0,12">
... Removed for clarity
</StackPanel>
<ContentControl x:Name="Tabs" Content="{Binding SelectedTabViewModel}" Margin="0,12,0,12"/>
</DockPanel>
On your DataTemplate, you can set the attribute x:Shared="True", this will allow the framework to reuse the visual control (inside the datatemplate) for another ContentPresenter.
This doesn't load the component at starting, but, this reuse it once instantiated.

Accessing a given child of a ControlTemplate programmatically

I have this ControlTemplate that contains stuff (gradients in this case) that I want to be able to access programmatically and after hours of trial and error I feel that it's finally time to turn to you for assistance, StackOverflow.
The template generates a flower, and I didn't know what to use, so I just picked the Thumb-element, since I've used that before in a similar manner. If you can think of anything else that would be better suited, please let me know.
Anyways, this is the beginning of my ControlTemplate, from the XAML-file:
<ControlTemplate x:Key="cherryFlowerStyle" TargetType="{x:Type Thumb}">
<Viewbox Width="119.560" Height="114.268" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Canvas Width="119.560" Height="114.268">
<Canvas>
<!-- Layer 1/<Path> -->
<Path Data="... (removed to save space) ...">
<Path.Fill>
<RadialGradientBrush x:Name="cherryFlowerColorGradient" MappingMode="Absolute" GradientOrigin="593.380,333.416" Center="593.380,333.416" RadiusX="36.460" RadiusY="36.460">
<RadialGradientBrush.GradientStops>
<!-- Flower color -->
<GradientStop x:Name="cherryFlowerColorGradientOuterColor" Offset="0.15" Color="#ffd6e062"/>
And here's what I'm doing in C#:
Thumb flower = new Thumb();
flower.Template = TryFindResource("cherryFlowerStyle") as ControlTemplate;
GradientStop grStop = (GradientStop)flower.Template.FindName("cherryFlowerColorGradientOuterColor", flower);
Console.WriteLine("gradient: " +grStop);
Creating a new Thumb and applying the template works (it's drawn as a flower on the canvas).
Trying to access the gradients inside the template, however, does not work. I hope there's a good solution to this, or else I have to do it the ugly way; create a flower off-screen (in XAML) and reference that in the code-behind, because that works :/
Thanks in advance!
That won't work, because the Gradient is not a child of your template, its the value of the property of a child of your template. So you can access the Path by name, and modify its Fill value. But remember, the Gradient might be frozen.
Why not use binding and the DataContext for that? Or yet better, use dependency properties to modify the colors directly and just use TemplateBinding. It would be a much much better way to handle that, with less work. Also the Thumb element is used for moving elements and it encapsulates the mouse handling for that, if you don't need that the Control element would be a better base class. If you want performance, Visual or ContainerVisual would be much better, but also very limited.

Loading vector graphics from XAML files programmatically in a WPF application

I would like to load vector graphics stored as XAML files (separate files, not in a dictionary), embedded in my application, and I have a few questions to do so:
XAML looks a bit ambiguous, since it can be used to represent either static resources like vector images, or interfaces which are being dynamically built like the ones in WPF. Because of this, the format of a XAML vector image is unclear to me : what should be the root element, like the "svg" tag for svg vector images ? Currently, I'm using a Canvas as the top element since I want to plot my graphics in another Canvas.
What is the best method to load those file programmatically (I mean, to create the Canvas from the xaml files) ? I've seen (and tried) different solutions with XamlReader, but nothing worked: the app crashes and the debugger does not help (most problems I've encountered seem to occur during the parsing, and the error message was unclear).
I've read http://learnwpf.com/post/2006/06/04/How-do-I-Include-Vector-Based-Image-Resources-in-my-WPF-Application.aspx, but the link to the article dealing with resource files loading is dead, and the images are not created using C# code.
Okay, I found the solution by myself and here it is :
My project is named "Editor", and I've placed the XAML file I want to read in a "Graphics" folder. This file is named "Image.xaml".
The project tree looks like this :
The XAML file itself holds this code :
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Width="40" Height="40">
<Rectangle Canvas.Left="0" Canvas.Top="0" Fill="White" Stroke="Black" StrokeThickness="1" Height="40" Width="40" />
<!-- ... -->
</Canvas>
(the xaml namespace 'xmlns' reference is needed)
The code used to load the file is :
StreamResourceInfo sr = Application.GetResourceStream(new Uri("Editor;component/Graphics/Image.xaml", UriKind.Relative));
Canvas result = (Canvas)XamlReader.Load(new XmlTextReader(sr.Stream));
layoutRoot.Children.Add(result);
'layoutRoot' being the name of the main Canvas of my application.
Last subtility : the property 'BuildAction' of the *.xaml file must be set to 'Resource', or you will encounter a XamlParseException with hexadecimal value 0x0C (to change this property, right-click on the file in the project treeview).
Hope this can help.

Access methods of a class from a control template in a resource dictionary

I want to make a WindowBaseClass that derives from Window but has a few custom functionalities. Such that WindowStyle would be none, I have my own color scheme applied and also have a resize logic.
Here is a snippet of the XAML that contains one of the 'borders' that has a MouseMOve and PreviewMouseDown events.
<Rectangle Stroke="{x:Null}" x:Name="top" VerticalAlignment="Top" Height="5"
Grid.Column="1" Grid.Row="0" PreviewMouseDown="Resize"
MouseMove="DisplayResizeCursor">
<Rectangle.Fill>
<SolidColorBrush Color="{StaticResource Ocean}"/>
</Rectangle.Fill>
In my code behind I have methods such as resize, drag etc. When it is all contained in a Window1.xaml/.cs it's all working nice.
Now I want to create a custom template (in a resource dictionary for example) with my color schemes and I want the PreviewMouseDown from the rectangle to point to the method defined in a class that extends Window.
Can it be done? Any help would be greatly appreciated.
I don't think you'd approach it quite like that. I think you'd subclass Window and add in your custom PreviewMouseDown logic, etc. and then you would set up the styles for your new subclass in the resource dictionary.
You might also want to look into a custom attached property.
If PreviewMouseDown, etc. are doing logic specific to one window as opposed to general functionality, this probably isn't going to work out so great either way.

Categories

Resources