WPF Dynamic Binding X and Y Co-ordinates - wpf

I have a question on WPF dynamic positioning.
I want to place Elipses on the screen based on X and Y co-ordinates that i have stored in a collection in C#.
I have been made aware of the drawing ability in WPF which you do from C# using the Windows.Media and Windows.Shapes.
Now what i actually want to do is use these namespaces to draw the elipses in the first case in a canvas all done in c# using my datasource that i have in c# to position the elipses using the x and y co-ordinates.
Now the complex part which is confusing me is what if the data in the datasource is changed as the data in the database changes, i will implement some sort of routine that checks the database every few seconds pulling back back any data that has changed since the last retrieval. Now i have seen the IPropertyChanged interface which i will inhert from for my class that i expose as my datasource for the page so when i retrieve the updated dataset i can call the PropertyChanged event which will notify WPF that the datasource has changed.
How would i bind the elipses in the UI when i was laying them out originally in C# to certain items from the datasource so when the datasource changed the elipses would automatically change as required to reflect the changed datasource as long as the ID for each x and y co-ordinate remained the same. So can i bind to specific rows from the collection for each elipse in my canvas when i'm setting them out?
I don't even know if its possible to bind a datasource to a Canvas inside which i can use the collection as i require to begin with but i thought i'd put this question out there incase someone has done something similar so i have a good starting point.
Thanks
Iffy.

To build on what others have said here is a complete self contained example - you can copy it straight into kaxaml or xamlpad (or blend, but I think in that case it has to go into a body of a usercontrol or a window) and see how it works.
Instead of using the rendertransform I prefer to use the canvas and set the left and top property, I just find it more readable that way. Alternatively, you can use a grid and set the margin but then you'll need a value converter of some kind.
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid.Resources>
<!-- This is our list of shapes, in this case an inline XML list -->
<XmlDataProvider x:Key="ShapeList">
<x:XData>
<ObjectList xmlns="">
<Shapes>
<shape height="30" width="30" x="50" y="50"/>
<shape height="30" width="40" x="100" y="100"/>
<shape height="30" width="50" x="150" y="150"/>
<shape height="30" width="60" x="200" y="200"/>
<shape height="30" width="70" x="250" y="350"/>
</Shapes>
</ObjectList>
</x:XData>
</XmlDataProvider>
</Grid.Resources>
<ItemsControl ItemsSource="{Binding Source={StaticResource ShapeList}, XPath=ObjectList/Shapes/*}">
<!-- this template sets the panel as canvas for easy positioning -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- this template defines how each bound item is represented -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Width="{Binding XPath=#width}" Height="{Binding XPath=#height}">
<Ellipse Fill="White" Stroke="Black" StrokeThickness="2"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<!-- This style positions each bound item's container -->
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding XPath=#x}"/>
<Setter Property="Canvas.Top" Value="{Binding XPath=#y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
Instead of binding to an inline xml list you can bind to a collection on your viewmodel (best choice), a dependency property on your control or window, set the resource from codebehind, etc.
The key point is that you shouldn't be laying out the ellipses in C# unless you absolutely have to. Provide the data as some sort of a list of meaningful objects. Then create a data template that defines how that data is represented. Assuming you don't have to do any sort of complicated processing of your object to get the relevant ellipse properties you should be able to do this without any code, or at most with a few value converters.
This is the sort of UI separation that allows you to deal with updating the datasource (business logic) and displaying items (ui) separately.
So basically the idea is:
Expose a collection of objects - in my example this would be a collection of classes mirroring the structure of the shape xml element in the list. This can be the business object itself, or a viewmodel - a class that wraps a business objects and exposes conveniently bindable properties (in this case, position and size). The collection itself would prefereably be an ObservableCollection, so that UI is notified when you add or remove objects. Toss in some design time data into it if possible.
Bind to the collection, using the WPF datatemplates to define how an element should be presented. In this case I used a plain ItemsControl with a few simple templates, but this can be as complex as required
Work out how the collection will be updated from the original datasource. If you set up the previous steps correctly this is essentially a separate problem

