Binding Geometry properties in an ItemsControl - silverlight

In a Silverlight game I'm working on I'm using an ItemsControl to display an ObservableCollection of game objects that we'll call Foo. Foo implements INotifyPropertyChanged and has a single property: Radius. The ItemsControl's ItemTemplate represents each Foo as an circular path, with the radius of the path bound to Foo.Radius.
The problem I'm running into is that whenever I try to add something to the ObservableCollection I get an InvalidOperationException with the message "Operation is not valid due to the current state of the object." If a remove the RadiusX and RadiusY bindings program runs fine, and it still works if I bind Foo.Radius to some property of Path. I'm at a loss for how to bind the geometry properties. Am I missing something?
XAML for reference:
<ItemsControl ItemsSource="{Binding}" x:Name="LayoutRoot">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Stroke="Black">
<Path.Data>
<EllipseGeometry RadiusX="{Binding Radius}" RadiusY="{Binding Radius}" />
</Path.Data>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Codebehind:
private ObservableCollection<Foo> things = new ObservableCollection<Foo>();
public MainPage()
{
InitializeComponent();
LayoutRoot.DataContext = things;
CompositionTarget.Rendering += Update;
}
void Update(object sender, EventArgs e)
{
things.Add(new Foo());
}

Try this ...
Dispatcher.BeginInvoke(() => things.Add(new Foo()));

I did a bit more searching and discovered that in Silverlight 3 it's only possible to bind properties to FrameworkElements, but Geometry inherits from DependencyObject. Upgrading the project to Silverlight 4 seemed to fix the problem.

Related

How to smoothly animate a bound property value from it's old value to it's new value in WPF

How do I smoothly animate from the previous value of a bound property to it's new value?
Let's say we have the following Canvas and Line.
<Canvas>
<Line
Canvas.Top="0"
Stroke="#887FFF00"
StrokeThickness="2"
X1="0" Y1="0"
X2="0" Y2="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Canvas}}"
Canvas.Left="{Binding Position}"
>
</Line>
</Canvas>
The horizontal position of the line is determined by a Position property bound to the Canvas.Left attached property. When the Position changes from say, 100 to 200, I would like to animate the position of the line from it's previous value, smoothly to it's new value.
How do I do this?
By far the simplest way to animate a property smoothly in wpf is to use an animation.
There are a few tricky bits to animations for your requirement though.
You can't bind a "from" or "to" on an animation so this will involve creating an animation in code.
You need a dependency property and hence a dependency object to animate a value.
You could consider making the viewmodel which exposes position into a dependency object. You can then create a new animation in code when you need to animate it and just retain your binding there.
Or... you could define a storyboard as a resource and alter that in code to do your animation.
Like this https://social.technet.microsoft.com/wiki/contents/articles/31191.wpf-tips-animating-a-viewmodel.aspx
Maybe you don't fancy making the viewmodel a dependency object.
If you're ok with a bit of "code behind" in your view then that's a dependency object. You could alternatively add a dependency property to your window. Bind that to Position and then animate the canvas.left of your line or another dependency property the line is bound to.
Instead of binding to directly the source property using the {Binding} syntax in XAML, you could subscribe to the PropertyChanged event of the view model yourself in the view and animate the property programmatically, e.g.:
private void OnViewLoaded(object sender, RoutedEventArgs e)
{
ViewModel viewModel = DataContext as ViewModel;
if (viewModel != null)
{
Canvas.SetLeft(line, viewModel.Position);
viewModel.PropertyChanged += OnPropertyChanged;
}
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Position")
{
double from = Canvas.GetLeft(line);
if (double.IsNaN(from))
from = 0;
ViewModel viewModel = sender as ViewModel;
if (viewModel != null)
{
DoubleAnimation doubleAnimation = new DoubleAnimation()
{
From = from,
To = viewModel.Position,
Duration = TimeSpan.FromSeconds(1)
};
line.BeginAnimation(Canvas.LeftProperty, doubleAnimation);
}
}
}
XAML:
<Canvas Width="100" Height="100" Background="Beige">
<Line x:Name="line"
Canvas.Top="0"
Stroke="#887FFF00"
StrokeThickness="2"
X1="0" Y1="0"
X2="0" Y2="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Canvas}}">
</Line>
</Canvas>
This is an example of a situation where you want to implement custom view-specific logic in the view, and you don't want to do this in XAML. This does not break the MVVM pattern in any way.

<Image> source not working on binding

