EDIT2: When the chart gets populated i am unable to change the values anymore. Even when i change the values in the list (where the ItemControls get the values from) the chart does not seem to update with the new values.
I call upon the GetDataGrafiek() method in the timer to update my chart every x seconds.
Grafiek graf = new Grafiek();
graf.GetDataGrafiek();
Is this due the Threading Timer running in a separate thread(IsAsynchronous method in the ObjectDataProvider) or do i need to access the DataContext of the ItemsControl in my timer method?
EDIT: I was unable able to populate the chart when the program was already running so i made the ObservableCollection<GrafiekBar> static (list that holds the Fill and Value of the bars) and initialized is as following:
public static ObservableCollection<GrafiekBar> listGrafiek = new ObservableCollection<GrafiekBar>()
{
new GrafiekBar() {Value = 0, Fill = (Brush)convertor.ConvertFromString(kleuren[0])},
new GrafiekBar() {Value = 0, Fill = (Brush)convertor.ConvertFromString(kleuren[1])},
new GrafiekBar() {Value = 0, Fill = (Brush)convertor.ConvertFromString(kleuren[2])}
};
From MSDN: ObjectDataProvider: "However, if you are binding to an object that has already been created, you need to set the DataContext in code, as in the following example."
I have a ItemsControl that is being displayed as a simple bar chart.
When i assign values (hardcoded in my codeBehind) the chart gets populated succesfully.
What i do is basically getting the largest value set it to 100% and calculate the length of the rest of the bars trough that.
Problem:
I do not want the chart bar values hardcoded but the bars have to change runTime.
For this i use a Threading.Timer that runs every second as long my program is running (other calculations happen in this timer as well).
The chart bar values get updates based on the calculations happening within this timer every x seconds.
I have tried everything and i cannot get values displayed when my program is running. I can only see bars when i hardcode them (see region of GetDataGrafiek() at the end of the thread). What exactly am i doing wrong / missing?
The GetDataGrafiek() (calculations to populate my chart) gets called in a ObjectDataProvider.
This method takes TimeSpan as input and then performs calculations so i get a Double Value (based on the 100% value explained above) which then gets placed in the Value of the bar (= width of the dateTemplate).
<ObjectDataProvider x:Key="odpLbGrafiek" ObjectType="{x:Type myClasses:GrafiekBar}" MethodName="GetDataGrafiek"/>
DataTemplate for my ItemsControl (this uses the Width for the value of the bars of my chart)
<DataTemplate x:Key="GrafiekItemTemplate">
<Border Width="Auto" Height="Auto">
<Grid>
<Rectangle StrokeThickness="0" Height="30"
Margin="15"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Width="{Binding Value}"
Fill="{Binding Fill}">
<Rectangle.LayoutTransform>
<ScaleTransform ScaleX="20" />
</Rectangle.LayoutTransform>
</Rectangle>
</Grid>
</Border>
</DataTemplate>
ItemsControl:
<ItemsControl x:Name="icGrafiek"
Margin="50,3,0,0"
ItemsSource="{Binding Source={StaticResource odpLbGrafiek}}"
ItemTemplate="{DynamicResource GrafiekItemTemplate}"
RenderTransformOrigin="1,0.5" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.RowSpan="6">
<ItemsControl.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY="-1" ScaleX="1"/>
<SkewTransform AngleY="0" AngleX="0"/>
<RotateTransform Angle="180"/>
<TranslateTransform/>
</TransformGroup>
</ItemsControl.RenderTransform>
</ItemsControl>
GetDataGrafiek() The region holds hardcoded values, when this is done my chart displays 6 bars succesfully. When i comment the region i no longer get any visible bars.
This method returns a list with Double values. Each value represents a bar that gets represented as Width in the DataTemplate, and the Fill just gives it a certain color.
ObservableCollection<GrafiekBar> listGrafiek = new ObservableCollection<GrafiekBar>();
public ObservableCollection<GrafiekBar> GetDataGrafiek()
{
var converter = new System.Windows.Media.BrushConverter();
#region ***TEST HARDCODED BAR VALUES***
int[] testWaardenUren = new int[] { 2, 1, 0, 1, 2, 0 };
int[] testWaardenMinuten = new int[] { 58, 2, 55, 55, 2, 20 };
for (int j = 0; j < 6; j++)
{
TimeSpan ts = new TimeSpan(testWaardenUren[j], testWaardenMinuten[j], 0);
GlobalObservableCol.regStilstanden[j].Value = ts;
GlobalObservableCol.regStilstanden[j].Name = "";
}
#endregion
totalMinutesMaxValue = GetLargestValueStilstanden(); //= "100%" value
//calculate % of stilstanden Values
for (int i = 0; i < GlobalObservableCol.regStilstanden.Count; i++)
{
Double totalMin = GlobalObservableCol.regStilstanden[i].Value.TotalMinutes;
totalMin = totalMin / totalMinutesMaxValue * 10;
valuesChartPercentage.Add(totalMin);
}
//the barChart (itemsControl) gets its final values here
for (int j = 0; j < GlobalObservableCol.regStilstanden.Count; j++)
{
GrafiekBar bar = new GrafiekBar();
bar.Value = valuesChartPercentage[j];
bar.Fill = converter.ConvertFromString(kleuren[j]) as Brush;
listGrafiek.Add(bar);
}
return listGrafiek;
}
GrafiekBar.cls
public class GrafiekBar : INotifyPropertyChanged
{
private double value;
private Brush fill;
public GrafiekBar()
{
}
public double Value
{
get { return this.value; }
set
{
this.value = value;
NotifyPropertyChanged("Value");
}
}
public Brush Fill
{
get { return this.fill; }
set
{
this.fill = value;
NotifyPropertyChanged("Fill");
}
}
//interface INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
//interface INotifyPropertyChanged
private void NotifyPropertyChanged(String info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
The Threading timer that runs every second (that has the calculation logic and the getDataGrafiek() called there does a invoke to the GUI thread for updates.
private void MyTimerCallBack(object state)
{
DisplayWegingInfo();
App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
new Action(() =>
{
//actions that involve updating the UI
CaculateTimeBetweenWegingen();
}));
}
Best Regards Peter.
It's a shot in the dark, 'cause I did not read through all your code, but here's a thought: you are binding to a static resource right? It gets read once and this is it, eh? Try DynamicResource instead.
If you are accessing the following code from another thread, this will cause an issue:
private void NotifyPropertyChanged(String info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
As the other thread will raise an event which will cause the Dispatcher Thread (if it's subscribed to the event) to error.
Try instead
private void NotifyPropertyChanged(String info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (PropertyChanged != null)
{
if (System.Threading.Thread.CurrentThread == System.Windows.Application.Current.Dispatcher.Thread)
PropertyChanged(this, new PropertyChangedEventArgs(info));
else
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => PropertyChanged(this,new PropertyChangedEventArgs(info));
}
}
Also, make sure you are using UpdateSourceMethod=PropertyChanged in WPF.
Related
I am attempting to update the Rotation Angle of an image in a custom WPF UserControl named MarkerImage. I have a property on MarkerImage named Heading and when that changes, I want the Angle of the image to change. I've tried numerous methods, and they all set the initial angle correctly, but none of them are able to update the angle. Here is the XAML of the control:
Here are some of the methods I've tried:
1) Created a Dependency Property on MarkerImage named Heading.
public static readonly DependencyProperty HeadingProperty = DependencyProperty.Register("Heading", typeof(uint), typeof(MarkerImage), new PropertyMetadata(default(uint)));
Then I set the DataContext of MarkerImage to {RelativeSource Self} and Bound the Angle property of the RotateTransform to that Heading property:
<UserControl x:Class="Pilot2ATC_EFB.Map.MarkerImage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Pilot2ATC_EFB.Map"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
d:DesignHeight="450" d:DesignWidth="800" Width="35" Height="35" x:Name="ctlMarkerImage">
<Image x:Name="userImage" HorizontalAlignment="Center" Height="34" Width="34" RenderTransformOrigin="0.5,0.5" Source="/myApplication;component/Resources/arrow.png" Margin="0,0,0,0" VerticalAlignment="Center">
<Image.RenderTransform>
<RotateTransform x:Name="rotateTransform" Angle="{Binding Path=Heading, UpdateSourceTrigger=PropertyChanged}"/>
</Image.RenderTransform>
</Image>
This correctly sets the Angle when the Control is created and the Heading property is set. However, as the Heading property is updated in the Set{} of the Heading property:
SetValue(HeadingProperty, value % 360);
the image angle does not change.
2)Attempted to set the Angle property directly from the property Heading Set code:
_Heading = value % 360;
rotateTransform.Angle = _Heading;
This again worked to set the initial Angle, but did not change the rotation of the image when Heading was updated.
3) Attempted to replace the RotateTransform with a new one each time the Heading changed:
userImage.RenderTransform = new RotateTransform(_Heading);
This also worked to set the initial Angle, but did nothing when the Heading value changed thereafter.
4) Attempted to do an animation of the Angle property when the Heading changed. (rotateTransform is the x:Name of the RotateTransform element):
var rotAnimation = new DoubleAnimation(Heading, TimeSpan.FromMilliseconds(1));
rotateTransform.BeginAnimation(RotateTransform.AngleProperty, rotAnimation);
And once again, the initial value was set correctly, but updated had no effect.
Here's the code where the Heading property is set when using the DependencyProperty.
public double Heading
{
get { return (double)GetValue(HeadingProperty); }
set
{
if (value < 0)
value = 360;
SetValue(HeadingProperty, value % 360);
}
}
Of course, I confirmed that the code was executing and the heading value was changing after the initial setting, but the image was not being rotated.
I have tried 10+ other variations of these and other suggestions in the forums, but they either threw errors, didn't set the initial value or had the same result as these 4.
It would seem that there is a definitive way to change the rotation angle of an image in a WPF UserControl dynamically from code behind or via Binding, but I am at a loss as to what that would be. ANY help will be greatly appreciated.
Here's a small app that seems to work and demonstrates the changing of the Angle property of a RotateTransform. In fact, there are many ways to do this. This is one that uses a DependencyProperty Approach.
Here's the Application Window XAML without the Window standard content:
<Grid Margin="0,0,2,1">
<local:ImageMarker x:Name="imgMarker" HorizontalAlignment="Left" Height="32"
Margin="45,35,0,0" VerticalAlignment="Top" Width="32"/>
<TextBox x:Name="txtHeading" HorizontalAlignment="Left" Height="23" Margin="174,44,0,0"
TextWrapping="Wrap" Text="0" VerticalAlignment="Top" Width="120" TextChanged="txtHeading_TextChanged"/>
</Grid>
Here's the Application Window code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private Marker marker;
private void txtHeading_TextChanged(object sender, TextChangedEventArgs e)
{
if(marker == null)
marker = new Marker(imgMarker);
if (txtHeading.Text.Length > 0)
{
double val;
var result = double.TryParse(txtHeading.Text, out val);
if (result)
marker.Heading = val;
}
}
}
Here is the Marker Object that will control the ImageMarker:
class Marker : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private ImageMarker imageMarker;
public Marker(ImageMarker imgMarker)
{
imageMarker = imgMarker;
}
private double _Heading;
public double Heading
{
get { return _Heading; }
set
{
if (value <= 0)
value = 360;
_Heading = value % 360;
imageMarker.Heading = _Heading;
//imageMarker.RenderTransform = new RotateTransform(_Heading);
}
}
}
Notice the commented out line in the Heading Setter. That method of changing the angle of the image also worked...No Binding required.
Here's the ImageMarker XAML:
<UserControl x:Class="TestRotateTransform.ImageMarker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TestRotateTransform"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" Width="32" Height="32">
<Image Source="Resources/arrow_up.png" RenderTransformOrigin="0.5,0.5">
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="{Binding Heading, RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1,AncestorType=UserControl}}" />
<TranslateTransform/>
</TransformGroup>
</Image.RenderTransform>
</Image>
And finally, here's the ImageMarker code behind with the addition of propertychanged, coercion and validation callbacks as suggested by Keith:
public partial class ImageMarker : UserControl
{
public ImageMarker()
{
InitializeComponent();
}
public static readonly DependencyProperty HeadingProperty = DependencyProperty.Register("Heading",
typeof(double), typeof(ImageMarker), new FrameworkPropertyMetadata(new PropertyChangedCallback(OnHeadingChanged),
new CoerceValueCallback(CoerceHeading)),new ValidateValueCallback(IsValidHeading));
private static void OnHeadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(HeadingProperty);
}
private static object CoerceHeading(DependencyObject d, object value)
{
double hdg = (double)value;
if (hdg <= 0)
hdg = 360;
return hdg % 360;
}
private static bool IsValidHeading(object value)
{
return true;
}
public double Heading
{
get { return (double)GetValue(HeadingProperty); } // { return _Heading; } //
set
{
SetValue(HeadingProperty, value % 360);
}
}
}
Thanks to Keith and Clemens for their suggestions. At least I know it can be done. Apparently, in my real project, something else is causing it to fail.
[https://drive.google.com/file/d/0B1xZLc69ZnLkWWRuaE9ycmhIXzg/view?usp=sharing]
This is the entire project - vs2013.
1You can see how the FindVisualChildren() works to assign every letter to a button's content, I need to do this for every word on the file, but it only works one time.
I have a text file, with 10 words (one per line). Once the word is read, it's placed into a text box, and spread, letter by letter, in order to be assigned to a button content in joint with some random letters to fill all the buttons. This step is made by using the FindVisualChildren() method.
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj)
where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
and
//Add random letters to all buttons
foreach (Button tb in FindVisualChildren<Button>(panel))
{
tb.Content = resultsChar[array[iconta]];
tb.Click += Button_Click;
++iconta;
}
Once all the letters are assigned, the user must create the word using every letter in every button, when the word is organized as corresponds to the one in the text box, then another word is called from the text, and the letters must be reassigned to the buttons with other random letters.
Everything works great, but just the first time. After that, I can't call the FindVisualChildren() method n times. This thing just work for the first word, and if I put this inside a while loop, just shows me the last word. not step by step.
Without a good Minimal, Complete, and Verifiable example that reliably reproduces your problem, it's impossible to know what exactly that problem is. From your problem description, it sounds like you might be running some code in the constructor, which should instead be placed elsewhere so that it can safely run multiple times.
That said, I do agree with the basic motivation (though not the presentation at all) of the comment that explains that you should not be messing directly with the visual tree at all.
In many APIs, and WPF especially, the "right way" to do things is to create a custom class — your "view model — which is a type that maintains the state of your program in a completely separate and independent way from the visual aspect of your program. In your code example, this class would keep track of the word and letters to be displayed, and any other state for the program's underlying logic, as well as any methods for controlling or modifying that state.
As an example, I wrote a class that could serve as the view model in your program:
class Model : INotifyPropertyChanged
{
private static readonly Random _random = new Random();
private const int _kcolumnCount = 6;
private const int _krowCount = 4;
private string _word;
public ObservableCollection<char> Letters { get; private set; }
public string Word
{
get { return _word; }
set { _UpdateValue(ref _word, value); }
}
public int ColumnCount { get { return _kcolumnCount; } }
public int RowCount { get { return _krowCount; } }
public Model()
{
Letters = new ObservableCollection<char>();
for (int i = 0; i < ColumnCount * RowCount; i++)
{
Letters.Add(' ');
}
}
public void UpdateLetters()
{
char[] characters = new char[ColumnCount * RowCount];
for (int i = 0; i < characters.Length; i++)
{
if (Word != null && i < Word.Length)
{
characters[i] = Word[i];
}
else
{
characters[i] = (char)('a' + _random.Next(26));
}
}
for (int i = characters.Length - 1; i > 0 ; i--)
{
int j = _random.Next(i + 1);
if (i != j)
{
char chT = characters[i];
characters[i] = characters[j];
characters[j] = chT;
}
}
for (int i = 0; i < characters.Length; i++)
{
Letters[i] = characters[i];
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void _UpdateValue<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
if (!object.Equals(field, newValue))
{
field = newValue;
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
It has three key features:
It stores the current word.
It stores the current list of letters to be displayed.
It provides a method to cause the list of letters to be updated, based on the current word.
It also includes the values for the row and column counts of your grid.
You'll notice that this class is entirely comprehensible as the fundamental logic for your program, and yet has nothing in it that is directly related to the implementation of the UI.
Having written such a class, it is then very easy to create a simple XAML markup for the user interface:
<Window x:Class="TestSO36231872RandomLetters.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:TestSO36231872RandomLetters"
xmlns:s="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:Model/>
</Window.DataContext>
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="3">
<TextBox Width="80" Text="{Binding Word}"/>
<Button HorizontalAlignment="Left" Content="Update Word"
Margin="3,0" Click="Button_Click"/>
</StackPanel>
<ItemsControl ItemsSource="{Binding Letters}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid IsItemsHost="True" Rows="{Binding RowCount}" Columns="{Binding ColumnCount}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type s:Char}">
<Button Content="{Binding}" FontSize="16"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Window>
The only thing left is the Click event handler (there are other ways to deal with user input, but this is the simplest and suits this particular example just fine). This goes in the code-behind for the Window class (i.e. "MainWindow.xaml.cs"):
private void Button_Click(object sender, RoutedEventArgs e)
{
Model model = (Model)DataContext;
model.UpdateLetters();
}
This causes the UpdateLetters() method to be called with the "Update Word" button is clicked.
Of course, this is not your full program. I don't really fully understand what you mean by "the user must create the word using every letter in every button". If you expect the user to click the buttons in the grid, you'll need to hook up another handler in the template for the buttons, and of course deal with the context somehow. But that's a "whole 'nother ball o' wax". I hope the above suffices to get you pointed back in the correct direction for dealing with WPF.
I'm creating a Polyline in a WPF canvas that is suppose to update it's position, when hitting on a button, based on calculations that happends in my model. I'm using a MVVM pattern.
My XAML code:
<Grid>
<StackPanel>
<Button Margin="10" Command="{Binding Path=RunAnalysisCmd}">Rune analysis!</Button>
<Canvas>
<Polyline Points="{Binding ModelPathPoints, UpdateSourceTrigger=PropertyChanged}" Stroke="Blue" StrokeThickness="2"/>
</Canvas>
</StackPanel>
</Grid>
In my ViewModel I have a PointCollection property where the path points are stored.
private PointCollection _modelPathPoints = new PointCollection();
public PointCollection ModelPathPoints
{
get { return _modelPathPoints; }
set
{
_modelPathPoints = value;
NotifyPropertyChanged("ModelPathPoints");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
The method RunAnalysis works fin, and I have tested it using Consol output. My problems are that the canvas is not changing when the points in the PointCollection are changing.
public void RunAnalysis()
{
double angle = 0;
for (int i = 0; i < 1000; i=i+10)
{
Model.TranslateModelEdgeNodes(i, i);
Model.RotateModelEdgeNodes(angle);
angle = angle + 0.1;
AddModelPointsToPointCollection();
System.Threading.Thread.Sleep(500);
}
}
public void AddModelPointsToPointCollection()
{
//ModelPathPoints.Clear();
PointCollection modelPathPoints = new PointCollection();
for (int i = 0; i < Model.ModelEdgeNodes.Count(); i++)
{
modelPathPoints.Add(new Point( XXX, XXX )) // Not important what XXX, XXX is
}
modelPathPoints.Add(new Point(XXX, XXX )); // Not important what XXX, XXX is
ModelPathPoints = modelPathPoints;
}
Does anyone see the problem??
To avoid wondering whether Thread.Sleep is interfering with the UI doing it's thing, why don't you use a click-event, that will make it do one iteration that should be visibly apparent?
Within that property-setting, you could then verify that it is being assigned with each click, verify the values of the points being assigned, and that the PropertyChanged event is happening.
Oh - and I assume your view-model implements INotifyPropertyChanged, and that you're assigning an actual value to your PropertyChanged event somewhere that you're not showing in your code above?
Let me know if that helps - if not, I'll create a project and check this for you. Best.
This question is directly related to a question I recently posted, but I feel that the direction has changed enough to warrant a new one. I am trying to figure out the best way to move a large collection of images on a canvas in real-time. My XAML currently looks like this:
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:Entity}">
<Canvas>
<Image Canvas.Left="{Binding Location.X}"
Canvas.Top="{Binding Location.Y}"
Width="{Binding Width}"
Height="{Binding Height}"
Source="{Binding Image}" />
</Canvas>
</DataTemplate>
</UserControl.Resources>
<Canvas x:Name="content"
Width="2000"
Height="2000"
Background="LightGreen">
<ItemsControl Canvas.ZIndex="2" ItemsSource="{Binding Entities}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
The Entity class:
[Magic]
public class Entity : ObservableObject
{
public Entity()
{
Height = 16;
Width = 16;
Location = new Vector(Global.rand.Next(800), Global.rand.Next(800));
Image = Global.LoadBitmap("Resources/Thing1.png");
}
public int Height { get; set; }
public int Width { get; set; }
public Vector Location { get; set; }
public WriteableBitmap Image { get; set; }
}
To move the object:
private Action<Entity> action = (Entity entity) =>
{
entity.Location = new Vector(entity.Location.X + 1, entity.Location.Y);
};
void Timer_Tick(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
foreach (var entity in Locator.Container.Entities)
{
action(entity);
}
});
}
If I have fewer than about 400 entries in the Entities collection, movement is smooth, but I'd like to be able to increase that number by quite a bit. If I go above 400, movement becomes increasingly choppy. At first I thought it was an issue with the movement logic (which at this point isn't really much of anything), but I have found that that's not the problem. I added another collection with 10,000 entries and added that collection to the same timer loop as the first but did not include it in the XAML, and the UI didn't react any differently. What I find odd, however, is that if I add 400 entries to the collection and then 400 more with Image set to null, movement becomes choppy even though half of the items aren't drawn.
So, what can I do, if anything, to be able to draw and smoothly move more images on a canvas? Is this a situation where I may want to shy away from WPF & XAML? If you need more code, I will gladly post it.
Update: Per Clemens' suggestion, my Entity DataTemplate now looks like this:
<DataTemplate DataType="{x:Type local:Entity}">
<Image Width="{Binding Width}"
Height="{Binding Height}"
Source="{Binding Image}">
<Image.RenderTransform>
<TranslateTransform X="{Binding Location.X}" Y="{Binding Location.Y}" />
</Image.RenderTransform>
</Image>
</DataTemplate>
There may be a boost in performance by using this, but if there is it is very subtle. Also, I have noticed that if I use a DispatcherTimer for the loop and set it up as:
private DispatcherTimer dTimer = new DispatcherTimer();
public Loop()
{
dTimer.Interval = TimeSpan.FromMilliseconds(30);
dTimer.Tick += Timer_Tick;
dTimer.Start();
}
void Timer_Tick(object sender, EventArgs e)
{
foreach (var entity in Locator.Container.Entities)
{
action(entity);
}
}
... The movement is smooth even with several thousand items, but very slow, regardless of the interval. If a DispatcherTimer is used and Timer_Tick looks like this:
void Timer_Tick(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
foreach (var entity in Locator.Container.Entities)
{
action(entity);
}
});
}
... the movement is very choppy. What I find odd is that a Stopwatch shows that the Task.Factory takes between 1000 and 1400 ticks to iterate over the collection if there are 5,000 entries. The standard foreach loop takes over 3,000 ticks. Why would Task.Factory perform so poorly when it is twice as fast? Is there a different way to iterate through the collection and/or a different timing method that might allow for smooth movement without any major slowdowns?
Update: If anybody can help me improve the performance of real-time movement of objects on a canvas or can suggest another way in WPF to achieve similar results, 100 bounty awaits.
Having so many controls move on the screen this frequently will never yield smooth results. You need to a completely different approach - rendering on your own. I'm not sure this would suit you, as now you will not be able to use control features per each item (e.g. to receive events, have tooltips or use data templates.) But with such a large amount of items, other approaches are impractical.
Here's a (very) rudimentary implementation of what that might look like:
Update: I've modified the renderer class to use the CompositionTarget.Rendering event instead of a DispatcherTimer. This event fires every time WPF renders a frame (normally around 60 fps). While this would provide smoother results, it is also more CPU intensive, so be sure to turn off the animation when it's no longer needed.
public class ItemsRenderer : FrameworkElement
{
private bool _isLoaded;
public ItemsRenderer()
{
Loaded += OnLoaded;
Unloaded += OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
_isLoaded = true;
if (IsAnimating)
{
Start();
}
}
private void OnUnloaded(object sender, RoutedEventArgs routedEventArgs)
{
_isLoaded = false;
Stop();
}
public bool IsAnimating
{
get { return (bool)GetValue(IsAnimatingProperty); }
set { SetValue(IsAnimatingProperty, value); }
}
public static readonly DependencyProperty IsAnimatingProperty =
DependencyProperty.Register("IsAnimating", typeof(bool), typeof(ItemsRenderer), new FrameworkPropertyMetadata(false, (d, e) => ((ItemsRenderer)d).OnIsAnimatingChanged((bool)e.NewValue)));
private void OnIsAnimatingChanged(bool isAnimating)
{
if (_isLoaded)
{
Stop();
if (isAnimating)
{
Start();
}
}
}
private void Start()
{
CompositionTarget.Rendering += CompositionTargetOnRendering;
}
private void Stop()
{
CompositionTarget.Rendering -= CompositionTargetOnRendering;
}
private void CompositionTargetOnRendering(object sender, EventArgs eventArgs)
{
InvalidateVisual();
}
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.Register("ImageSource", typeof (ImageSource), typeof (ItemsRenderer), new FrameworkPropertyMetadata());
public ImageSource ImageSource
{
get { return (ImageSource) GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
public static readonly DependencyProperty ImageSizeProperty =
DependencyProperty.Register("ImageSize", typeof(Size), typeof(ItemsRenderer), new FrameworkPropertyMetadata(Size.Empty));
public Size ImageSize
{
get { return (Size) GetValue(ImageSizeProperty); }
set { SetValue(ImageSizeProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof (IEnumerable), typeof (ItemsRenderer), new FrameworkPropertyMetadata());
public IEnumerable ItemsSource
{
get { return (IEnumerable) GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
protected override void OnRender(DrawingContext dc)
{
ImageSource imageSource = ImageSource;
IEnumerable itemsSource = ItemsSource;
if (itemsSource == null || imageSource == null) return;
Size size = ImageSize.IsEmpty ? new Size(imageSource.Width, imageSource.Height) : ImageSize;
foreach (var item in itemsSource)
{
dc.DrawImage(imageSource, new Rect(GetPoint(item), size));
}
}
private Point GetPoint(object item)
{
var args = new ItemPointEventArgs(item);
OnPointRequested(args);
return args.Point;
}
public event EventHandler<ItemPointEventArgs> PointRequested;
protected virtual void OnPointRequested(ItemPointEventArgs e)
{
EventHandler<ItemPointEventArgs> handler = PointRequested;
if (handler != null) handler(this, e);
}
}
public class ItemPointEventArgs : EventArgs
{
public ItemPointEventArgs(object item)
{
Item = item;
}
public object Item { get; private set; }
public Point Point { get; set; }
}
Usage:
<my:ItemsRenderer x:Name="Renderer"
ImageSize="8 8"
ImageSource="32.png"
PointRequested="OnPointRequested" />
Code Behind:
Renderer.ItemsSource = Enumerable.Range(0, 2000)
.Select(t => new Item { Location = new Point(_rng.Next(800), _rng.Next(800)) }).ToArray();
private void OnPointRequested(object sender, ItemPointEventArgs e)
{
var item = (Item) e.Item;
item.Location = e.Point = new Point(item.Location.X + 1, item.Location.Y);
}
You can use the OnPointRequested approach to get any data from the item (such as the image itself.) Also, don't forget to freeze your images, and pre-resize them.
A side note, regarding threading in the previous solutions. When you use a Task, you're actually posting the property update to another thread. Since you've bound the image to that property, and WPF elements can only be updated from the thread on which they were created, WPF automatically posts each update to the Dispatcher queue to be executed on that thread. That's why the loop ends faster, and you're not timing the actual work of updating the UI. It's only adding more work.
In a first optimization approach you may reduce the number of Canvases to just one by removing the Canvas from the DataTemplate and setting Canvas.Left and Canvas.Top in an ItemContainerStyle:
<DataTemplate DataType="{x:Type local:Entity}">
<Image Width="{Binding Width}" Height="{Binding Height}" Source="{Binding Image}"/>
</DataTemplate>
<ItemsControl ItemsSource="{Binding Entities}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Location.X}"/>
<Setter Property="Canvas.Top" Value="{Binding Location.Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
Then you may replace setting Canvas.Left and Canvas.Top by applying a TranslateTransform:
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform X="{Binding Location.X}" Y="{Binding Location.Y}"/>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
Now this could similarly be applied to the Image control in the DataTemplate instead of the item container. So you may remove the ItemContainerStyle and write the DataTemplate like this:
<DataTemplate DataType="{x:Type local:Entity}">
<Image Width="{Binding Width}" Height="{Binding Height}" Source="{Binding Image}">
<Image.RenderTransform>
<TranslateTransform X="{Binding Location.X}" Y="{Binding Location.Y}"/>
</Image.RenderTransform>
</Image>
</DataTemplate>
Try using TranslateTransform instead of Canvas.Left and Canvas.Top. The RenderTransform and TranslateTransform are efficient in scaling/moving existing drawing objects.
That's an issue I had to solve when developping a very simple Library called Mongoose.
I tried it with a 1000 images and its totally smooth (I don't have code that automatically moves images, I move them manually by drag and dropping on the Surface, but you should have the same result with code).
I wrote a quick sample you can run by using the library (you just need an attached view model with a collection of anything called PadContents) :
MainWindow.xaml
<Window x:Class="Mongoose.Sample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:col="clr-namespace:System.Collections;assembly=mscorlib"
xmlns:mwc="clr-namespace:Mongoose.Windows.Controls;assembly=Mongoose.Windows"
Icon="Resources/MongooseLogo.png"
Title="Mongoose Sample Application" Height="1000" Width="1200">
<mwc:Surface x:Name="surface" ItemsSource="{Binding PadContents}">
<mwc:Surface.ItemContainerStyle>
<Style TargetType="mwc:Pad">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Image Source="Resources/MongooseLogo.png" Width="30" Height="30" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</mwc:Surface.ItemContainerStyle>
</mwc:Surface>
</Window>
MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.Windows;
namespace Mongoose.Sample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public ObservableCollection<object> PadContents
{
get
{
if (padContents == null)
{
padContents = new ObservableCollection<object>();
for (int i = 0; i < 500; i++)
{
padContents.Add("Pad #" + i);
}
}
return padContents;
}
}
private ObservableCollection<object> padContents;
}
}
And here is what it looks like for 1000 images :
The full code is available on Codeplex so even if you don't want to reuse the library, you can still check the code to see how achieved it.
I rely on a few tricks, but mostly the use of RenderTransform and CacheMode.
On my computer it's ok for up to 3000 images. If you want to do more, you'll probably have to think of other ways to achieve it though (maybe with some kind of virtualization)
Good luck !
EDIT:
By adding this code in the Surface.OnLoaded method :
var messageTimer = new DispatcherTimer();
messageTimer.Tick += new EventHandler(surface.messageTimer_Tick);
messageTimer.Interval = new TimeSpan(0, 0, 0, 0, 10);
messageTimer.Start();
And this method in the Surface class :
void messageTimer_Tick(object sender, EventArgs e)
{
var pads = Canvas.Children.OfType<Pad>();
if (pads != null && Layout != null)
{
foreach (var pad in pads)
{
pad.Position = new Point(pad.Position.X + random.Next(-1, 1), pad.Position.Y + random.Next(-1, 1));
}
}
}
You can see that it's totally ok to move each object separately.
Here is a samll example with 2000 objects
The issue here is the rendering/creation of so many controls.
The first question is whether you need to show all the images on the canvas. If so, I'm sorry but I can't help (if you need to draw all items then there's no way around it).
But if not all items are visible on the screen at one time - then you have hope in the shape of Virtualization. You'd need to write your own VirtualizingCanvas that inherits VirtualizingPanel and creates only the items that are visible. This will also allow you to recycle the containers which in turn will remove a lot of the load.
There's an example of a virtualizing canvas here.
Then you'd need to set the new canvas as your items panel, and set up the items to have the necessary information for the canvas to work properly.
A few thoughts that come to mind:
Freeze your bitmaps.
Hard set the size of your bitmaps when you read them to be identical to the size you're displaying them in, and set the BitmapScalingMode to LowQuality.
Track your progress while updating your entities and cut out early if you can't and grab them next frame. This will require tracking their last frame too.
// private int _lastEntity = -1;
// private long _tick = 0;
// private Stopwatch _sw = Stopwatch.StartNew();
// private const long TimeSlice = 30;
// optional: this._sw.Restart();
var end = this._sw.ElapsedMilliseconds + TimeSlice - 1;
this._tick++;
var ee = this._lastEntity++;
do {
if (ee >= this._entities.Count) ee = 0;
// entities would then track the last time
// they were "run" and recalculate their movement
// from 'tick'
action(this._entities[ee], this._tick);
if (this._sw.ElapsedMilliseconds > end) break;
} while (ee++ != this._lastEntity);
this._lastEntity = ee;
I would like to know, how I could make the transition between two background colors more smooth. Is it possible to make some kind of fade transition?
I have created this little sample project to illustrate the behavior.
MainWindow.xaml
<Window x:Class="FadeTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Converter="clr-namespace:FadeTest" Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Converter:BackgroundPercentConverter x:Key="backgroundPercentConverter"/>
</Window.Resources>
<DockPanel>
<Label Content="{Binding PercentComplete}" Height="100" Width="200"
DockPanel.Dock="Top" Foreground="White" FontSize="28"
Background="{Binding PercentComplete, Converter={StaticResource backgroundPercentConverter}}"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center" />
<Button DockPanel.Dock="Bottom" Click="Button_Click" Width="100" Height="32">Click</Button>
</DockPanel>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyClass {PercentComplete = 0};
}
private void Button_Click(object sender, RoutedEventArgs e)
{
((MyClass) DataContext).PercentComplete++;
}
}
MyClass.cs
class MyClass : INotifyPropertyChanged
{
private int _percentComplete;
public int PercentComplete
{
get { return _percentComplete; }
set
{
if (value >= 10)
value = 0;
_percentComplete = value;
OnPropertyChanged("PercentComplete");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
BackgroundPercentConverter.cs
public class BackgroundPercentConverter : IValueConverter
{
private static readonly SolidColorBrush[] MyColors = new[]
{
new SolidColorBrush(Color.FromRgb(229, 29, 37)),
new SolidColorBrush(Color.FromRgb(252, 52, 0)),
new SolidColorBrush(Color.FromRgb(253, 81, 0)),
new SolidColorBrush(Color.FromRgb(255, 101, 1)),
new SolidColorBrush(Color.FromRgb(255, 133, 0)),
new SolidColorBrush(Color.FromRgb(254, 175, 0)),
new SolidColorBrush(Color.FromRgb(221, 182, 3)),
new SolidColorBrush(Color.FromRgb(173, 216, 2)),
new SolidColorBrush(Color.FromRgb(138, 191, 62)),
new SolidColorBrush(Color.FromRgb(47, 154, 69))
};
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((int)value < 0 || (int)value >= MyColors.Length)
return MyColors[0];
return MyColors[(int)value];
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You could define a StoryBoard which has a ColorAnimation which defines a from and to colour i.e. the colours you want to interpolate smoothly between.
Your animation should target the Color property of a SolidColorBrush which can either be one that is defined as a Resource and is then referenced by your control (i.e. your Label uses {StaticResource xxxxxx} in the Background to refer to it, or your animation could target the Background in your Label directly IF the Background contains a Brush which is a SolidColorBrush (...normally that is the case...but that depends entirely on how the Template for the control is designed).
Then you would need to begin and pause the animation and seek to the position in the animation that corresponds to your percentage. E.g. you could set the Duration to 100 seconds, and then you could simply use your percentage value as the number of seconds in a TimeSpan. (Might have to set IsControllable=true).
You would start and pause the animation when the control is loaded, and then change the seek position in sync with your percentage change.
Note, it's probably a bad idea just having an animation running all the time, just to map a range value to a presentation colour, but it's another option to what you have.
Otherwise stick to your valueconverter and you could calculate the linear interpolation between the 2 colours yourself. See this link:
Algorithm: How do I fade from Red to Green via Yellow using RGB values?
Here are some links related to the animation:
http://social.msdn.microsoft.com/Forums/en/wpf/thread/d2517850-81cc-4d22-be7d-cad522540ad2
How to seek a WPF Storyboard to a specific frame/time offset when not running