WPF Custom Control drawing avoid infinite measure/arrange loop - c#

I am building a custom control that does some custom drawing based on some data. I want to update the drawing when arrange is called (i.e. the size is changed). But when I am changing my Children in ArrangeOverride() I get an infinite loop of course. How can I avoid this?
For simplicity it is easier for me to rebuild the whole visual tree instead of creating children once and resizing them individually.
Is there a better approach to do this? I can also live with just using a DrawingContext object and invoking my drawing logic there.
public class MyCanvas : Canvas
{
private static int _drawCounter = 0;
private System.Windows.Size _arrangeSize;
private MyData _data;
protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
{
_arrangeSize = arrangeSize;
Draw();
return base.ArrangeOverride(arrangeSize);
}
public void SetData(MyData data)
{
_data = data;
Draw();
}
private void Draw()
{
Children.Clear();
if (_data == null || _arrangeSize.IsEmpty)
{
return;
}
Children.Add(new TextBlock() {Text = (++_drawCounter).ToString()});
}
}

Here is how I solved it:
public class MyCanvas : Canvas
{
private readonly DispatcherTimer _dispatcherTimer;
private Size _arrangeSize;
private Size _drawnSize;
public MyCanvas()
{
_dispatcherTimer = new DispatcherTimer(DispatcherPriority.Render)
{
Interval = TimeSpan.FromMilliseconds(500)
};
_dispatcherTimer.Tick += (sender, args) =>
{
var dispatcherTimer = (DispatcherTimer)sender;
dispatcherTimer.Stop();
Debug.WriteLine("Draw call from DispatcherTimer");
Draw();
};
}
protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
{
_arrangeSize = arrangeSize;
if (_drawnSize != _arrangeSize)
{
QueueDrawCall();
}
return base.ArrangeOverride(arrangeSize);
}
private void QueueDrawCall()
{
if (_dispatcherTimer.IsEnabled)
{
_dispatcherTimer.Stop();
}
_dispatcherTimer.Start();
}
public void SetData(MyData data)
{
_data = data;
Console.WriteLine("Direct Draw Call " + data);
Draw();
}
private void Draw()
{
if (Children.Count > 0)
{
Children.Clear();
}
if (_data == null || _arrangeSize.IsEmpty)
{
return;
}
InternalDraw(); // Drawing logic goes in this function
_drawnSize = _arrangeSize;
}
}

Related

Differentiate items loading to list vs scroll event Xamarin forms listview

