Flipbook Animations using List - c#

I want to crate a simple flip-book animation in WPF. A flip-book animation just flips between multiple images (eg. classical animation or an animated gif). Note: I am not trying to create a Page Curl animation.
The set of images comes from a List collection, and is not know at compile time.
As such, I was thinking it would be good to start with an ItemsControl bound to my list of images, and then somehow cycle through them with a storyboard animation. However, I am running into a lot of issues with this, and feeling like there might be a better way.
Has anyone done this before, or have ideas for solutions? Thanks!

It might be easier to use a DispatcherTimer. Storyboards are good for animating properties, but they do not work so well with lists and ItemsControls.
I have tested the following approach, and it seems to work well. First put each image into a view-model wrapper with a Visibility property.
public class ImageViewModel : INotifyPropertyChanged
{
public string ImagePath { get; set; }
public Visibility Visibility
{
get { return _vis; }
set
{
_vis = value;
RaisePropertyChanged("Visibility");
}
}
private Visibility _vis = Visibility.Collapsed;
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string prop)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(prop));
}
}
So your main view model would have a list of the image view models:
public class FlipBookViewModel
{
public List<ImageViewModel> FlipBookImages { get; private set; }
public FlipBookViewModel(string[] imagePaths)
{
FlipBookImages = imagePaths.Select(imagePath => new ImageViewModel
{ ImagePath = imagePath }
).ToList();
}
}
Then, in the page, simply place them on top of each other by using a Grid as the ItemsPanel:
<ItemsControl ItemsSource="{Binding FlipBookImages}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding ImagePath}" Visibility="{Binding Visibility}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
To start the flip book, fire up the DispatcherTimer. Something like this:
var dispatcherTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
int i=0;
dispatcherTimer.Tick += (sender, args) => {
if (i>0)
FlipBookImages[i-1].Visibility = Visibility.Collapsed;
if (i>=FlipBookImages.Count)
dispatcherTimer.Stop();
else
FlipBookImages[i].Visibility = Visibility.Visible;
i++;
};
dispatcherTimer.Start();

Related

Improve WPF performance with many usercontrols

I am building my first WPF application and I ran into some performance issues that I am not sure how to deal with. I have a list of around 500 items that I bind to UserControls using an ItemsControl in a Page. The data gets loaded into memory during the program startup and is displayed when the user opens the page. This already takes a few seconds but can be dealt with, however the data can also be reordered (e.g. ascending, descending) and filtered which takes around 0.6-1.3 seconds every time (depending on reordering method used). It seems to be caused by the UI parsing and layout and I was hoping this could be improved somehow. Here is the performance graph in case that helps.
And here is the code of the Item class and associated UserControl xaml (simplified):
public class Item : INotifyPropertyChanged
{
public String name { get; set; }
public String content { get; set; }
public BitmapImage image { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private Visibility visibility = Visibility.Visible;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public void LoadImage()
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.DecodePixelWidth = 48;
bitmap.DecodePixelHeight = 48;
bitmap.UriSource = new Uri(somepath);
bitmap.EndInit();
bitmap.Freeze();
image = bitmap;
}
public Item()
{
LoadImage();
}
}
<UserControl
...
Visibility="{Binding Path=Visibility}">
<Grid Background="White">
<Image x:Name="ItemImage" Source="{Binding Path=image}" Width="64" Height="64"></Image>
<TextBlock x:Name="NameTextBlock" Text="{Binding Path=name}" FontSize="18px"></TextBlock>
<TextBlock x:Name="ContentTextBlock" Text="{Binding Path=content}" FontSize="14px" ></TextBlock>
</Grid>
</UserControl>
And here is the xaml and code of the Page which displays the Usercontrols:
<Page>
<StackPanel x:Name="RootStackPanel">
<Some static content>
<ScrollViewer x:Name="ItemScrollViewer" CanContentScroll="True" VerticalScrollBarVisibility="Auto" Width="auto" VerticalAlignment="Top" HorizontalAlignment="Stretch">
<ItemsControl x:Name="CustomItemsControl">
</ItemsControl>
</ScrollViewer>
</StackPanel>
</Page>
public partial class ItemsPage : Page
{
public static ItemsPage Instance = new ItemsPage();
public ObservableCollection<Item> collection { get; set; }
// called by a button on the page
private void ReorderItems()
{
// Simplified: Usually more ordering options e.g. OrderByDescending
List<Item> temp = new List<Item>(collection.OrderBy(item => item.name));
collection.Clear();
foreach (Item item in temp)
{
collection.Add(item);
}
}
public ItemsPage()
{
InitializeComponent();
collection = new ObservableCollection<Item>(ItemCollection.GetItems());
// getItems returns the list of items as List<>
FrameworkElementFactory factoryPanel = new FrameworkElementFactory(typeof(VirtualizingStackPanel));
factoryPanel.SetValue(VirtualizingStackPanel.IsItemsHostProperty, true);
factoryPanel.SetValue(VirtualizingStackPanel.IsVirtualizingProperty, true);
factoryPanel.SetValue(VirtualizingStackPanel.VirtualizationModeProperty, VirtualizationMode.Recycling);
ItemsPanelTemplate template = new ItemsPanelTemplate();
template.VisualTree = factoryPanel;
CustomItemsControl.ItemsPanel = template;
FrameworkElementFactory dataTemplateFactory = new FrameworkElementFactory(typeof(ItemUserControl));
DataTemplate dataTemplate = new DataTemplate();
dataTemplate.VisualTree = dataTemplateFactory;
CustomItemsControl.ItemTemplate = dataTemplate;
CustomItemsControl.ItemsSource = collection;
}
}
As can be seen I tried using Virtualization but I am not sure if I am using it correctly since I saw no measurable performance gain. The pictured reordering method is obviously not ideal since I alternatively tried reordering the members of the items instead and calling the PropertyChangedEventHandler on the bound members. This did improve performance by getting rid of the parsing step but still takes 600ms on average per reorder. Are there better methods to do that or should I be trying to simplify my UserControls instead?
When you use a ScrollViewer around ItemsControl Virtualization will not work.
extract ItemsControl from ScrollViewer.