I am trying to bind a listbox via ItemsTemplate to a collection of custom "Document" objects but am having an issue while trying to bind an image to the Document.ImageResourcePath property. Here is my markup
<ListBox Name="lbDocuments">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding Path=ImageResourcePath}"
Margin="5,0,5,0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This is my load event for the form that has the listbox.
private void Window_Loaded_1(object sender, RoutedEventArgs e)
{
List<Objects.Document> docs = Objects.Document.FetchDocuments();
lbDocuments.ItemsSource = docs;
}
My Document class holds a string to a resource image located in my resources folder depending on the document extension.
e.g. (this is part of a case statement within the document class)
case Cache.DocumentType.Pdf:
this.ImageResourcePath = "/JuvenileOrganizationEmail;component/Resources/pdf_icon.jpg";
break;
When the Window loads I get absolutely nothing in my listbox when it is bound to 23 perfectly well Document types. What could I be doing wrong?
Use an ObservableCollection instead of a List, and make the reference "class level" to your Window.
ObservableCollection<Objects.Document> _docs;
Make sure the DataContext is set in the Window's Ctor.
public Window()
{
_docs = new ObservableCollection<Objects.Document>(Objects.Document.FetchDocuments());
this.DataContext = this;
}
Then, you can just update your Window Loaded event:
private void Window_Loaded_1(object sender, RoutedEventArgs e)
{
lbDocuments.ItemsSource = _docs;
}
Or, an alternative solution, will be binding the ItemsSource of the ListBox directly to a public property of the collection. This is assuming the Ctor (above) is still used.
<ListBox Name="lbDocuments" ItemsSource={Binding Docs}>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding Path=ImageResourcePath}" Margin="5,0,5,0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In your Window.cpp file (though, a separate ViewModel class may be recommended if you are doing MVVM)
public ObservableCollection<Objects.Document> Docs
{
get { return _docs; }
}

Binding on dynamically-added elements