You can use a translate transform to position the ellipses as you create them.
TranslateTransform transform = new TranslateTransform();
transform.X = X;
transform.Y = Y;
Ellipse ellipse = new Ellipse();
ellipse.RenderTransform = transform;
...
You could store the ellipses in a dictionary with the id as they key for quick and easy retrieval.
TranslateTransform transform = data[id].RenderTransform as TranslateTransform;
transform.X = newX;
transform.Y = newY;

You can accomplish this within a DataTemplate if your Ellipse objects are represented by a class, and perhaps displayed in an ItemsControl.
<Ellipse>
<Ellipse.LayoutTransform>
<TranslateTransform X="{Binding XCoord}"
Y="{Binding YCoord}" />
</Ellipse.LayoutTransform>
</Ellipse>
You would choose between LayoutTransform and RenderTransform based on the panel which held your Ellipse objects.
I also recommend reviewing an article by Bea Stollnitz (neé Costa) which shows how to leverage a ListBox backed by a Canvas with DataBinding to produce offset objects. Very cool.

Related

WPF: Get DataTemplate element from data context object

I have a fairly complex tree of data which I'm displaying in a WPF UserControl and the control is using DataTemplates to create and link various UI elements up to the various data inside the tree. Here's a (very) simplistic example which involves a list of items being display in an ItemsControl sitting on a canvas, and each element is represented with a TextBox:
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox
Text="{Binding ...}"
Canvas.Left="{Binding ...}"
Canvas.Top="{Binding ...}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
Now an event has occurred inside my UserControl whereby I have a reference to one of the list items and I want to set the focus to the TextBox item behind it. Is there an easy way to get the TextBox element from the data object that its Data Context is bound to? Or do I have to manually walk the entire visual tree myself?
I realize I could put a member inside the data element itself and use triggers to do whatever it is I'm trying to do but in my case it would require an extra layer of abstraction that I'd really like to avoid if at all possible.
As you say, if you cannot directly add a member to the item class that you are using, I assume its a built-in type you don't have access to, you must create a proper View Model to access a trigger like
<Trigger Property="IsFocused" Value="True">
<Setter TargetName="myTextBox" Property="FocusManager.FocusedElement" Value="{Binding IsTextBoxFocused}" />
</Trigger>
It's another layer of abstraction but you will always find it important to use a View Model that you have access to.

ItemsControl with only custom subelements