This is my code that can successfully detect scroll up or down:
MyListView.ItemAppearing += async (object sender, ItemVisibilityEventArgs e) =>
{
var currentIdx = CurrentList.IndexOf((MyClass)e.Item);
if (currentIdx > _lastItemAppearedIdx)
ShowChopped();
else
ShowFull();
_lastItemAppearedIdx = CurrentList.IndexOf((MyClass)e.Item);
};
What is working is the following: Items get added to the list, then once i start scrolling it works fine where ShowChoppedand ShowFull are methods with animations that just makes a simple animation to either half the size of an object or make it full. This works fine, but if i however click a new category that changes the content in the list, ItemAppearing gets triggered of course and ShowChoppedand ShowFull are called even though i only want it called during a scrollevent.
How would i be able to differentiate a scroll to a item collection change? I have only tried this on iOS.
Updated code:
public class ListView_iOS : ListViewRenderer
{
private IDisposable _offsetObserver;
private double _prevYOffset;
private IListView _myListView;
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.ListView> e)
{
base.OnElementChanged(e);
if (e.NewElement is IListView)
_offsetObserver = Control.AddObserver("contentOffset",
Foundation.NSKeyValueObservingOptions.New, HandleAction);
}
private static bool CloseTo(double x, double y)
{
return Math.Abs(x - y) < 0.1;
}
private void HandleAction(Foundation.NSObservedChange obj)
{
var effectiveY = Math.Max(Control.ContentOffset.Y, 0);
if (!CloseTo(effectiveY, _prevYOffset) && Element is IListView)
{
var myList = Element as IListView;
myList.IsScrolling = true;
}
}
}
You can differentiate items loading from list scrolling by
1 adding the code if (EmployeeView.IsScrolling) within ItemAppearing method.
2 adding the code EmployeeView.IsScrolling = false; within any function you write to change the appearing of items without scrolling action, for example, when you add items or change category.
And the EmployeeView.IsScrolling value is set from listview renderer.
So the code is like:
NativeListView.cs
public class NativeListView : ListView
{
public static readonly BindableProperty
IsScrollingProperty =
BindableProperty.Create(nameof(IsScrolling),
typeof(bool), typeof(NativeListView), false);
public bool IsScrolling
{
get { return (bool)GetValue(IsScrollingProperty); }
set { SetValue(IsScrollingProperty, value); }
}
}
NativeAndroidListViewRenderer.cs
[assembly: ExportRenderer(typeof(NativeListView), typeof(NativeAndroidListViewRenderer))]
namespace App2.Droid
{
public class NativeAndroidListViewRenderer : ListViewRenderer
{
public NativeAndroidListViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.ListView> e)
{
base.OnElementChanged(e);
if (e.NewElement is NativeListView)
Control.Scroll += Control_Scroll;
}
private void Control_Scroll(object sender, AbsListView.ScrollEventArgs e)
{
var myList = Element as NativeListView;
myList.IsScrolling = true;
}
}
}
NativeiOSListViewRenderer.cs
private IDisposable _offsetObserver;
private double _prevYOffset;
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.ListView> e)
{
base.OnElementChanged(e);
if (e.NewElement is NativeListView)
_offsetObserver = Control.AddObserver("contentOffset",
Foundation.NSKeyValueObservingOptions.New, HandleAction);
}
private void HandleAction(Foundation.NSObservedChange obj)
{
var effectiveY = Math.Max(Control.ContentOffset.Y, 0);
if (!CloseTo(effectiveY, _prevYOffset) && Element is NativeListView)
{
var myList = Element as NativeListView;
myList.IsScrolling = true;
_prevYOffset = effectiveY;
}
}
private static bool CloseTo(double x, double y)
{
return Math.Abs(x - y) < 0.1;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing && _offsetObserver != null)
{
_offsetObserver.Dispose();
_offsetObserver = null;
}
}
MainPage.xaml.cs
namespace App2
{
public partial class MainPage : ContentPage
{
ObservableCollection<String> employeeList = new ObservableCollection<String>();
int count = 0;
public MainPage()
{
InitializeComponent();
AddButtion.Clicked += AddButtion_Clicked;
DelButtion.Clicked += DelButtion_Clicked;
EmployeeView.ItemsSource = employeeList;
EmployeeView.ItemAppearing += async (object sender, ItemVisibilityEventArgs e) =>
{
if (EmployeeView.IsScrolling) {
await DisplayAlert("ItemAppearing", e.Item + " row is appearing", "OK");
Console.WriteLine("ItemAppearing!!!!!!!!!!");
}
};
}
private void AddButtion_Clicked(object sender, EventArgs e)
{
employeeList.Add("Mr. Mono"+ count++);
EmployeeView.IsScrolling = false;
}
private void DelButtion_Clicked(object sender, EventArgs e)
{
if (employeeList.Count > 0) {
employeeList.RemoveAt(0);
}
EmployeeView.IsScrolling = false;
}
}
}