TPTB have decided that our app must run in a single window, popping up new windows in modal mode is not allowed.
And naturally, we have a UI design that involves popping up modal dialogs all over the place.
So I added a top-level Grid to the Window. In that Grid I defined no rows or columns, so everything draws in Row 0/Column 0.
The first element in the Grid was another Grid that contained everything that was normally displayed in the Window. The second was a full-sized Border with a gray, semi-transparent Background. The rest were Borders with wide Margins and white Backgrounds, containing the various UserControls that needed to be displayed as popups. All but the first had Visibility="Collapsed".
And then, when I needed to show a popup, I'd set Visibility="Visible" on the gray background and on the appropriate UserControl. The result was a nice shadowbox effect that worked fine.
Until somebody decided that the popups needed to be able to display popups. In a non-predictable order.
The limitation of the method I had implemented, using Visibility="Collapsed" elements in a Grid was that their order was fixed. UserControlB would always be displayed on top of UserControlA, even if it was UserControlB that asked to have UserControlA displayed. And that's not acceptable.
So my next attempt was to define the various UserControls in Window.Resources, and to add them to the Grid in code:
this.masterGrid.Children.Add(this.Resources["userControlA"] as UserControlA);
And that almost works. But the bindings are all messed up.
As an example, one of the controls is supposed to bind a Property to the CurrentItem of a collection in a member object of the Window's viewmodel. When I had the control defined as an invisible item in the Grid, it worked fine. But when I defined it as a Resource, the Property was null - it was never bound.
So I tried binding it in code, after I added it to the grid:
userControlA.SetBinding(UserControlA.myProperty, new Binding()
{ Source = this.viewModel.myCollection.CurrentItem });
And that compiles and runs just fine, but I'm not binding to the right object.
The first time I display the UserControl, I see the right object bound to it. But when I close it, and move the CurrentItem in the collection to a different object, and display the UserControl again, I still see the first object bound. If I close it again, and open it a third time, then I will see the right object bound to the control.
I've checked in code, and the CurrentItem that I'm binding to is right, every time, but it only seems to take every other time.
So I tried explicitly clearing the binding, first:
BindingOperations.ClearBinding(userControlA, UserControlA.myProperty);
userControlA.SetBinding(UserControlA.myProperty, new Binding()
{ Source = this.viewModel.myCollection.CurrentItem });
But that doesn't seem to have made any difference.
In all, it feels like I'm running down a rabbit hole, chasing deeper and deeper into complexity, to solve what should be a fairly simple problem.
Does anyone have any suggestions as to:
How to get binding to work on dynamically-added elements, or
How to get arbitrarily-ordered popups to display, as shadowboxes, without using dynamically-ordered elements?
Thanks in advance.
While it seems really odd for me that you can't create new Windows, I would definitely recommend not to complicate it too much by doing unnecesary things such as storing your views in the MainWindow's resources.
It would be better if you just added new instances of these elements into an ObservableCollection:
XAML:
<Window x:Class="WpfApplication4.Window8"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication4"
Title="Window8" Height="300" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModel1}">
<StackPanel Background="Green">
<TextBlock Text="This is ViewModel1!!"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel2}">
<StackPanel Background="Blue" HorizontalAlignment="Center">
<TextBlock Text="This is ViewModel2!!"/>
<TextBlock Text="{Binding Text2}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel3}">
<StackPanel Background="Red" VerticalAlignment="Center">
<TextBlock Text="This is ViewModel3!!"/>
<TextBlock Text="{Binding Text3}"/>
<TextBox Text="{Binding Text3}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<DockPanel>
<Button Width="100" Content="Add" Click="Add_Click" DockPanel.Dock="Top"/>
<Button Width="100" Content="Remove" Click="Remove_Click" DockPanel.Dock="Top"/>
<ListBox ItemsSource="{Binding ActiveWidgets}" SelectedItem="{Binding SelectedWidget}">
<ListBox.Template>
<ControlTemplate>
<ItemsPresenter/>
</ControlTemplate>
</ListBox.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<ContentPresenter ContentSource="Content"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</DockPanel>
</Window>
Code Behind:
using System.Linq;
using System.Windows;
using System.Collections.ObjectModel;
using System;
namespace WpfApplication4
{
public partial class Window8 : Window
{
private WidgetsViewModel Widgets { get; set; }
public Window8()
{
InitializeComponent();
DataContext = Widgets = new WidgetsViewModel();
}
private Random rnd = new Random();
private int lastrandom;
private void Add_Click(object sender, RoutedEventArgs e)
{
var random = rnd.Next(1, 4);
while (random == lastrandom)
{
random = rnd.Next(1, 4);
}
lastrandom = random;
switch (random)
{
case 1:
Widgets.ActiveWidgets.Add(new ViewModel1() {Text = "This is a Text"});
break;
case 2:
Widgets.ActiveWidgets.Add(new ViewModel2() { Text2 = "This is another Text" });
break;
case 3:
Widgets.ActiveWidgets.Add(new ViewModel3() { Text3 = "This is yet another Text" });
break;
}
Widgets.SelectedWidget = Widgets.ActiveWidgets.LastOrDefault();
}
private void Remove_Click(object sender, RoutedEventArgs e)
{
Widgets.ActiveWidgets.Remove(Widgets.SelectedWidget);
Widgets.SelectedWidget = Widgets.ActiveWidgets.LastOrDefault();
}
}
public class WidgetsViewModel: ViewModelBase
{
public ObservableCollection<ViewModelBase> ActiveWidgets { get; set; }
private ViewModelBase _selectedWidget;
public ViewModelBase SelectedWidget
{
get { return _selectedWidget; }
set
{
_selectedWidget = value;
NotifyPropertyChange(() => SelectedWidget);
}
}
public WidgetsViewModel()
{
ActiveWidgets = new ObservableCollection<ViewModelBase>();
}
}
public class ViewModel1: ViewModelBase
{
public string Text { get; set; }
}
public class ViewModel2: ViewModelBase
{
public string Text2 { get; set; }
}
public class ViewModel3: ViewModelBase
{
public string Text3 { get; set; }
}
}
Just copy and paste my code in a File - New - WPF Application and see the results for yourself.
Since the Grid always places the last UI Element added to it topmost, you will see that Adding items to the observablecollection makes these "different widgets" always appear on top of each other, with the topmost being the last one added.
The bottom line is, when WidgetA requests to open WidgetB, just create a new WidgetBViewModel and add it to the ActiveWidgets collection. Then, when WidgetB is no longer needed, just remove it.
Then, it's just a matter of putting your UserControls inside a proper DataTemplate for each ViewModel. I strongly suggest you keep a separate ViewModel for each of your Widgets, and if you need to share data between them, just share data between the ViewModels.
Don't attempt to do things like ListBox ItemsSource="{Binding Whatever, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}" unless you have a good reason to.
This way you no longer have to deal with Panel.ZIndex stuff. Maybe you can create a couple of attached properties to deal with things like focus and whatnot, but this approach is dead simple, and by far more performant than the Visibility and the Resources approaches.

Draw from data of the VM