Textblock binding does not update in RunTime

I'm new in c# UWP development and I'm trying to change the value of a TextBlock in runtime, but the binding does not work properly.
I'm binding the text property of the TextBlock in XAML to a property on a ViewModel with INotifyPropertyChanged, and the value changes every 10 seconds.
I don't know if it's the correct way to do it, can someone help me?
Thanks in advance!
this is the ViewModel code
class MainPaigeViewModel : INotifyPropertyChanged
{
public MainPaigeViewModel()
{
Task.Run(async () =>
{
Random random = new Random();
while (true)
{
await Task.Delay(10000);
int newValue = random.Next(-40, 40);
_MyValue = newValue.ToString();
Debug.WriteLine(MyValue);
}
});
}
//Properties
private string _MyValue;
public string MyValue
{
get { return _MyValue; }
set
{
_MyValue = value;
RaisePropertyChanged("MyValue");
}
}
//INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
 and the XAML code
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CountDown2"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ViewModels="using:CountDown2.ViewModels"
x:Class="CountDown2.MainPage"
mc:Ignorable="d">
<Page.DataContext>
<ViewModels:MainPaigeViewModel/>
</Page.DataContext>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<RelativePanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="{Binding MyValue, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Width="100"
Height="40"
TextAlignment="Center"
FontSize="20"
/>
</RelativePanel>
</Grid>
</Page>
In UWP unlike silver light and WPF the default binding is One time for performance reasons. The Binding only takes place once as the application starts up. One way binding is the default of WinRT, Silverlight and wpf. Meaning the view will be updated but updating the view will not update view model. Two way binding will update both the view and the view model.
So for a <TextBlock> in the example, it is recommended to use One Way binding.
In a <TextBox> it is recommended to use Two Way binding for user input.
I found a couple small bugs that were causing the binding to fail ... so I changed the viewmodel... The private property was being used rather than public one. Since the code is updating the value in a thread, and then trying to marshal the objects across threads, a dispatcher was added. Also added a common base class for all view models. This make property binding a little easier, it stops binding issues when refactoring property names.
Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync
public class MainPaigeViewModel: ViewModelBase
{
public MainPaigeViewModel()
{
Task.Run(async () =>
{
Random random = new Random();
while (true)
{
await Task.Delay(1000);
int newValue = random.Next(-40, 40);
try
{
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() => {
MyValue = newValue.ToString();
});
}
catch (Exception ex)
{
string s = ex.ToString();
}
Debug.WriteLine(MyValue);
}
});
}
//Properties
private string _MyValue;
public string MyValue
{
get { return _MyValue; }
set
{
_MyValue = value;
OnPropertyChanged();
}
}
}
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I also changed the view to use x:binding. I like x:binding over the old data binding because it shows binding issues at compile time rather than at runtime. This is besides the performance enhancements it gives.
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<RelativePanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="{x:Bind viewModel.MyValue, Mode=OneWay}"
Width="100"
Height="40"
TextAlignment="Center"
FontSize="20"
/>
</RelativePanel>
</Grid>
Page behind code for x:bind
public sealed partial class MainPage : Page
{
public MainPaigeViewModel viewModel;
public MainPage()
{
this.InitializeComponent();
viewModel = new MainPaigeViewModel();
}
}
Try:Text="{Binding MyValue, Mode=TwoWay}"

Windows 10 Development: How to refresh ListView whenever there is a change in the items inside ListView?