Class for controlling properties of the PictureBox (C# forms)

I am trying to write something along of a "clean" code... I want to make a Pong game, for now based on Forms.
I want to divide the game nicely into classes.
I want to have a ball class, AI that inherits from player class, I want to use the premade Form class for setting main Form properties (width etc).
I made a player class as such and I would like to ask you if the approach for naming, getters and setters and the general idea is correct. Isn't certain bits (if not all of it) rather redundant or badly written, I do not want to base entire "project" on bad assumptions and multiply the same mistakes all over the code.
namespace Pong
{
public class Player
{
protected PictureBox PaddleBox { get; set; }
protected Size PlayerSize
{
get
{
return PlayerSize;
}
set
{
if (PlayerSize.Height > 0 && PlayerSize.Width > 0)
{
PlayerSize = new Size(value.Width, value.Height);
PaddleBox.Size = PlayerSize;
}
}
}
protected Point Location
{
get
{
return Location;
}
set
{
PaddleBox.Location = new Point(value.X, value.Y);
}
}
protected Color BackColor
{
get
{
return BackColor;
}
set
{
PaddleBox.BackColor = value;
}
}
public Player()
{
PaddleBox = new PictureBox();
}
}
}
FORM class looks something along of this for now, maybe I should pass parameters such as size,location and color in the constructor? What is the best?
namespace Pong
{
public partial class Form1 : Form
{
public Timer gameTime;
const int screenWidth = 1248;
const int screenHeight = 720;
public Form1()
{
InitializeComponent();
this.Height= screenHeight;
this.Width=screenWidth;
this.StartPosition=FormStartPosition.CenterScreen;
Player player = new Player();
player.PaddleBox.Size = new Size(20, 50);
player.PaddleBox.Location = new Point(player.PaddleBox.Width / 2, ClientSize.Height/2-player.PaddleBox.Height/2);
player.PaddleBox.BackColor = Color.Blue;
this.Controls.Add(player.PaddleBox);
gameTime = new Timer();
gameTime.Enabled = true;
}
void gameTime_Tick(object sender, EventArgs e)
{
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
}
You got an issue here:
protected Point Location
{
get
{
return Location; // <--- this is a circular reference..
// meaning, it will recall this getter again.
}
set
{
PaddleBox.Location = new Point(value.X, value.Y);
}
}
use this instead:
protected Point Location
{
get
{
return PaddleBox.Location;
}
set
{
PaddleBox.Location = value;
}
}
Same with protected Color BackColor
Here is an example, how I would implement it (in your current programming style (powered by notepad))
namespace Pong
{
public partial class Form1 : Form
{
public Timer gameTime;
const int screenWidth = 1248;
const int screenHeight = 720;
public Form1()
{
InitializeComponent();
this.Height= screenHeight;
this.Width=screenWidth;
this.StartPosition=FormStartPosition.CenterScreen;
Player player = new Player(this);
player.PlayerSize = new Size(20, 50);
player.Location = new Point(player.PaddleBox.Width / 2, ClientSize.Height/2-player.PaddleBox.Height/2); // <-- the location is always the upperleft point. don't do this...
player.BackColor = Color.Blue;
gameTime = new Timer();
gameTime.Enabled = true;
}
private void gameTime_Tick(object sender, EventArgs e)
{
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
public class Player
{
private PictureBox _paddleBox;
protected Size PlayerSize
{
get
{
return _paddleBox.Size;
}
set
{
if (PlayerSize.Height == 0 || PlayerSize.Width == 0)
throw new ArgumentException("Size must be greater than 0");
_paddleBox.Size = value;
}
}
protected Point Location
{
get { return PaddleBox.Location; }
set { PaddleBox.Location = value; }
}
protected Color BackColor
{
get { return PaddleBox.BackColor; }
set { PaddleBox.BackColor = value; }
}
public Player(Form form)
{
PaddleBox = new PictureBox();
form.Controls.Add(PaddleBox);
}
}
}
You should try to isolate the picturebox in your player class, this will separate the functionality of the form and the picturebox...

Highlighting keywords in a richtextbox in WPF [duplicate]

This question already has an answer here:
Highlight text in RichTextBox
(1 answer)
Closed 2 years ago.
I'm making a program which needs to look through a paragraph of text and find how many times a certain keyword/keywords appear. It also has to highlight each of these key words in the text.
I have managed to make he interface and it can now track how many times the word appears but I am really stuck for how to highlight where the keywords appear. I will post my code below, any help is greatly appreciated on how to search for and highlight text inside a richtextbox. Since this is in WPF the obvious richtextbox.find() is not avaliable for use.
class TextAnalyser
{
public int FindNumberOfOccurances(List<string> keywords, string email)
{
int occurances = 0;
foreach (string keyword in keywords)
{
occurances += email.ToUpper().Split(new string[] { keyword.ToUpper() }, StringSplitOptions.None).Count() - 1;
}
return occurances;
}
public void TurnTextRed(List<string> keywords, string email, RichTextBox TextBox)
{
foreach(string keyword in keywords)
{
}
}
public List<string> ConvertTextToList(string text)
{
char[] splitChars = {};
string[] ArrayText = text.Split( splitChars, StringSplitOptions.RemoveEmptyEntries);
return ArrayText.ToList<string>();
}
public string GetStringFromTextBox(RichTextBox TextBox)
{
var textRange = new TextRange(
TextBox.Document.ContentStart,
TextBox.Document.ContentEnd
);
return textRange.Text;
}
}
And here is my Main Window
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void AnalyseButton_Click(object sender, RoutedEventArgs e)
{
var textTool = new TextAnalyser();
var keyWords = textTool.ConvertTextToList(textTool.GetStringFromTextBox(WordTextBox).Trim());
var email = textTool.GetStringFromTextBox(EmailTextBox).Trim();
int usesOfWord = textTool.FindNumberOfOccurances(keyWords, email);
Occurances.Text = usesOfWord.ToString();
}
}
Here is the method is used to get all of word in richtextbox's document.
public static IEnumerable<TextRange> GetAllWordRanges(FlowDocument document)
{
string pattern = #"[^\W\d](\w|[-']{1,2}(?=\w))*";
TextPointer pointer = document.ContentStart;
while (pointer != null)
{
if (pointer.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
{
string textRun = pointer.GetTextInRun(LogicalDirection.Forward);
MatchCollection matches = Regex.Matches(textRun, pattern);
foreach (Match match in matches)
{
int startIndex = match.Index;
int length = match.Length;
TextPointer start = pointer.GetPositionAtOffset(startIndex);
TextPointer end = start.GetPositionAtOffset(length);
yield return new TextRange(start, end);
}
}
pointer = pointer.GetNextContextPosition(LogicalDirection.Forward);
}
}
You can change the pattern which is used to split words.
At last, easy to highlight your words.
IEnumerable<TextRange> wordRanges = GetAllWordRanges(RichTextBox.Document);
foreach (TextRange wordRange in wordRanges)
{
if (wordRange.Text == "keyword")
{
wordRange.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Red);
}
}
Ran across a need for this and couldn't find any suitable solutions. (Using a TextBox for binding, highlighting on the fly, multiple hits and colors, etc.) This can obviously be extended to suit your needs. This references a couple of extension methods that add/remove adorners of the specified Type T from the UIElement's adorner layer.
public class HighlightRule
{
public SolidColorBrush Brush { get; set; }
public string MatchText { get; set; }
public HighlightRule(SolidColorBrush solidColorBrush, string matchText)
{
Brush = solidColorBrush;
MatchText = matchText;
}
public HighlightRule(Color color, string matchText)
{
Brush = new SolidColorBrush(color);
MatchText = matchText;
}
public HighlightRule()
{
MatchText = null;
Brush = Brushes.Black;
}
}
public class HighlightTextBox : TextBox
{
public List<HighlightRule> HighlightRules
{
get { return ( List<HighlightRule>)GetValue(HighlightRulesProperty); }
set { SetValue(HighlightRulesProperty, value); }
}
// Using a DependencyProperty as the backing store for HighlightRules. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HighlightRulesProperty =
DependencyProperty.Register("HighlightRules", typeof(List<HighlightRule>), typeof(HighlightTextBox), new FrameworkPropertyMetadata(new List<HighlightRule>(), new PropertyChangedCallback(HighlightRulesChanged)));
private static void HighlightRulesChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
HighlightTextBox tb = (HighlightTextBox)sender;
tb.ApplyHighlights();
}
public HighlightTextBox() : base()
{
this.Loaded += HighlightTextBox_Loaded;
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
ApplyHighlights();
}
private void HighlightTextBox_Loaded(object sender, RoutedEventArgs e)
{
ApplyHighlights();
}
static HighlightTextBox()
{
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
}
public void ApplyHighlights()
{
this.TryRemoveAdorner<GenericAdorner>();
foreach(HighlightRule rule in HighlightRules)
{
if (!string.IsNullOrEmpty(rule.MatchText) && !string.IsNullOrEmpty(Text) &&
Text.ToLower().Contains(rule.MatchText.ToLower()))
{
if (base.ActualHeight != 0 && base.ActualWidth != 0)
{
int indexOf = 0;
do
{
indexOf = Text.IndexOf(rule.MatchText, indexOf);
if (indexOf == -1) break;
Rect rect = base.GetRectFromCharacterIndex(indexOf);
Rect backRect = base.GetRectFromCharacterIndex(indexOf + rule.MatchText.Length - 1, true);
this.TryAddAdorner<GenericAdorner>(new GenericAdorner(this, new Rectangle()
{ Height = rect.Height, Width = backRect.X - rect.X, Fill = rule.Brush, Opacity = 0.5 }));
indexOf++;
} while (true);
}
}
}
}
}
GenericAdorner / Helper Methods
public class GenericAdorner : Adorner
{
private readonly UIElement adorner;
private readonly Point point;
public GenericAdorner(UIElement targetElement, UIElement adorner, Point point) : base(targetElement)
{
this.adorner = adorner;
if (adorner != null)
{
AddVisualChild(adorner);
}
this.point = point;
}
protected override int VisualChildrenCount
{
get { return adorner == null ? 0 : 1; }
}
protected override Size ArrangeOverride(Size finalSize)
{
if (adorner != null)
{
adorner.Arrange(new Rect(point, adorner.DesiredSize));
}
return finalSize;
}
protected override Visual GetVisualChild(int index)
{
if (index == 0 && adorner != null)
{
return adorner;
}
return base.GetVisualChild(index);
}
}
public static void TryRemoveAdorner<T>(this UIElement element)
where T:Adorner
{
AdornerLayer layer = AdornerLayer.GetAdornerLayer(element);
if (layer != null)
layer.RemoveAdorners<T>(element);
}
public static void RemoveAdorners<T>(this AdornerLayer layer, UIElement element)
where T : Adorner
{
var adorners = layer.GetAdorners(element);
if (adorners == null) return;
for (int i = adorners.Length -1; i >= 0; i--)
{
if (adorners[i] is T)
layer.Remove(adorners[i]);
}
}
public static void TryAddAdorner<T>(this UIElement element, Adorner adorner)
where T: Adorner
{
AdornerLayer layer = AdornerLayer.GetAdornerLayer(element);
if (layer != null)
try
{
layer.Add(adorner);
}
catch (Exception) { }
}
public static bool HasAdorner<T>(this AdornerLayer layer, UIElement element) where T : Adorner
{
var adorners = layer.GetAdorners(element);
if (adorners == null) return false;
for (int i = adorners.Length - 1; i >= 0; i--)
{
if (adorners[i] is T)
return true;
}
return false;
}
public static void RemoveAdorners(this AdornerLayer layer, UIElement element)
{
var adorners = layer.GetAdorners(element);
if (adorners == null) return;
foreach (Adorner remove in adorners)
layer.Remove(remove);
}
The XAML
<local:HighlightTextBox FontFamily="Calibri" Foreground="Blue" FontSize="12" Text="Hello you!! And also hello to you!" TextWrapping="Wrap" Margin="5,3,0,0">
<local:HighlightTextBox.HighlightRules>
<local:HighlightRule Brush="Red" MatchText="you"/>
<local:HighlightRule Brush="Blue" MatchText="also"/>
</local:HighlightTextBox.HighlightRules>
</local:HighlightTextBox>
Appearance

Restoring scroll position on a GridView at the right time

I need to restore the scroll position of a GridView in my windows app. I'm trying to find the right time to call ScrollViewer.ScrollToHorizontalOffset() and have it succeed.
If I call it in the OnNavigatedTo override, it has no effect:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
DataContext = LoadModel();
RestoreScrollPos();
}
If I call it in the Loaded handler for the page, it has no effect.
private void onPageLoaded(object sender, RoutedEventArgs e)
{
DataContext = LoadModel();
RestoreScrollPos();
}
If I do something like the following, then it works but it is jarring because the GridView is first drawn at scroll position 0 and then snaps to the new scroll position.
var dontAwaitHere =
Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
delegate()
{
RestoreScrollPos();
});
If I try to repro this behavior from the default visual studio GridView project, it seems to work most of the time, but I did see it not work once. I believe there is some sort of race condition, and I suspect I am putting it in the wrong place.
QUESTION = Where should I call RestoreScrollPos() Or where should I look to debug this?
private void RestoreScrollPos()
{
var scrollViewer = findScrollViewer(itemGridView);
if (scrollViewer != null)
{
scrollViewer.ScrollToHorizontalOffset(100000.0); // TODO test
}
}
public static ScrollViewer findScrollViewer(DependencyObject el)
{
ScrollViewer retValue = findDescendant<ScrollViewer>(el);
return retValue;
}
public static tType findDescendant<tType>(DependencyObject el)
where tType : DependencyObject
{
tType retValue = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(el);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(el, i);
if (child is tType)
{
retValue = (tType)child;
break;
}
retValue = findDescendant<tType>(child);
if (retValue != null)
{
break;
}
}
return retValue;
}
You should call RestoreScrollPos only after the grid has finished loading:
public MyPageConstructor()
{
this.InitializeComponent();
this.itemGridView.Loaded += (s,e) => itemGridView_Loaded(s, e);
}
private void itemGridView_Loaded(object sender, RoutedEventArgs e)
{
RestoreScrollPos();
}
As to where to load the data, you should try in LoadState:
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
DataContext = LoadModel();
base.LoadState(navigationParameter, pageState);
}