I would like to build a custom component that layouts its childs in either a StackPanel or a Grid (with variable row count, which makes me consider the StackPanel instead). The items are custom elements/objects that just hold some configuration, based on which a few controls are created to display them (some labels and text boxes).
Ideally, the component should be used somehow like this (where SpecializedCustomPanelItem is a subtype of CustomPanelItem):
<CustomPanel>
<CustomPanelItem Param1="value A" Param2="value B">Text</CustomPanelItem>
<CustomPanelItem Param1="value C">Other text</CustomPanelItem>
<SpecializedCustomPanelItem>More text</SpecializedCustomPanelItem>
<!-- The number of items is variable -->
</CustomPanel>
I’ve read on the ItemsControl for a while now, and it fits my needs rather well. I would create simply types for the items, and make data templates for them available from inside the ItemsControl. Then they should already render fine.
However I would like to require the items inside that ItemsControl to be of a specific type (i.e. CustomPanelItem or a subtype). I actually thought that the ItemsControl would allow this, just like you within a ComboBox or a MenuItem, but it turns out that it actually allows any subtype, and if necessary wraps them in a item container.
So I have been thinking if an ItemsControl is actually what I am looking for, as I do not want any “fancy” things like selection or scrolling which most of those controls implement. I actually only want to build a simple interface to a common pattern in the application that auto generates those components and layouts them in a Grid/StackPanel.
Should I still be using the ItemsControl or rather build some more custom component?
In this case you don't really need a custom component. Changing the ItemsPanel type to whatever type you need + multiple templates for the Items should do the trick.
However to answer the question in the heading: If you want to force an items control to only accept a certain type of items, you will have to create
a. A CustomItemsControl
b. A CustomItemsControlItem
Then for the CustomItemsControl you should declare the attribute
[StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(CustomItemsControlItem))]
Then you also will need to
protected override DependencyObject GetContainerForItemOverride()
{
return new CustomItemsControlItem();
// You can throw an exception here
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is CustomItemsControlItem;
}
If memory serves this should force the ItemsControl to not allow other types to be added as children and should throw exceptions. You could then do some magic inside CustomItemsControlItem by defining some DependencyProperties which you can then set when adding the items in XAML.
But yet if you have multiple types in your ViewModel that you want to display correctly, the correct way is still to provide multiple templates for the CustomItemsControlItem targetting your ViewModel types.
Hope this helps.
This sounds perfect for an ItemsControl
You can set it's ItemsPanelTemplate to define the kind of panel which will hold your items, and set the ItemContainerTemplate to define how to draw each item.
If items should be drawn differently based on what type they are, I'd suggest using implicit DataTemplates instead of setting the ItemContainerTemplate
<Window.Resources>
<DataTemplate DataType="{x:Type my:BasePanelItem}">
<my:CustomPanelItem Param1="{Binding Param1}" Param2="{Binding Param2}" Content="{Binding SomeValue}" />
</DataTemplate>
<DataTemplate DataType="{x:Type my:SpecializedPanelItem}">
<my:SpecializedCustomPanelItem Content="{Binding SomeValue}" />
</DataTemplate>
</Window.Resources>
<ItemsControl ItemsSource="{Binding MyItems}">
<!-- ItemsPanelTemplate -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<my:CustomPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
You mentioned that you wanted to perhaps use a dynamically created Grid instead of a StackPanel as well. If you do, you might be interested in some GridHelpers I have posted on my blog. This would allow you to bind the number of Columns/Rows on the Grid in the ItemsPanelTemplate
<ItemsControl ItemsSource="{Binding MyCollection}">
<!-- ItemsPanelTemplate -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid local:GridHelpers.RowCount="{Binding RowCount}"
local:GridHelpers.ColumnCount="{Binding ColumnCount}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- ItemContainerStyle -->
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Column" Value="{Binding ColumnIndex}" />
<Setter Property="Grid.Row" Value="{Binding RowIndex}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>

WPF "partial forms"

I'm making an application for DB migrations. I made a multithreaded framework with WPF GUI. I put someting like this in my namespace/folder:
class Something : Migrator {
public override Run(){
//I would need this
string valueOfMyCustomFieldOnForm = xyz.Text; //example
int count = 500;
for(int i = 0; i < 500; i++){
//do something here
OnProgressChanged(...); //call event, GUI is updated
}
OnCompleted(...); //migration completed
}
}
Then using reflection I put all classes in that namespace onto dropdown list. When I choose one in a list and click Start, the Thread with code in Run method is started.
DB Host: TEXTBOX
DB Username: TEXTBOX
DB Password: TEXTBOX
--
Migrator custom field 1: TEXTBOX
Migrator custom field 2: TEXTBOX
...
--
List with migrated items - irrelevant
There are few commong field on GUI (like database host, username etc...). But for some of those migrators I would need custom fields on GUI (for example 3 extra textbox fields).
What is the best way to do this in WPF? I need part of the GUI to be dynamic.
There's a lot of seemingly-irrelevant information in your question, which - I think - is really about mechanisms for creating metadata-driven UIs in WPF. Here's a way to approach that problem:
Suppose that you want to build a property-sheet-like UI: a grid that displays a row for each property, with a prompt and an input control of some kind. To do this, you're going to need a collection of objects, with each item in the collection including properties that describe the property and its value. A simple design would be a class that exposes a Prompt property and a Value property and that implements change notification.
Once you have created and populated this collection, you can implement an ItemsControl that displays it in a grid:
<ItemsControl ItemsSource="{Binding Properties}" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="PropertyViewModel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="Prompt"/>
<ColumnDefinition SharedSizeGroup="Value"/>
</Grid.ColumnDefinition>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
</Grid>
<Label Content="{Binding Prompt}"/>
<TextBox Grid.Column="1" Text="{Binding Value, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This is pretty simple - the most complicated thing about it is using Grid.IsSharedSizeScope so that all of the grids that this control creates use the same column widths. You could also use a ListView instead of an ItemsControl, though using a ListView for this introduces a bunch of issues surrounding focus and selection that you may not want to deal with.
Note that because of the magic that is WPF template matching, you could conceivably implement the Value property as an object, and create different templates to handle the different possible types of the Value property - just like a real property sheet does. To do this, you'd create a template for each type, e.g.:
<DataTemplate DataType="{x:Type System:String}">
<TextBox Text="{Binding Value, Mode=TwoWay}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type System:DateTime}">
<DatePicker Value="{Binding Value, Mode=TwoWay}"/>
</DataTemplate>
etc. Then you'd change the template for the PropertyViewModel so that instead of showing the Value in a TextBox, it uses a ContentPresenter, e.g.:
<ContentPresenter Grid.Column="1" Content="{Binding}"/>

