I have a MVVM application that has a slider bar and when the user changes the slider bar it updates a graphic on the screen and updates some plots. This all works when the user changes the position of the slider, I would like to add a 'Play' button that automatically moves the slider and everything updates. I have tried the following code to do that and when I try it nothing changes on the screen. I have confirmed that it is indeed running the code and changing the 'SliderPos' variable. What am I missing?
private void VSMPlayer()
{
SliderPos = 0;
const int speed = 1;
while (SliderPos < SliderLength)
{
Thread.Sleep(100 / speed);
SliderPos = SliderPos + 20;
}
// todo finish this function
}
For clarity's sake here is the SliderPos property
public double SliderPos
{
get
{
return this.sliderPos;
}
set
{
this.sliderPos = value;
SetCursorLocation();
SetParameters();
this.RaisePropertyChanged("SliderPos");
}
}
The class owning SliderPos needs to implement INotifyPropertyChanged. (If your Slider.Value is bound to that property)
Edit: This alone does not work, as Will correctly noted the UI-Thread is sleeping.
You could try something like this, it works:
SliderPosition = 0;
DispatcherTimer timer = null;
timer = new DispatcherTimer(TimeSpan.FromSeconds(0.1), DispatcherPriority.Render, delegate
{
SliderPosition += 20;
if (SliderPosition > 100) timer.Stop();
},
Dispatcher.CurrentDispatcher);
timer.Start();
Edit2: If you are not modifying any UI-Thread-Owned controls you can just use any thread apart from the UI-Thread, e.g.:
SliderPosition = 0;
new Thread(new ThreadStart(delegate
{
while (SliderPosition < 100)
{
Thread.Sleep(100);
SliderPosition += 20;
}
})).Start();
Look at this similar solution:
ViewModel:
public class MainVM : INotifyPropertyChanged
{
public int SliderLength
{
get
{
return Names.Count - 1;
}
}
private void VSMPlayer()
{
SliderPos = 0;
const int speed = 1;
while (SliderPos < SliderLength)
{
Thread.Sleep(100 / speed);
SliderPos = SliderPos + 1;
}
// todo finish this function
}
private bool CanVSMPlayer()
{
return Names.Count > 0;
}
public ICommand Play
{
get
{
return new RelayCommand(() =>
{
IAsyncResult result = new Action(VSMPlayer).BeginInvoke((c =>
{
//operation completed
}), null);
}, CanVSMPlayer);
}
}
public ObservableCollection<string> Names
{
get
{
return new ObservableCollection<string>() { "a", "b", "c", "d", "e", "f", "g", "h", "i" };
}
}
int _sliderPos = 0;
public int SliderPos
{
get { return _sliderPos; }
set
{
_sliderPos = value;
RaisePropertyChanged("SliderPos");
RaisePropertyChanged("ActiveName");
}
}
public string ActiveName
{
get
{
if (SliderPos < Names.Count)
{
return Names[SliderPos];
}
else
{
return Names[0];
}
}
}
void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
View:
<StackPanel>
<TextBlock Text="{Binding ActiveName}"/>
<Slider Value="{Binding SliderPos}" Maximum="{Binding SliderLength}"/>
<Button Content="Play" Command="{Binding Play}"/>
</StackPanel>
Related
I'm currently working on a kind of VirtualizedWrapPanel to use as the ItemsPanel in a ListView.
After following this guy's instructions, and borrowing heavily from this guy's implementation found on codeproject but I don't have the reputation to post the link so sorry..., I have something that is nicely shaping up to be exactly what I need.
The item size is fixed so the scrolling is pixel based. the orientation is always horizontal.
the ListView :
<ListView Name="lv"
ItemsSource="{Binding CV}"
IsSynchronizedWithCurrentItem="True"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<i:Interaction.Behaviors>
<local:ScrollToSelectionListViewBehavior/> <!-- Behavior calling ScrollIntoView whenever the selection changes -->
<local:ListViewSelectedItemsBehavior SelectedItems="{Binding SelectedItems}"/> <!-- Behavior exposing the attached ListView's SelectedItems array -->
</i:Interaction.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Height="100" Width="100" Orientation="Vertical">
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="5" Width="90" Height="90">
<TextBlock Text ="{Binding ItemText}" FontWeight="Bold" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Border>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<local:VirtualizingWrapPanel ItemHeight="100" ItemWidth="110" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
the local:VirtualizingWrapPanel :
public class VirtualizingWrapPanel : VirtualizingPanel, IScrollInfo
{
private ScrollViewer _owner;
private const bool _canHScroll = false;
private bool _canVScroll = false;
private Size _extent = new Size(0, 0);
private Size _viewport = new Size(0, 0);
private Point _offset;
UIElementCollection _children;
ItemsControl _itemsControl;
IItemContainerGenerator _generator;
Dictionary<UIElement, Rect> _realizedChildLayout = new Dictionary<UIElement, Rect>();
#region Properties
private Size ChildSlotSize
{
get { return new Size(ItemWidth, ItemHeight); }
}
#endregion
#region Dependency Properties
[TypeConverter(typeof(LengthConverter))]
public double ItemHeight
{
get { return (double)base.GetValue(ItemHeightProperty); }
set { base.SetValue(ItemHeightProperty, value); }
}
[TypeConverter(typeof(LengthConverter))]
public double ItemWidth
{
get { return (double)base.GetValue(ItemWidthProperty); }
set { base.SetValue(ItemWidthProperty, value); }
}
public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register("ItemHeight", typeof(double), typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(double.PositiveInfinity));
public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register("ItemWidth", typeof(double), typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(double.PositiveInfinity));
private int LineCapacity
{ get { return Math.Max((_viewport.Width != 0) ? (int)(_viewport.Width / ItemWidth) : 0, 1); } }
private int LinesCount
{ get { return (ItemsCount > 0) ? ItemsCount / LineCapacity : 0 ; } }
private int ItemsCount
{ get { return _itemsControl.Items.Count; } }
public int FirstVisibleLine
{ get { return (int)(_offset.Y / ItemHeight); } }
public int FirstVisibleItemVPos
{ get { return (int)((FirstVisibleLine * ItemHeight) - _offset.Y); } }
public int FirstVisibleIndex
{ get { return (FirstVisibleLine * LineCapacity); } }
#endregion
#region VirtualizingPanel overrides
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
_itemsControl = ItemsControl.GetItemsOwner(this);
_children = InternalChildren;
_generator = ItemContainerGenerator;
this.SizeChanged += new SizeChangedEventHandler(this.Resizing);
}
protected override Size MeasureOverride(Size availableSize)
{
if (_itemsControl == null || _itemsControl.Items.Count == 0)
return availableSize;
if (availableSize != _viewport)
{
_viewport = availableSize;
if (_owner != null)
_owner.InvalidateScrollInfo();
}
Size childSize = new Size(ItemWidth, ItemHeight);
Size extent = new Size(availableSize.Width, LinesCount * ItemHeight);
if (extent != _extent)
{
_extent = extent;
if (_owner != null)
_owner.InvalidateScrollInfo();
}
foreach (UIElement child in this.InternalChildren)
{
child.Measure(childSize);
}
_realizedChildLayout.Clear();
Size realizedFrameSize = availableSize;
int firstVisibleIndex = FirstVisibleIndex;
GeneratorPosition startPos = _generator.GeneratorPositionFromIndex(firstVisibleIndex);
int childIndex = (startPos.Offset == 0) ? startPos.Index : startPos.Index + 1;
int current = firstVisibleIndex;
using (_generator.StartAt(startPos, GeneratorDirection.Forward, true))
{
bool stop = false;
double currentX = 0;
double currentY = FirstVisibleItemVPos;
while (current < ItemsCount)
{
bool newlyRealized;
// Get or create the child
UIElement child = _generator.GenerateNext(out newlyRealized) as UIElement;
if (newlyRealized)
{
// Figure out if we need to insert the child at the end or somewhere in the middle
if (childIndex >= _children.Count)
{
base.AddInternalChild(child);
}
else
{
base.InsertInternalChild(childIndex, child);
}
_generator.PrepareItemContainer(child);
child.Measure(ChildSlotSize);
}
else
{
// The child has already been created, let's be sure it's in the right spot
Debug.Assert(child == _children[childIndex], "Wrong child was generated");
}
childSize = child.DesiredSize;
Rect childRect = new Rect(new Point(currentX, currentY), childSize);
if (childRect.Right > realizedFrameSize.Width) //wrap to a new line
{
currentY = currentY + ItemHeight;
currentX = 0;
childRect.X = currentX;
childRect.Y = currentY;
}
if (currentY > realizedFrameSize.Height)
stop = true;
currentX = childRect.Right;
_realizedChildLayout.Add(child, childRect);
if (stop)
break;
current++;
childIndex++;
}
}
CleanUpItems(firstVisibleIndex, current - 1);
return availableSize;
}
public void CleanUpItems(int minDesiredGenerated, int maxDesiredGenerated)
{
for (int i = _children.Count - 1; i >= 0; i--)
{
GeneratorPosition childGeneratorPos = new GeneratorPosition(i, 0);
int itemIndex = _generator.IndexFromGeneratorPosition(childGeneratorPos);
if (itemIndex < minDesiredGenerated || itemIndex > maxDesiredGenerated)
{
//var c = _children[i] as ListViewItem;
//if(c!= null && c.IsSelected)
//{
//}
_generator.Remove(childGeneratorPos, 1);
RemoveInternalChildRange(i, 1);
}
}
}
protected override Size ArrangeOverride(Size finalSize)
{
if (finalSize != _viewport)
{
_viewport = finalSize;
if (_owner != null)
_owner.InvalidateScrollInfo();
}
Size childSize = new Size(ItemWidth, ItemHeight);
Size extent = new Size(finalSize.Width, LinesCount * ItemHeight);
if (extent != _extent)
{
_extent = extent;
if (_owner != null)
_owner.InvalidateScrollInfo();
}
if (_children != null)
{
foreach (UIElement child in _children)
{
var layoutInfo = _realizedChildLayout[child];
child.Arrange(layoutInfo);
}
}
return finalSize;
}
protected override void BringIndexIntoView(int index)
{
SetVerticalOffset((index / LineCapacity) * ItemHeight);
}
protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
{
base.OnItemsChanged(sender, args);
_offset.X = 0;
_offset.Y = 0;
switch (args.Action)
{
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Replace:
RemoveInternalChildRange(args.Position.Index, args.ItemUICount);
break;
case NotifyCollectionChangedAction.Move:
RemoveInternalChildRange(args.OldPosition.Index, args.ItemUICount);
break;
}
}
#endregion
#region EventHandlers
public void Resizing(object sender, EventArgs e)
{
var args = e as SizeChangedEventArgs;
if(args.WidthChanged)
{
int lineCapacity = LineCapacity;
int previousLineCapacity = (int)(args.PreviousSize.Width / ItemWidth);
if (previousLineCapacity != lineCapacity)
{
int previousFirstItem = ((int)(_offset.Y / ItemHeight) <= 0) ? 0 : ((int)(_offset.Y / ItemHeight) * previousLineCapacity);
BringIndexIntoView(previousFirstItem);
}
}
if (_viewport.Width != 0)
{
MeasureOverride(_viewport);
}
}
#endregion
#region IScrollInfo Implementation
public ScrollViewer ScrollOwner
{
get { return _owner; }
set { _owner = value; }
}
public bool CanHorizontallyScroll
{
get { return false; }
set { if (value == true) throw (new ArgumentException("VirtualizingWrapPanel does not support Horizontal scrolling")); }
}
public bool CanVerticallyScroll
{
get { return _canVScroll; }
set { _canVScroll = value; }
}
public double ExtentHeight
{
get { return _extent.Height;}
}
public double ExtentWidth
{
get { return _extent.Width; }
}
public double HorizontalOffset
{
get { return _offset.X; }
}
public double VerticalOffset
{
get { return _offset.Y; }
}
public double ViewportHeight
{
get { return _viewport.Height; }
}
public double ViewportWidth
{
get { return _viewport.Width; }
}
public Rect MakeVisible(Visual visual, Rect rectangle)
{
var gen = (ItemContainerGenerator)_generator.GetItemContainerGeneratorForPanel(this);
var element = (UIElement)visual;
int itemIndex = gen.IndexFromContainer(element);
while (itemIndex == -1)
{
element = (UIElement)VisualTreeHelper.GetParent(element);
itemIndex = gen.IndexFromContainer(element);
}
Rect elementRect = _realizedChildLayout[element];
if (elementRect.Bottom > ViewportHeight)
{
double translation = elementRect.Bottom - ViewportHeight;
_offset.Y += translation;
}
else if (elementRect.Top < 0)
{
double translation = elementRect.Top;
_offset.Y += translation;
}
InvalidateMeasure();
return elementRect;
}
public void LineDown()
{
SetVerticalOffset(VerticalOffset + 50);
}
public void LineUp()
{
SetVerticalOffset(VerticalOffset - 50);
}
public void MouseWheelDown()
{
SetVerticalOffset(VerticalOffset + 50);
}
public void MouseWheelUp()
{
SetVerticalOffset(VerticalOffset - 50);
}
public void PageDown()
{
int fullyVisibleLines = (int)(_viewport.Height / ItemHeight);
SetVerticalOffset(VerticalOffset + (fullyVisibleLines * ItemHeight));
}
public void PageUp()
{
int fullyVisibleLines = (int)(_viewport.Height / ItemHeight);
SetVerticalOffset(VerticalOffset - (fullyVisibleLines * ItemHeight));
}
public void SetVerticalOffset(double offset)
{
if (offset < 0 || _viewport.Height >= _extent.Height)
{
offset = 0;
}
else
{
if (offset + _viewport.Height >= _extent.Height)
{
offset = _extent.Height - _viewport.Height;
}
}
_offset.Y = offset;
if (_owner != null)
_owner.InvalidateScrollInfo();
InvalidateMeasure();
}
public void LineLeft() { throw new NotImplementedException(); }
public void LineRight() { throw new NotImplementedException(); }
public void MouseWheelLeft() { throw new NotImplementedException(); }
public void MouseWheelRight() { throw new NotImplementedException(); }
public void PageLeft() { throw new NotImplementedException(); }
public void PageRight() { throw new NotImplementedException(); }
public void SetHorizontalOffset(double offset) { throw new NotImplementedException(); }
#endregion
#region methods
#endregion
}
Now my problem is : An Item Selection should always Deselect the previously selected item, when using a normal WrapPanel, the previously selected ListViewItem's IsSelected property is always set to false before the new selected ListViewItem's IsSelected is set to true.
This deselection does not happen with my VirtualizingPanel when the previously selected item is no longer realized (when it is not visible in the viewport), so I end up with two or more selected items at once and the panel's behavior becomes really weird. Sometimes it even settles into an infinite loop, the two selected items yanking visibility from each other in a never ending battle.
I searched a bit for a solution to this problem but I don't really know where to start or what to search for.
Here is a test project if you want to experiment with it.
Thanks
I found a way by preventing the virtualization of selected items. Now the control behaves correctly.
public void CleanUpItems(int minDesiredGenerated, int maxDesiredGenerated)
{
for (int i = _children.Count - 1; i >= 0; i--)
{
GeneratorPosition childGeneratorPos = new GeneratorPosition(i, 0);
int itemIndex = _generator.IndexFromGeneratorPosition(childGeneratorPos);
if (itemIndex < minDesiredGenerated || itemIndex > maxDesiredGenerated)
{
//I don't much like this cast
// how do I access the IsSelectedProperty
// of a UIElement of any type ( ListBoxItem, TreeViewItem...)?
var c = _children[i] as ListViewItem;
c.IsEnabled = false;
if (c != null && c.IsSelected)
{
var layoutInfo = new Rect(0, 0, 0, 0);
c.Arrange(layoutInfo);
}
else
{
_generator.Remove(childGeneratorPos, 1);
RemoveInternalChildRange(i, 1);
}
}
}
}
In ArrangeOverride :
if (_children != null)
{
foreach (UIElement child in _children)
{
if (child.IsEnabled)
{
var layoutInfo = _realizedChildLayout[child];
child.Arrange(layoutInfo);
}
}
}
In MeasureOverride:
while (current < ItemsCount)
{
bool newlyRealized;
// Get or create the child
UIElement child = _generator.GenerateNext(out newlyRealized) as UIElement;
child.IsEnabled = true;
I'm still curious to know if there's a better way. In the meantime this will do.
btw I'll update the test project with this fix in case anyone wants a virtualizing wrappanel.
I make a following sample to attempt to dynamic update my UI while long lasting operation running, but the UI hang while it runs.
Here is my entity class:
public class ClsTest : INotifyPropertyChanged
{
private string _text;
public string Text
{
get { return _text; }
set
{
if (string.IsNullOrEmpty(value) && value == _text)
return;
_text = value;
NotifyPropertyChanged(() => Text);
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged<T>(Expression<Func<T>> property)
{
if (PropertyChanged == null)
return;
var memberExpression = property.Body as MemberExpression;
if (memberExpression == null)
return;
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(memberExpression.Member.Name));
}
}
And here is my binding code in my main form_load
ClsTest x = new ClsTest();
x.Text = "yes";
txtValue.DataBindings.Add("Text", x, "Text", false, DataSourceUpdateMode.Never);
When I run following code in a button click method, it hangs:
for (int i = 0; i < 20; i++)
{
x.Text = i.ToString();
System.Threading.Thread.Sleep(1000);
}
How can I resolve this problem?
I'm writing a user control for animation.
Its uses an internal ImageList to store the animation images and paint one after the other in a loop.
This is the whole code:
public partial class Animator : UserControl
{
public event EventHandler OnLoopElapsed = delegate { };
private ImageList imageList = new ImageList();
private Timer timer;
private bool looping = true;
private int index;
private Image newImage;
private Image oldImage;
public Animator()
{
InitializeComponent();
base.DoubleBuffered = true;
timer = new Timer();
timer.Tick += timer_Tick;
timer.Interval = 50;
}
public bool Animate
{
get { return timer.Enabled; }
set
{
index = 0;
timer.Enabled = value;
}
}
public int CurrentIndex
{
get { return index; }
set { index = value; }
}
public ImageList ImageList
{
set
{
imageList = value;
Invalidate();
index = 0;
}
get { return imageList; }
}
public bool Looping
{
get { return looping; }
set { looping = value; }
}
public int Interval
{
get { return timer.Interval; }
set { timer.Interval = value; }
}
private void timer_Tick(object sender, EventArgs e)
{
if (imageList.Images.Count == 0)
return;
Invalidate(true);
index++;
if (index >= imageList.Images.Count)
{
if (looping)
index = 0;
else
timer.Stop();
OnLoopElapsed(this, EventArgs.Empty);
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
if (oldImage != null)
e.Graphics.DrawImage(oldImage, ClientRectangle);
else
e.Graphics.Clear(BackColor);
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
if (imageList.Images.Count > 0)
{
newImage = imageList.Images[index];
g.DrawImage(newImage, ClientRectangle);
oldImage = newImage;
}
else
{
e.Graphics.Clear(BackColor);
}
}
}
The animation seems very nice and smooth,
but the problem is that its surrounding rectangle is painted black.
What am I missing here?
I've seen very smooth transparent animation done here in WPF,
I've placed some label behind it and they are seen thru the rotating wheel as I hoped.
But I don't know WPF well enough to build such a control in WPF.
Any idea or WPF sample code will be appreciated.
This was solved by removing this line from the constructor:
base.DoubleBuffered = true;
Now the control is fully transparent, even while changing its images.
I need to show Splash Screen with Image & progress bar.
In my application start up i have the code as below to show the main window.
SplashScreenWindowViewModel vm = new SplashScreenWindowViewModel();
AutoResetEvent ev = new AutoResetEvent(false);
Thread uiThread = new Thread(() =>
{
vm.Dispatcher = Dispatcher.CurrentDispatcher;
ev.Set();
Dispatcher.CurrentDispatcher.BeginInvoke((Action)delegate()
{
SplashScreenWindow splashScreenWindow = new SplashScreenWindow();
splashScreenWindow = new SplashScreenWindow();
splashScreenWindow.Show();
splashScreenWindow.DataContext = vm;
vm.InstigateWorkCommand.Execute(null);
});
Dispatcher.Run();
});
uiThread.SetApartmentState(ApartmentState.STA);
uiThread.IsBackground = true;
uiThread.Start();
ev.WaitOne();
In my main viewmodel i have code as below
class MainviewModel : viewmodelbase
{
rivate string _message;
private object content;
private readonly BackgroundWorker worker;
private readonly ICommand instigateWorkCommand;
public SplashScreenWindowViewModel()
{
this.instigateWorkCommand = new
RelayCommand(() => this.worker.RunWorkerAsync(), () => !this.worker.IsBusy);
this.worker = new BackgroundWorker { WorkerReportsProgress = true };
this.worker.DoWork += this.DoWork;
this.worker.ProgressChanged += this.ProgressChanged;
_message = "0 % completed";
}
public ICommand InstigateWorkCommand
{
get { return this.instigateWorkCommand; }
}
private double _currentProgress;
public double CurrentProgress
{
get { return this._currentProgress; }
set
{
if (this._currentProgress != value)
{
this._currentProgress = value;
RaisePropertyChanged("CurrentProgress");
}
}
}
private int _progressMax;
public int ProgressMax
{
get { return this._progressMax; }
set
{
if(this._progressMax != value)
{
this._progressMax = value;
RaisePropertyChanged("ProgressMax");
}
}
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.CurrentProgress = e.ProgressPercentage;
}
private void DoWork(object sender, DoWorkEventArgs e)
{
// calling my long running operation
DAL.dotimeconsumingcode();
worker.ReportProgress((int)e.argument);
}
public string Message
{
get
{
return _message;
}
set
{
if (Message == value) return;
_message = value;
RaisePropertyChanged("Message");
}
}
public object Content
{
get
{
return content;
}
set
{
if (Content == value) return;
content = value;
RaisePropertyChanged("Content");
}
}
public Dispatcher Dispatcher
{
get;
set;
}
}
MY UI has one user control with progress bar and one splash main window.
when my long running operation is completed , my Main window(main application) is opened.
//User Control
<ProgressBar Height="27" Value="{Binding CurrentProgress, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" Margin="53,162,57,0" Name="progressBar" Grid.Row="1"
Maximum="{Binding Path=ProgressMax, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding ProgressVisibility}" />
//SplashWindow
<localView:usercontrol/>
My Problem is
ProgressChangedevent is not firing and % completion is not showing up in the text block either. Please help
You have not registered a complete handler and you are not calling progress properly.
This sample from MSDN covers it all.
BackGroundWorker
CompositeTransform is only used for silverlight?. Is there anyway we can use that in WPF or any equivalent replacement?
There is no CompositeTransform in WPF however there is a TransformGroup. Hence an equivalent replacement is a TransformGroup containing ScaleTransform, SkewTransform, RotateTransform and TranslateTransform in that order.
Here is a much nicer solution if you are anal about code cleanliness:
http://www.singulink.com/CodeIndex/post/getting-rid-of-ugly-transformgroup-blocks-in-wpf
Its easy on the eyes and because it just returns a TransformGroup, you can still use the Blend designer to work with animating over the transform!
<Rectangle Width="100" Height="100" Fill="LightGreen"
RenderTransform="{data:CompositeTransform ScaleX=2.5, ScaleY=1, SkewX=-60, Rotation=145}"
RenderTransformOrigin="0.5,0.5" />
Implementation:
public class CompositeTransformExtension : MarkupExtension
{
public double CenterX
{
get { return _scale.CenterX; }
set
{
_scale.CenterX = value;
_skew.CenterX = value;
_rotate.CenterX = value;
}
}
public double CenterY
{
get { return _scale.CenterY; }
set
{
_scale.CenterY = value;
_skew.CenterY = value;
_rotate.CenterY = value;
}
}
public double ScaleX
{
get { return _scale.ScaleX; }
set { _scale.ScaleX = value; }
}
public double ScaleY
{
get { return _scale.ScaleY; }
set { _scale.ScaleY = value; }
}
public double SkewX
{
get { return _skew.AngleX; }
set { _skew.AngleX = value; }
}
public double SkewY
{
get { return _skew.AngleY; }
set { _skew.AngleY = value; }
}
public double Rotation
{
get { return _rotate.Angle; }
set { _rotate.Angle = value; }
}
public double TranslateX
{
get { return _translate.X; }
set { _translate.X = value; }
}
public double TranslateY
{
get { return _translate.Y; }
set { _translate.Y = value; }
}
private ScaleTransform _scale = new ScaleTransform();
private SkewTransform _skew = new SkewTransform();
private RotateTransform _rotate = new RotateTransform();
private TranslateTransform _translate = new TranslateTransform();
public CompositeTransformExtension()
{
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var group = new TransformGroup();
group.Children.Add(_scale);
group.Children.Add(_skew);
group.Children.Add(_rotate);
group.Children.Add(_translate);
return group;
}
}