How to include an animated gif in a DataGridView?

I have included an animated gif in a DataGridView, but the image is shown static. Is it possible?
I implemented a DataGridViewImageAnimator class that takes care of the gif images' animations for any DataGridView. Instances of DataGridViewImageAnimator monitor a given DataGridView and animate any gif image displayed in it. You may download the source code from https://docs.google.com/open?id=0B1r6und31C6BQXktM2VQN1Jza2c
You can create an animator in the form that includes a DataGridView as follows:
public partial class YourForm : Form
{
private DataGridViewImageAnimator dataGridImageAnimator;
public YourForm()
{
InitializeComponent();
dataGridImageAnimator = new DataGridViewImageAnimator(dataGridView1);
}
// ...
}
Alternatively, you can use a DataGridView-derived class that already does this in its constructor, as it's shown below for the AnimatedDataGridView class below (code available at https://docs.google.com/open?id=0B1r6und31C6BQnZUaTBjVXA4SkE)
using System.Windows.Forms;
namespace JLR.Utils
{
public class AnimatedDataGridView : DataGridView
{
private DataGridViewImageAnimator _imageAnimator;
public AnimatedDataGridView()
: base()
{
_imageAnimator = new DataGridViewImageAnimator(this);
}
}
}
The entire code for the DataGridViewImageAnimator is shown below:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
namespace JLR.Utils
{
public class DataGridViewImageAnimator
{
private class RowCol
{
public int Column { get; set; }
public int Row { get; set; }
public RowCol(int column, int row)
{
Column = column;
Row = row;
}
public override bool Equals(object obj)
{
if (obj == null)
return false;
RowCol other = obj as RowCol;
if (other == null)
return false;
return (other.Column == Column && other.Row == Row);
}
public bool Equals(RowCol other)
{
if (other == null)
return false;
return (other.Column == Column && other.Row == Row);
}
public override int GetHashCode()
{
return Column.GetHashCode() ^ Row.GetHashCode();
}
public static bool operator ==(RowCol a, RowCol b)
{
// If both are null, or both are same instance, return true.
if (object.ReferenceEquals(a, b))
{
return true;
}
// If one is null, but not both, return false.
if (((object)a == null) || ((object)b == null))
{
return false;
}
// Return true if the fields match:
return a.Column == b.Column && a.Row == b.Row;
}
public static bool operator !=(RowCol a, RowCol b)
{
return !(a == b);
}
}
private class AnimatedImage
{
private DataGridView DataGridView { get; set; }
private HashSet<RowCol> _cells = new HashSet<RowCol>();
public Image Image { get; set; }
public AnimatedImage(Image image, DataGridView dataGridView)
{
Image = image;
DataGridView = dataGridView;
}
public bool IsUsed { get { return _cells.Count > 0; } }
public void AddCell(RowCol rowCol)
{
Debug.Assert(!_cells.Contains(rowCol));
if (!_cells.Contains(rowCol))
{
_cells.Add(rowCol);
if (_cells.Count == 1)
{
// this is the first cell we are using this image, so start animation
ImageAnimator.Animate(Image, new EventHandler(OnFrameChanged));
}
}
}
public void RemoveCell(RowCol rowCol)
{
Debug.Assert(_cells.Contains(rowCol));
if (_cells.Contains(rowCol))
{
_cells.Remove(rowCol);
if (_cells.Count == 0)
{
// this was the last cell we were using this image, so stop animation
ImageAnimator.StopAnimate(Image, new EventHandler(OnFrameChanged));
}
}
}
private void OnFrameChanged(object o, EventArgs e)
{
// invalidate each cell in which it's being used
RowCol[] rcs = new RowCol[_cells.Count];
_cells.CopyTo(rcs);
foreach (RowCol rc in rcs)
{
DataGridView.InvalidateCell(rc.Column, rc.Row);
}
}
}
private Dictionary<RowCol, Image> _values = new Dictionary<RowCol, Image>();
private Dictionary<Image, AnimatedImage> _animatedImages = new Dictionary<Image, AnimatedImage>();
private DataGridView _dataGridView;
public DataGridViewImageAnimator(DataGridView dataGridView)
{
_dataGridView = dataGridView;
_dataGridView.CellPainting += new DataGridViewCellPaintingEventHandler(OnDatagridCellPainting);
}
void OnDatagridCellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if (e.ColumnIndex >= 0 && e.RowIndex >= 0)
{
object value = _dataGridView[e.ColumnIndex, e.RowIndex].Value;
CheckValue(e.ColumnIndex, e.RowIndex, value);
ImageAnimator.UpdateFrames();
}
}
private void AddImage(RowCol rowCol, Image image)
{
_values[rowCol] = image;
AnimatedImage animatedImage;
if (!_animatedImages.TryGetValue(image, out animatedImage))
{
animatedImage = new AnimatedImage(image, _dataGridView);
_animatedImages[image] = animatedImage;
}
animatedImage.AddCell(rowCol);
}
private void RemoveImage(RowCol rowCol, Image image)
{
Debug.Assert(_values.ContainsKey(rowCol));
Debug.Assert(_animatedImages.ContainsKey(image));
_values.Remove(rowCol);
AnimatedImage animatedImage;
if (_animatedImages.TryGetValue(image, out animatedImage))
{
animatedImage.RemoveCell(rowCol);
if (!animatedImage.IsUsed)
{
_animatedImages.Remove(image);
}
}
}
private void CheckValue(int columnIndex, int rowIndex, object value)
{
RowCol rowCol = new RowCol(columnIndex, rowIndex);
// is the new value an Image, and can it be animated?
Image newImage = value as Image;
bool newValueIsImage = (newImage != null && ImageAnimator.CanAnimate(newImage));
// is there a previous image value?
Image oldImage;
if (_values.TryGetValue(rowCol, out oldImage))
{
if (newImage == oldImage)
{
// same old image --> nothing else to do
return;
}
RemoveImage(rowCol, oldImage);
}
if (newValueIsImage)
{
AddImage(rowCol, newImage);
}
}
}
}
Regards,
Following the link only answer by #AseemGautam which links to same question posted in another forum with another link only answer which links to a rar file containing a complex project, it boils down to a simple answer.
Including image directly from resource will not play its animation. So we include the image in a 1x1 pixel image on same form as grid (this 1x1 image cannot hide behind other components otherwise animation wont work, hopefully that 1 pixel is transparent in gif file). Then using a timer invalidate the cell every 60ms (or smallest frame duration in gif animation) intervals. Most probably this will cause flicker on images so by adding a RowPostPaint event which draws a 1 pixel line will sync cell drawing.
void frmMain_Load(object sender, EventArgs e) {
DataGridView1.Rows.Clear();
DataGridView1.Rows.Add(PictureBox1.Image);
}
void Timer1_Tick(object sender, EventArgs e) {
if (DataGridView1.Rows.Count > 0) {
DataGridView1.Rows[0].Cells[0].Value = PictureBox1.Image;
DataGridView1.InvalidateCell(0, 0);
}
}
void DataGridView1_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e) {
e.Graphics.DrawLine(new Pen(SystemColors.ActiveBorder), 0, 0, 1, 1);
}

Categories

Resources