I am very new to the concept of data binding and I don't think I understood it completely. I have a class named Project with a LinkedList of type ToDo as one of its properties. When I navigate to one instance of Project, I will display the LinkedList of type ToDo in a ListView. I have created functions that allow me to change the sequences of the nodes in the LinkedList (move up, move down) and to remove the selected node (delete). I want the ListView to refresh whenever there is a change in the LinkedList, (move up, move down or delete). However, I cannot achieve that. Here is my code: (not all parts are included)
XAML of the page:
<ListView x:Name="myListView" ItemsSource="{Binding Source={StaticResource ToDos}, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox x:Name="myCheckBox"
Content="{Binding ToDoTitle, Mode=TwoWay}"
IsChecked="{Binding IsCompleted, Mode=TwoWay}">
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
C# for DataModel:
public class ToDo : INotifyPropertyChanged
{
private string toDoTitle;
private bool isCompleted;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public string ToDoTitle { get { return this.toDoTitle; } set { this.toDoTitle = value; this.OnPropertyChanged(); } }
public bool IsCompleted { get { return this.isCompleted; } set { this.isCompleted = value; this.OnPropertyChanged(); } }
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// Raise the PropertyChanged event, passing the name of the property whose value has changed.
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Projects : INotifyPropertyChanged
{
private LinkedList<ToDo> toDos;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public LinkedList<ToDo> ToDos { get { return this.toDos; } set { this.toDos = value; this.OnCollectionChanged(); } }
public Projects()
{
ToDos = new LinkedList<ToDo>();
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// Raise the PropertyChanged event, passing the name of the property whose value has changed.
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Thank you.
First I would advise you to read about MVVM, and try to follow some basic tutorials like this one.
You can use MVVM Light to avoid managing the INotifyPropertyChanged by yourself at first (but it's really good to know how MVVM light work under the hood).
To come back to your problem, your current code notifies only if you set the full ToDos list. If you want to be aware of any change in a list (seing when an item is add/remove/update), you are probably looking for an ObservableCollection, not a LinkedList.
Hope it helps.

unable to bind images to listbox and get the selected item in wpf

I need to show a collection of images horizontally in a ListBox which I could achieve. However, the challenge am facing here is that am unable to bind the selected item (Image) to the ListBox-selected item property when I click or select any image in the ListBox and get the selected image as well in code-behind.
the following is my XAML code and later is my XAML.cs code. Please let me know if I am missing something to fix the issue.
Thanks.
<ListBox Grid.Row="1" x:Name="ThumbnailPanel" ItemsSource="{Binding Frames}"
SelectedItem="{Binding Path=MediaFrame,Mode=TwoWay}"
ScrollViewer.CanContentScroll="True"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Hidden">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="20" Rows="1">
</UniformGrid>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string property)
{
var propertyChanged = PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public ObservableCollection<Image> Frames { get; set; }
public Image MediaFrame
{
get
{
return mediaFrame;
}
set
{
if (mediaFrame != value)
{
mediaFrame = value;
OnPropertyChanged("MediaFrame");
}
}
}
private Image mediaFrame;
if (Frames == null)
{
Frames = new ObservableCollection<Image>();
}
Frames.Add(
new Image
{
Source = imageSource,
ToolTip = _mediaPlayer.Position,
MaxWidth = _mediaPlayer.NaturalVideoWidth,
MaxHeight = _mediaPlayer.NaturalVideoHeight,
Margin = new Thickness(2)
});
ThumbnailPanel.DataContext = Frames;
It's not quite clear, but when you write
ThumbnailPanel.DataContext = Frames;
is that the Frames property? How would the ItemsSource="{Binding Frames}" binding work then? I guess you need to set the DataContext to the object that has the Frames and MediaFrame property. Perhaps:
ThumbnailPanel.DataContext = this;

DataBinding TextBox with an Integer value WP7

I want to bind the user score to a text box on the windows phone app in silverlight. here is the skeleton of my Game Class
public class Game : INotifyPropertyChanged
{
private int _userScore;
public string UserScore {
{
return _userScore.ToString();
}
set
{
_userScore = Convert.ToInt32(value);
NotifyPropertyChanged("UserScore");
}
}
public Game()
{
UserScore = "0";
}
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In my XAML I have
<TextBlock Margin="28,74,242,386" Name="scoreTextBlock"
Text="SCORE" DataContext="{Binding UserScore}" />
and in the MainPage.xaml.cs
public MainPage()
{
InitializeComponent();
Game theGame = new Game();
DataContext = theGame;
}
The Question
When I run the App, the score gets modified correctly but it doesn't display inside the scoreTextBlock.
Is there something that I'm doing wrong?
You don't need to bind to a string. You can bind directly to an integer:
private int _userScore;
public int UserScore
{
{
return _userScore;
}
set
{
_userScore = value;
NotifyPropertyChanged("UserScore");
}
}
And you'd simply set it like this:
public Game()
{
UserScore = 0;
}
Then change your TextBlock to:
<TextBlock Margin="28,74,242,386" Name="scoreTextBlock" Text="{Binding UserScore}" />
You've set the DataContext on the view, you don't need to do it again. If you want to display the word "Score" you'll have to use a second TextBlock.
This should work.
I think you are attempting to bind on this line:
<TextBlock Margin="28,74,242,386" Name="scoreTextBlock" Text="SCORE" DataContext="{Binding UserScore}"/>
but that is is incorrect. The DataContext property should be the instance of the game class, and Text property should be the score. Something like this:
<StackPanel Orientation="Horizontal">
<TextBlock Text="SCORE:"/>
<TextBlock Text="{Binding UserScore}"/>
</StackPanel>
That code still needs a datacontext, but I'm not sure how you are instantiating and locating the instance, so I declined to add any example code for it.
Keep #ChrisF's comments in mind also.

Categories