Background
I've got a collection of objects which I want to draw on a canvas. Each of these object has a DateTime property, which determines the position of that object along the x-axis on the canvas. Each object also has some other properties which determine the image that need to be drawn on the canvas. The most important feature that I want to implement is that as time passes by the second, these images representing the objects would move along the x-axis. In other words, the right vertical boundary of the canvas would always represent the current time (e.g. DateTime.Now), and objects in the collection would need to update their position on the canvas relative to that boundary. I am very new to Silverlight and hence I have quite a few questions including the following. In addition, I also have the requirement to follow the MVVM framework.
Questions
What should I use in the XAML to achive the above? I thought about using ItemsControl with Canvas as the Panel, but I am not sure how to do it or even whether it is the best way. Any actual XAML code would be great.
How should I bind the collection of objects to the canvas? And if so, how do I move them along the x-axis as time passes? That is, I would like the canvas to update whenever:
there are objects added to the
collection; or
objects removed from the collection;
or
existing object changing (e.g. some
property changed and hence need to
change the image that get shown on
the canvas) in the collection; or
even if there are no changes to the
collection as mentioned above, these
objects will need to move every
second.
Sorry if I have use the wrong terms for anything as I am still new to Silverlight.
Thanks.
I know this question is a little old, but you can just use a render transform - I'm doing something similar;
<ItemsControl ItemsSource="{Binding Notes}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Width="{Binding W}" Height="{Binding H}" BorderBrush="Navy" BorderThickness="5" CornerRadius="10">
<TextBlock Text="{Binding Text}"/>
<Border.RenderTransform>
<TransformGroup>
<... elided ...>
<TranslateTransform X="{Binding X}" Y="{Binding Y}"/>
</TransformGroup>
</Border.RenderTransform>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If you really want to use MVVM and data-binding, then an ItemsControl with the ItemsPanel defined as a Canvas might just work. Bind the ItemsControl.ItemsSource to an ObservableCollection in your VM. In your ItemTemplate for the ItemsControl, bind the UI item element's Canvas.X and Canvas.Y to your data items, using an IValueConverter in between to do the mapping of DateTime to X coord, etc...
Something like this:
<ItemsControl ItemsSource="{Binding Path=MyItemsInVM, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas></Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Width="50" Height="50" Canvas.Left="{Binding Path=MyDateTimeProperty, Converter={StaticResource DateTimeToLeftOffsetConverter}}" Canvas.Top="100" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Another approach is using the Model-View-Presenter pattern. MVVM is not the only show in town. When you have the need to do a lot of UI manipulation, or work with the VSM, then a Presenter can be a better fit (although Behaviors can also play an important role).
You can set up a timer in your presenter that operates at your refresh interval, and in the Presenter just handle the timer event to iterate over the collection and map objects to (X,Y) positions, updating the UI elements directly.
Related
I have a small problem designing problem:
My card tool should display 0-10 images on my view.
Sometime just three, sometimes six, sometimes all ten...
Due to MVVM I don't want to just Controls.Add() the images to the grids/controls content.
What is a good approach to this? One way would be having ten bitmap variables, but is there another better way?
Edit:\
I tried it like that:
<ItemsControl ItemsSource="{Binding CardImages}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"></WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Stretch="Uniform" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But its not working as it should.
I have SIX pictures, but the rest of the pictures arent visibilty (out of sigth). I want to show all of the pictures, so the 3 have to be about 50 % smaller in height and the other three atm not visible pictures should be shown under the shown three pictures. Hope thats clear.
How to solve that? I tried ScrollViewers, ViewBoxes and some different kind of templates / panels, but had no sucess. :(
I would bind an ItemsControl to a public observable collection property that contains your image sources / locations / however you reference them on your view model and then template the items to display images.
<ItemsControl Binding="{Binding MyPublicProperty}">
<ItemsControl.ItemTemplate>
<Image Source="{Binding Source={StaticResource MyImage}, Path=Source}"/>
</ItemsControl.ItemTemplate>
</ItemsControl>
See this SO question for binding the image.
I would like to create a multi-column list of checkboxes, but here's the catch - as I resize the window I would like everything to scale accordingly, including text size. I've been trying to make use of a WrapPanel and ViewBox but can't get the XAML right. Are these controls the best option or should I be using a ListBox (note I don't need selection functionality or scrollbars)? Any suggestions or examples on how I could achieve this would be much appreciated. I'm using MVVM and the list will be data bound, if that makes a difference.
BTW since starting WPF I've been struggling to understand which controls size to their children and which size to their parent. Are there any good sites, cheat sheets, or whatever summarising the behaviour of each control?
If you have a variable number of child elements, you could put a UniformGrid into a ViewBox.
If the child elements have to be visualized by a DataTemplate, you would have to use an ItemsControl with the ItemsPanel property set to such a UniformGrid:
<Viewbox Stretch="Uniform">
<ItemsControl ItemsSource="{Binding Items}" Width="400" Height="200">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="4"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="AliceBlue">
<CheckBox Content="{Binding Label}" IsChecked="{Binding IsChecked}"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Viewbox>
I am a bit baffled here. I have a List<BitmapImage> in a Viewmodel that is populating. I am trying to display the list on the view with an ItemsControl, but the Images don't show up. Oddly, I can access the same collection and get an image to display if I am using an Image tag.
<ItemsControl ItemsSource="{Binding Path=Images}" MinHeight="80">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" MinWidth="80" MinHeight="80" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Image HorizontalAlignment="Left" Name="image1" Stretch="Uniform" VerticalAlignment="Top" Source="{Binding Path=Images[0]}" MinHeight="80" MaxHeight="200" />
Note that they both point to Images. The Image shows up, the ItemsControl stays empty. What is going on?
You'll solve this by using an ObservableCollection as opposed to a List. The ItemsControl isn't going to rebind on the change notification of a List. You won't have to worry about explicitly calling the change notification for the List either if you use an ObservableCollection.
I was able to replicate your scenario and simply using an ObservableCollection solves the issue. As to why: http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx
Specifically, see this quote in the Remarks section:
For change notification to occur in a binding between a bound client and a data source, your bound type should either: Implement the INotifyPropertyChanged interface (preferred). Provide a change event for each property of the bound type. Do not do both.
I have created a listbox with a tiled data template. What I am trying to figure out now is how to properly apply a scale effect to each listbox item when a mouse over or selected event occurs and have it render properly within the wrap panel. I currently have the animations added to the visual states of the ListBoxItemTemplate.
A couple of thoughts:
When the animation is called the tiles within the wrap panel do not reposition to allow for the scaled item to be viewed properly. I would like to have the items within the wrap panel re-position to allow for the item scaled to be visible.
Also I notice that the items when scaled are going beyond the boundary of the wrap panel is there a way to also keep the item when scaled constrained to the viewable area of the wrap panel?
Code used in in search view
<Grid x:Name="LayoutRoot">
<ListBox x:Name="ResultListBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{x:Null}"
BorderThickness="0"
HorizontalContentAlignment="Stretch"
ItemContainerStyle="{StaticResource TileListBoxItemStyle}"
ItemsPanel="{StaticResource ResultsItemsControlPanelTemplate}"
ItemsSource="{Binding SearchResults[0].Results}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<formatter:TypeTemplateSelector Content="{Binding}" HorizontalContentAlignment="Stretch" Margin="2.5">
<!-- Person Template -->
<formatter:TypeTemplateSelector.PersonTemplate>
<DataTemplate>
<qr:ucTilePerson />
</DataTemplate>
</formatter:TypeTemplateSelector.PersonTemplate>
<!-- Incident Template -->
<formatter:TypeTemplateSelector.IncidentTemplate>
<DataTemplate>
<qr:ucTileIncident />
</DataTemplate>
</formatter:TypeTemplateSelector.IncidentTemplate>
</formatter:TypeTemplateSelector>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
ResultsItemsControlPanelTemplate is defined in app.xaml as
<ItemsPanelTemplate x:Key="ResultsItemsControlPanelTemplate">
<toolkit:WrapPanel x:Name="wrapTile"/>
</ItemsPanelTemplate>
I would greatly appreciate any suggestions on where to look
Thanks in advance
Image of current result
Render transformers are applied after a layout has occured, its purely graphical task that the Silverlight layout engine knows very little about. What you need is control that when scaled actually increases the size it desires and causes Silverlight to update its layout.
The control you need is the LayoutTransformer control from the Silverlight Toolkit.
Place the content of each of your tiles inside a LayoutTransformer and assign a ScaleTransform to its LayoutTransform property. Now you can get your animations to manipulate the transform and as the tile grows the other tiles will flow.
Working through this I have my user controls stored within within a Listbox datatemplate which is also a userControl referenced within the project. I think I have a partial solution started so now I need to just continue to tweak what is happening. Since I am using a datatemplate I had to set a binding reference on the user control to the Layouttrasnformation
<ListBox x:Name="ResultListBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{x:Null}"
BorderThickness="0"
HorizontalContentAlignment="Stretch"
ItemsPanel="{StaticResource ResultsItemsControlPanelTemplate}"
ItemContainerStyle="{StaticResource TileListBoxItemStyle}"
ItemsSource="{Binding SearchResults[0].Results}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<formatter:TypeTemplateSelector Content="{Binding}" HorizontalContentAlignment="Stretch" Margin="2.5">
<!-- Person Template -->
<formatter:TypeTemplateSelector.PersonTemplate>
<DataTemplate >
<layoutToolkit:LayoutTransformer x:Name="PersonTransformer">
<layoutToolkit:LayoutTransformer.LayoutTransform>
<ScaleTransform x:Name="personScale"/>
</layoutToolkit:LayoutTransformer.LayoutTransform>
<qr:ucTilePerson MouseEnter="ucTilePerson_MouseEnter" Tag="{Binding ElementName=PersonTransformer}" />
</layoutToolkit:LayoutTransformer>
</DataTemplate>
</formatter:TypeTemplateSelector.PersonTemplate>
//rest edited for brevity
Then within the code behind of the usercontrol which holds the listbox I used the following:
private void ucTilePerson_MouseEnter(object sender, MouseEventArgs e)
{
var ps = ((UserControl)sender).Tag as LayoutTransformer;
if (ps != null)
{
var transform = ps.LayoutTransform as ScaleTransform;
transform.ScaleX = (transform.ScaleX + 1.2);
transform.ScaleY = (transform.ScaleY + 1.2);
Dispatcher.BeginInvoke(() => { ps.ApplyLayoutTransform(); });
}
I still have to tweak this some as it does not seem to be as fluid as I like ( and I have to also setup mouseLeave events).
Not sure if this is the most appropriate approach and I would appreciate any feedback on alternatives.
Thanks again to Anthony for pointing me in the right direction
I am having issues with something that seems like it should be very simple but in fact has proven quite difficult.
Lets say you have a TabControl bound to an itemsource of ViewModels and the items displayed using a DataTemplate. Now lets say the DataTemplate consists of a Grid with two columns and a Grid splitter to resize the columns.
The problem is if you resize the columns on one tab, and switch to another tab, the columns are also resized. This is because the TabControl shares the DataTemplate among all tabs. This lack of UI persistence is applied to all elements of the template which can make for a frustrating experience when various UI components are adjusted. Another example is the scroll position in a DataGrid (on a tab). A DataGrid with few items will be scrolled out of view (only one row visible) if a DataGrid with more rows was scrolled to the bottom on another tab. On top of this, if the TabControl has various items defined in multiple DataTemplates the view is reset when you switch between items of differenet types. I can understand that this approach saves resources but the resultant functionality seems quite contradictory to expected UI behavior.
And so i'm wondering if there is a solution/workaround to this as i'm sure it's something that others have encountered before. I've noticed a few similar questions on other forums but there was no real solution. One about using the AdornerDecorator but that doesn't seem to work when used with a DataTemplate. I'm not keen on binding all the UI properties (like column width, scroll position) to my ViewModels and in fact I tried it for the simple GridSplitter example and I didn't manage to make it work. The width of the ColumnDefinitions were not necessarily affected by a grid splitter. Regardless, it would be nice if there were a general solution to this. Any thoughts?
If I ditch the TabControl and use an ItemsControl will I encounter a similar issue? Would it be possible to modify the TabControl Style so it doesn't share the ContentPresenter between tabs?
I've been messing with this on and off for a quite a while now. Finally, instead of trying to fix/modify the TabControl I simply recreated it's functionality. It's actually worked out really well. I made a Tab'like'Control out of a Listbox (Tab headers) and an ItemsControl. The key thing was to set the ItemsPanelTemplate of the ItemsControl to a Grid. A bit of Styling, and a DataTrigger to manage the Visibility of the Items and voila. It works perfect, each "Tab" is a unique object and preserves all it's UI states like scroll position, selections, column widths, etc. Any downsides or problems that might occur with this type of solution?
<DockPanel>
<ListBox
DockPanel.Dock="Top"
ItemsSource="{Binding Tabs}"
SelectedItem="{Binding SelectedTab}"
ItemContainerStyle="{StaticResource ImitateTabControlStyle}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel
Orientation="Horizontal">
</StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel
Margin="2,2,2,0"
Orientation="Horizontal" >
<TextBlock
Margin="4,0" FontWeight="Bold"
Padding="2"
VerticalAlignment="Center" HorizontalAlignment="Left"
Text="{Binding Name}" >
</TextBlock>
<Button
Margin="4,0"
Command="{Binding CloseCommand}">
<Image Source="/TERM;component/Images/Symbol-Delete.png" MaxHeight="20"/>
</Button>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ItemsControl
ItemsSource="{Binding Tabs}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl
Content="{Binding}">
<ContentControl.Style>
<Style>
<Style.Triggers>
<DataTrigger
Binding="{Binding IsSelected}" Value="False">
<Setter Property="ContentControl.Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>