I'm learning to create WPF applications and I got a homework.
I have to create a wpf mvvm "tron lightcycle game", but unfortunately got stuck.
In the View (Mainwindow.xaml) there is a Canvas. I should draw here.
...
<Canvas Name="cnvs_game" Margin="5,5,5,5">
...
In the ViewModel there is a GameData class and a Timer.
Every tick the GameData updates(GameTime,Player1CanvasPosition (Point),... ).
I bid the Gametime to the View like this:
Mainwindow.xaml.cs:
...
<TextBlock Text="{Binding GameTime}" />
...
ViewModel.cs:
...
private GameData _GameData;
...
public String GameTime { get { return _GameData.GameTime.ToString(); } }
...
private void GameTimer_Tick(object sender, EventArgs e)
{
_GameData.Step();
OnPropertyChanged("GameTime"); // PropertyChanged with error handling
OnPropertyChanged("Player1CanvasPosition ");
OnPropertyChanged("Player2CanvasPosition ");
}
The GameTime refresh in the View. It wasn't hard. But I still have no idea how to draw.
How should I get the Player1CanvasPosition and draw there a Rectangle (in the Canvas). What is the best way to do this? Help Me Please! :S
You can do this the same way you did With the GameTime, for example:
<Canvas Name="cnvs_game" Margin="5,5,5,5">
<Rectangle Canvas.Left="{Binding Player1CanvasPositionX}" Canvas.Top="{Binding Player1CanvasPositionY}" ... />
<Rectangle Canvas.Left="{Binding Player2CanvasPositionX}" Canvas.Top="{Binding Player2CanvasPositionY}" ... />
...
And create the Player1CanvasPositionX property in the ViewModel which call the OnPropertyChanged, then when changing the properties in the ViewModel the rectangles will move.
Edit:
For dynamically adding rectangles I would use an ItemsControl which is bound to an ObservableCollection of positions. The ItemsControl datatemplate would contain a rectangle which would bind to the position. Look at this link for more details WPF Canvas, how to add children dynamically with MVVM code behind.

WPF databinding with a user control

I have a wpf user control, which exposes a single custom dependency property. Inside the user control, a textblock binds to the value of the dp. This databinding works in all scenarios except when the data source is an object.
The minimal code necessary to reproduce this is:
this is the main part of the user control
<StackPanel Orientation="Horizontal">
<TextBlock Text="**SimpleUC** UCValue: "/>
<TextBlock Text="{Binding UCValue}"/>
</StackPanel>
and the user control code behind:
public SimpleUC()
{
InitializeComponent();
this.DataContext = this;
}
public string UCValue
{
get { return (string)GetValue(UCValueProperty); }
set { SetValue(UCValueProperty, value); }
}
public static readonly DependencyProperty UCValueProperty =
DependencyProperty.Register("UCValue", typeof(string), typeof(SimpleUC), new UIPropertyMetadata("value not set"));
this is the test window. I imported my project xml namespace as "custom"
<Window.Resources>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="Margin" Value="20"/>
</Style>
</Window.Resources>
<StackPanel>
<StackPanel>
<TextBlock Text="This fails to bind:"/>
<custom:SimpleUC UCValue="{Binding SomeData}"/>
</StackPanel>
<StackPanel>
<TextBlock>The same binding on a regular control like Label</TextBlock>
<Label Content="{Binding SomeData}"/>
</StackPanel>
<Slider x:Name="sld" />
<StackPanel>
<TextBlock>However, binding the UC to another element value, like a slider works</TextBlock>
<custom:SimpleUC UCValue="{Binding ElementName=sld,Path=Value}"/>
</StackPanel>
</StackPanel>
and the test window code behind is:
public TestWindow()
{
InitializeComponent();
this.DataContext = this;
}
//property to bind to
public string SomeData { get { return "Hello S.O."; } }
When I turn on the diagnostic tracing on the TestWindow, it spits out the error "BindingExpression path error:
'SomeData' property not found on 'object' ''SimpleUC' (Name='')' ... "
The binding expression is the same as the one I used in the neighboring label and it worked fine. This behavior seems really bizarre to me. Can anyone shed some light?
You set DataContext of your SimpleUC to itself here
public SimpleUC()
{
InitializeComponent();
this.DataContext = this; // wrong way!
}
so when you use binding here
<custom:SimpleUC UCValue="{Binding SomeData}"/>
it searches property SomeData in control's data context which is set to this object because code in SimpleUC constructor overrides value of DataContext and it is not set to TestWindow object anymore as you expected. That's why your solution works - it doesn't affect DataContext which is inherited from window. Also you can keep this.DataContext = this; but set element where to search property explicitly like this (skipped irrelevant)
<Window ... Name="wnd1">
<custom:SimpleUC UCValue="{Binding SomeData, ElementName=wnd1}"/>
...
But my oppinion is that your variant from the answer looks more convenient to me, setting data context to this is not very good practice.
Hope it helps.
If you must use a UserControl, your
<TextBlock
Text="{Binding RelativeSource={RelativeSource Self},
Path=Parent.Parent.UCValue}"
/>
is an ok way to do it and
<TextBlock
Text="{Binding UCValue,
RelativeSource={RelativeSource FindAncestor,custom:SimpleUC,1}}"
/>
is better because you don't rely on the control hierarchy and possible instantiation order issues.
However I would recommend for this kind of situation that you use "custom controls" instead of "user controls". They take a little bit of getting used to, but they are much more powerful because their XAML is the template itself which means you can use TemplateBinding and {RelativeSource TemplatedParent}.
In any case, DataContext = this; is definitely to be avoided.

Resources