Binding Grid Height Using FindAncestor and AncestorLevel To Another Grid

My question is about properly binding the property of one element to another.
Here is the structure of my code:
Data Template contains a grid (let's called the grid GridA), and in the grid, I specify an instance of a control, called ControlA.
ControlA is a UserControl that contains a Grid. There is custom logic in ControlA (code-behind) that dynamically builds the content - but in a nutshell, it uses another data template.
The data template for ControlA consists of another Grid. I want to bind the Height property for this grid in this data template to the Height property of the Grid in the data template referenced in my first bullet point above (the grid called GridA).
Here is the XAML that I have used for the binding, but, essentially, it isnt working, as its not finding the Grid:
<Grid Height="{Binding Path=ActualHeight,
RelativeSource={RelativeSource FindAncestor,
AncestorType=Grid,
AncestorLevel=2},
UpdateSourceTrigger=PropertyChanged}">
</Grid>
I understand that by specifying AncestorLevel=2, that it will use the second "found" occurrence of the type that you are looking for, in this case, the type is Grid. So in my mind, it will first find the Grid in ControlA, then it will continue to walk up the tree and find the Grid in the first data template, which is the Grid named GridA. This should be the second occurrence, correct?
Since you're beginning your search from the second grid, you actually want ancestor level = 1 (which is the default). Observe:
<Grid x:Name="first">
<Grid x:Name="second">
<Grid x:Name="third" Tag="{Binding Name, RelativeSource={RelativeSource FindAncestor, AncestorType=Grid, AncestorLevel=2}}">
<!-- displays "first", not "second" -->
<TextBlock Text="{Binding Tag, ElementName=third}"/>
</Grid>
</Grid>
</Grid>
BTW, your design sounds like it fights WPF's layout system rather than embracing it. As such, you're probably creating a lot of unnecessary pain for yourself.
Am I missing something, or could you just use the SharedSizeGroup property of the RowDefinitions and set Grid.IsSharedSizeGroup="True" on the outermost control?

In a WPF ListBox with more than 1000 Image Items the Zoom Images become slow

