I have a WPF program in which must draw an array of "bricks" onto a Canvas. There is a handful of different brick types, each of which looks different.
I want each brick type to be drawn using a fairly arbitrary fragment of XAML, e.g.
<Canvas> <!-- or some other per-brick container -->
<Rectangle Fill="Brown" Stroke="Gray" StrokeThickness="0.1" Canvas.Left="-1" Canvas.Top="-1" Width="2" Height="2" />
<Ellipse Fill="Blue" Canvas.Left="-0.8" Canvas.Top="-0.8" Width="1.6" Height="1.6"/>
</Canvas>
But I want the code-behind to clone this fragment many times, translating and rotating each copy into position.
This is similar to what an ItemsControl does, except I need to calculate my own brick positions. It also seems similar to what ControlTemplate does, but I don't understand that well enough to solve my problem.
Can anyone explain how it should be done?
There are several approaches but the one I'd go for is using view models and yes, an ItemsControl. You can specify what type of container the ItemsControl should use and pass a Canvas in, and then you can bind a collection of Bricks to the ItemsSource of the ItemsControl.
Then for each type of brick you can define a DataTemplate that specifies how that type should be rendered. If the items have properties such as X and Y you can bind those to the Canvas.Left and Canvas.Right properties in the DataTemplate and off you go...
A good example of what I mean is https://stackoverflow.com/a/1030191/430661 (the most upvoted answer, not the selected one), except there the item template is specified inline instead of through DataTemplates. Just leave the itemTemplate empty and let the framework select the proper DataTemplate based on type.
Now on the other hand if you expect to have many bricks on screen, or perform complex animations on them, or anything like that... this probably won't perform well enough for that kind of scenarios. But then you're probably better off not using WPF for that anyway...
Let me know if you need more help and I can throw together a sample for you.
Related
To simplify the question, I will describe a simplified use case:
Let's say I have a top level Grid that contains 1 dynamic inner Grid and a ListBox that fits in certain cells of the top level Grid:
The top level Grid structure (TopGrid.xaml) is relatively static with only dynamic size of the cell that will contain the inner Grid.
The inner Grid structure is dynamic, there are many/unkown kind of contents, but each kind of content is static and can be represented by a separate XAML. So I will have InnerGrid_1.xaml, InnerGrid_2.xaml, InnerGrid_n.xaml, etc.
At run time, depend on the ListBox selection, certain InnerGrid should be displayed.
I'm thinking of two ways to do it:
Run time text-edit the XAML in the code:
In the code, use XmlDocument to edit the TopGrid.xaml to add the content of certain InnerGrid_x.xaml to create the final in-memory TopGrid.xaml. Then use XamlReader to read the final TopGrid.xaml into a Visual Tree, then add the tree to LayoutRoot to show it.
Visual Tree manipulation (no text edit):
In the code, use XamlReader to read the TopGrid.xaml into a top-visual-tree, read InnerGrid_x.xaml into a sub-visual-tree. Then use VisualTreeHelper to find the TopGrid element in top-visual-tree, find the InnerGrid element in the sub-visual-tree. Then add the sub-visual-tree to the top-visual tree. Then add the combined tree to LayoutRoot show it.
I know the first way will work but need complex XML manipulation (or text editing) in the code. I desire the second way but not sure if it will work. Especially, I will probably change the size of the cell in the TopGrid to hold the dynamic InnerGrid.
Anybody has done this and can shed some light? or any better solution?
A side question: Seems the dynamically loaded XAML cannot specify event handler inside XAML (my test), I need set event handler dynamically after creating the tree. Is that true?
The WPF way is to just bind to your data and let WPF resolve the visual tree:
<Grid>
...
<ContentControl Content="{Binding SomeData}"/>
In order to render your content, WPF will look for a DataTemplate matching the type of data bound. So, for each type of data you have, you would define a DataTemplate:
<DataTemplate DataType="...">
<!-- your visual tree representing the data goes here -->
</DataTemplate>
You can also tell WPF explicitly how to represent your data by specifying the ContentTemplate property:
<ContentControl Content="{Binding SomeData}" ContentTemplate="{StaticResource MyContentTemplate}"/>
TLDR: neither of your proposed approaches are idiomatic WPF. You should look into data templating.
What's the best way to enable dynamic skinning of a WPF Application when some items requiring skin modification do not support values of type DynamicResourceExtention? In particular, our problem is that ConverterParameters require StaticResourceExtentions.
Here's our situation with ConverterParameters Using Visual Studio 2008 and WPF 3.5.
We have a custom converter which takes a value and a parameter and simply returns their product.
Very simple, works fine, and we use it for various tasks, including setting some window element sizes. For example, passing a value of "Source={x:Static SystemParameters.PrimaryScreenHeight}" and a parameter of "0.1" enables us to set an element's height to exactly 1/10 of the screen height.
Height="{Binding Source={x:Static SystemParameters.PrimaryScreenHeight},
Converter={StaticResource PctConverter},
ConverterParameter=0.1}"
where PctConverter is a resource reference to our custom converter. No problem there.
Now we want to skin the application dynamically, by extracting the ConverterParameter and putting it in a seperate resource. For example, we might want the element height to be 0.1 of the screen height in some skins, and say 0.25 of the screen height in others. Initially we thought we'd simply set the ConverterParameter to a DynamicResource, but this is not supported, so we have to set it using a StaticResourceExtension like this:
Height="{Binding Source={x:Static SystemParameters.PrimaryScreenHeight},
Converter={StaticResource PctConverter},
ConverterParameter={StaticResource OurElementHeightParameter}}"
where OurElementHeightParameter is defined in a seperate ResourceDictionary (call it MainResource.xaml) as follows:
<sys:Double x:Key="OurElementHeightParameter">0.1</sys:Double>
(where namespace is defined as xmlns:sys="clr-namespace:System;assembly=mscorlib".)
This works fine, as far as extracting the CustomParameter is concerned, but it still hasn't enabled us to change our ConverterParameter by swapping skins on the fly.
After researching this some more, in particular the following articles
How to assign wpf resources to other resource tags
Skinning using a color as staticresource for another color
Aliasing resources
what we think we need to do now is take our StaticResourceExtention and set its value dynamically behind the scenes using resource aliases.
Trying to do this, we replaced the previous OurElementHeightParameter resource with the following two resources
<sys:Double x:Key="SkinnedHeightRatio">0.1</sys:Double>
<StaticResourceExtension x:Key="OurElementHeightParameter" ResourceKey="SkinnedHeightRatio" />
which works fine, producing an identical result.
When that worked okay, we thought it would be a simple matter of placing the SkinnedHeightRatio resource in a seperate ResourceDictionary (call it Skin.xaml) and merging that with the original MainResource.xaml ResourceDictionary and we would have the dynamic skinning we are after.
But, as soon as we extract <sys:Single x:Key="SkinnedHeightRatio">0.1</sys:Single> to another ResourceDictionary we encounter build error as follows:
Unknown build error, 'Index was out of range. Must be non-negative and less than the size of the collection.'
Even more strange is that if we keep the two resources above in the same ResourceDictionary and just seperate them by putting another random resource between them, for example
<sys:Double x:Key="SkinnedHeightRatio">0.1</sys:Double>
<Thickness x:Key="SomeRandomResource" >5</Thickness>
<StaticResourceExtension x:Key="OurElementHeightParameter" ResourceKey="SkinnedHeightRatio" />
then the OurElementHeightParameter points to the SomeRandomResource directly above it and not the
resource specified in its ResourceKey property (SkinnedHeightRatio) which is only 2 lines above it...
In this case, the parameter passed to the converter is the Thickness SomeRandomResource.
All very confusing, and makes us think we are barking up the wrong tree completely. So where are we going wrong?
If anyone needs full code for an application reproducing the problem, I can post it up.
Any pointers greatly appreciated.
It might be simpler to create a multi-value converter and bind to two values for it.
for reasons based on storabiligty I have the following objects in a XAML graph:
A WorkArea,
Containing WorkSheets,
Containing WorkItems
WorkArea, workSheets are ItemsControl instances.
To start: The reason that I am not using standard elements here is that mine are going to get loaded / saved - they represent a business contect (actually the work area of a trading application), and I want those to have as few "surplus" elements as I can. I especially do not want to be tied into user level controls that are from a third party and regularly changing dll names (during upgrades - the major version is encoded there) and I am not sure I will not replace them at all, so I rather go with my own "slim" objects.
The WorkArea corresponds to a window (actually there is a WorkAreaWindow that will take the WorkArea as ContentItem.
The WorkSheets are supposed to work like a TabControl - you can switch between them.
How do I do that? ;)
I get the impression that with the templating mechanisms I could possibly "visuall wrap" the WorkSheets as pages in a TabControl, but I am pretty much totally lost on the how. Anyone can enlighten me?
Here is how far I got:
My Herarchy is WorkArea -> WorkSheet(s) -> WorkItem(s)
WorkArea should be presented as a TabControl, with one tab per WorkSheet.
WorkArea:
<local:WorkArea x:Name="WorkArea">
<local:WorkArea.Template>
<ControlTemplate>
<TabControl>
<ItemsPresenter />
</TabControl>
</ControlTemplate>
</local:WorkArea.Template>
<local:WorkArea.ItemTemplate>
<DataTemplate>
<TabItem Header="{Binding Path=Title}">
<ContentPresenter />
</TabItem>
</DataTemplate>
</local:WorkArea.ItemTemplate>
<local:WorkSheet Title="Markets">
<local:WorkTile local:WorkSheet.Row="2" local:WorkSheet.Column="3">
test-11
What I can see now is a TabControl, with one Tab. No text, all content in the one tab. Anyone an idea how to split this further?
You should carefully read Josh Smith's introduction to MVVM here and look at the demo application source code. The demo application is almost exactly what you are asking for. It generates a tabbed interface dynamically based on custom classes for "contacts" data using data templates, observable collection binding and tabcontrol/tabitem. Some of the MVVM and commanding stuff might not be your thing, but a portion of the code does what you are looking for. The XAML has no code behind at all. You would simply set that data context of your window to your work area class instance that would have an observable collection of worksheets which in turn have an observable collection of workItems and item/data templates will do everything.
I am trying to come to a working understanding of how databinding works, but even after several tutorials I only have a basic understanding of how databinding works. Thus this question might seem fundamental to those more familiar with silverlight. Even if it is trivial, please point me to some tutorial that deals with this problem. All that I could find simply solved this via adding the data binding on a parent page.xaml (that i must not use in my case).
For the sake of this example let us assume, that we have 5 files:
starter.cs
button1.xaml + codeBehind
button2.xaml + codeBehind
The two buttons are generated in code in the starter(.cs) file, and then added to some MapLayer
button1 my_button1 = new button1();
button2 my_button1 = new button2();
someLayer.Children.Add(my_button1);
someLayer.Children.Add(my_button2);
My aim is to connect the two buttons, so that they always display the same "text" (i.e. my_button1.content==my_button2.content = true;). Thus when something changes my_button1.content this change should be propagated to the other button (two way binding).
At the moment my button1.xaml looks like this:
<Grid x:Name="LayoutRoot">
<Button x:Name="x_button1" Margin="0,0,0,0" Content="{Binding ElementName=x_button2, Path=Content}" ClickMode="Press" Click="button1_Click"/>
</Grid>
But everthing that i get out of that is a button with no content at all, it is just blank as the binding silently fails.
How could I create the databinding in the context I described? Preferably in code and not XAML ;)
Thanks in advance
The chunk of documentation you need to read is this: XAML Namescopes
Your button1 xaml has a binding looking for an element with the name "x_button2". However in a real application there can be many controls which in turn have nested controls. All of these controls have all manner of UI elements some of which may have names.
It would be impossible to get anything done if all names throughout the entire application had be unique. Yet that would need to be true if it were for your button1 to be able to hunt down the existence of another control somewhere in the visual tree outside of that which it actually knows (its own xaml).
Hence each loaded Xaml document exists in its own "namescope" and the search for other elements with other names is limited to that "namescope".
The are various solutions to this problem depending on what you real requirements are as opposed to the simplified problem in your question.
Typically you give each of your controls a DependencyProperty to which the inner button Content property binds. In "MapLayer" as call it, could then bind the propert on one of your button controls to the other.
I am trying to learn WPF and so far I love it. However, there is something missing or simply something that I don't understand. How can we display multiple layers of controls in WPF? Attached, the screenshot gives a good idea of what I am trying to do. I have a window and I want to display something else on top of it. How can I do that?
Placing two elements into the same row/column in a Grid simply overlays them. You can then use the Visibility on each element to show/hide them. So for example:
<Grid>
... row/column definitions
<Grid Grid.Row="0" Grid.Column="0">
... main content here
</Grid>
<Grid Grid.Row="0" Grid.Column="0" x:Name="grid2">
... overlaid content here
</Grid>
</Grid>
Now the overlaid content will appear over the top of the main content content. Setting grid2.Visibility to Visible/Hidden will show/hide your overlaid content.
I can think of two ways to achieve this effect (non rectangular semi transparent overlay on top of a window that extends behind the window's bounds).
First option, DON'T USE THIS IN PRODUCTION CODE (AllowTransparency is slow and very buggy) - Make the window larger then the actual content, set AllowsTransaprencey="True" so that the area "outside" of the window is transparent, now all you have to do for the popup is to add another visual in the same container as the "window" .
Second option, use a Popup, you will have to position your popup carefully, call SetWindowRgn via interop to make it non rectangular and call the Win32 API function that sets transparency value (sorry, don't remember the name at the moment) to make it semi-transparent.
The first option is easy to implement and will look better then the second option, but it's slower and you will run into all sorts of weird bugs (including crushes with some display drivers) - I know this from experience, when I switched the software I'm selling from AllowTransparency to SetWindowRgn 90% of the crush reports disappeared.
The second options requires a lot of native Win32 API calls but it should work.
I don't know much about WPF myself, but is the Panel.ZIndex attached property what you're after, perhaps?
you can use adorner layer to overlay . It's basically a rendering surface that overlays any adorned element.
You could achieve a similar effect by overlaying things in a grid (things just stack on top of each other if they're in the same cell), but my spider sense would tell me that that is just another window defined something like:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SubWindow" Height="100" Width="100" WindowStyle="None" AllowsTransparency="True" Opacity="0.5" >
You could then show it in you main window by doing something like:
SubWindow sw = new SubWindow ();
sw.DoModal();
Not sure exactly what you are trying to accomplish but from my brief encounters with WPF, it seems like maybe you might accomplish what you need with the proper configuration of a parent\child relationship between your two windows. I recently so a basic 'hello, world' on Dr. Dobb's portal that may give you some direction.
Here's the link to the training tutorial video -- Dr. Dobb's TV you will just need to select the "Build a Standard WPF Application' video from the list...