I met a problem when deveoping a photo viewer application.
I use ListBox to Show Images, which is contained in a ObservableCollection.
I bind the ListBox's ItemsSource to the ObservableCollection.
<DataTemplate DataType="{x:Type modeldata:ImageInfo}">
<Image
Margin="6"
Source="{Binding Thumbnail}"
Width="{Binding ZoomBarWidth.Width, Source={StaticResource zoombarmanager}}"
Height="{Binding ZoomBarWidth.Width, Source={StaticResource zoombarmanager}}"/>
</DataTemplate>
<Grid DataContext="{StaticResource imageinfolder}">
<ScrollViewer
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<ListBox Name="PhotosListBox"
IsSynchronizedWithCurrentItem="True"
Style="{StaticResource PhotoListBoxStyle}"
Margin="5"
SelectionMode="Extended"
ItemsSource="{Binding}"
/>
</ScrollViewer>
I also bind the Image'height in ListBox with a slider.(the slider's Value also bind to zoombarmanager.ZoomBarWidth.Width).
But I found if the collection become larger, such as: contains more then 1000 images, If I use the slider to change the size of iamges, it become a bit slow.
My Question is.
1. Why it become Slow? become it tries to zoom every images,or it just because notify("Width") is invoked more than 1000 times.
2. Is there any method to solve this kind of problem and make it faster.
The PhotoListBoxStyle is like this:
<Style~~ TargetType="{x:Type ListBox}" x:Key="PhotoListBoxStyle">
<Setter Property="Foreground" Value="White" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}" >
<WrapPanel
Margin="5"
IsItemsHost="True"
Orientation="Horizontal"
VerticalAlignment="Top"
HorizontalAlignment="Stretch" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style~~>
But If I use the Style above, I have to use ScrollViewer outside ListBox, otherwise I have no idea how to get a smooth scrolling scrollerbar and the wrappanel seems have no default scrollerbar. Anyone help? It is said listbox with scrollviewer has poor performance.
The problem is that your new Layout Panel is the WrapPanel and it doesn't support Virtualization! It is possible to create your own Virtualized WrapPanel... Read more here
Also read more about other issues like the implementation IScrollInfo here
I also highly recommend that your do not create a new control template just to replace the layout panel... Rather do the following:
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
The advantage of doing this is that you do not need to wrap your listbox in a scrollviewer!
[UPDATE] Also read this article by Josh Smith! To make the WrapPanel wrap... you also have to remember to disable horizontal scrolling...
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
I am not familiar with this component, but in general there is going to be limitations on the number of items a listbox can display at one time.
A method to solve this kind of problem is to keep the number of images loaded in the control within the number the control can display at acceptable performance levels. Two techniques to do this are paging or dynamic loading.
In paging, you add controls to switch between discrete blocks of pictures, for example, 100 at a time, with forward and back arrows, similar to navigating database records.
With dynamic loading, you implement paging behind the scenes in such a way that when the user scrolls to the end, the application automatically loads in the next batch of pictures, and potentially even removes a batch of old ones to keep the responsiveness reasonable. There may be a small pause as this occurs and there may be some work involved to keep the control at the proper scroll point, but this may be an acceptable trade-off.
I would recommend you not bind the Width/Height property of each individual image, but rather you bind a LayoutTransform on the ListBox's ItemsPanel. Something like:
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel>
<StackPanel.LayoutTransform>
<ScaleTransform
ScaleX="{Binding Path=Value, ElementName=ZoomSlider}"
ScaleY="{Binding Path=Value, ElementName=ZoomSlider}" />
</StackPanel.LayoutTransform>
</StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
Part of the problem is that it is loading the full image in each. You have to use an IValueConverter to open each image in a thumbnail size by setting either the DecodePixelWidth or DecodePixelHeight properties on the BitmapImage. Here's an example I use in one of my projects...
class PathToThumbnailConverter : IValueConverter {
public int DecodeWidth {
get;
set;
}
public PathToThumbnailConverter() {
DecodeWidth = 200;
}
public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) {
var path = value as string;
if ( !string.IsNullOrEmpty( path ) ) {
FileInfo info = new FileInfo( path );
if ( info.Exists && info.Length > 0 ) {
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.DecodePixelWidth = DecodeWidth;
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.UriSource = new Uri( info.FullName );
bi.EndInit();
return bi;
}
}
return null;
}
public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) {
throw new NotImplementedException();
}
}
try to virtualize your stackpael with the VirtualizingStackPanel.IsVirtualizing="True" attached property. this should increase performance.
using a listbox with many items in a scrollviewer is another known performance issue within wpf. if you can, try to get rid of the scrollviewer.
if your itemtemplates are kinda complex you should consider using the Recycling VirtualizationMode. this tells your listbox to reuse existing objects and not create new ones all the time.
What does your PhotoListBoxStyle style look like? If it's changing the ListBox's ItemsPanelTemplate then there's a good chance your ListBox isn't using a VirtualizingStackPanel as its underlying list panel. Non-virtualized ListBoxes are a lot slower with many items.

Resources