I want to set the image position via code to fit on the button (see screenshot). But I can't work it out. In WinForms it was easy, but in Silverlight I can't just set the X & Y apparently.
public void LockControls()
{
int LockIndex = 0;
DependencyObject myUserControl = LayoutRoot;
foreach (var button in FindAll<Button>(myUserControl))
{
if (button.Tag != null)
{
Image LockedIcon = new Image();
LockedIcon.Width = 20;
LockedIcon.Height = 20;
//LockedIcon.Margin = new Thickness(0,0,0,0);
LockedIcon.Source = new BitmapImage(new Uri("images/LockedIconx20alpha.png", UriKind.Relative));
LockedIcon.Name = "Lockie" + LockIndex;
LayoutRoot.Children.Add(LockedIcon);
button.Tag = "Locked" + LockIndex;
LockIndex++;
}
}
}
http://puu.sh/wS7g
THe screenshot shows the image position (the locck), but I don't understand how the current position is being set. Just to clarify, I want to set the position to the "0%" button
Thanks in advance,
Jack
That's not the correct way of doing. If I understand correctly you want to super-impose an image on top of a button to prevent the user from using it. It won't work.
For that you have to understand the layout system of Silverlight: the controls are laid out by the engine during the measure and arrange events.
Trying to overlay an image like that will require you to hook up on those events, or derive the Button class and override the Arrange method to overlay your image.
But that won't prevent the user from using the button because the button itself is not disabled, and one could just "tab" into it, and activate it.
Instead I suggest you use a style for the button, and override, say, the Disabled state to overlay your locked image. The button style is described here.
All you have to do is replace:
<Rectangle x:Name="DisabledVisualElement" RadiusX="3" RadiusY="3" Fill="#FFFFFFFF" Opacity="0" IsHitTestVisible="false" />
by
<Image x:Name="DisabledVisualElement" IsHitTestVisible="false" Opacity="0" Width="20" Height="20" Source="images/LockedIconx20alpha.png" />
And set the opacity in the following to 1:
<vsm:VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="DisabledVisualElement" Storyboard.TargetProperty="Opacity" To=".55"/>
</Storyboard>
</vsm:VisualState>
Set this style on your button (I assume you know how to do this) then when you need to lock your button, set it to disabled and your image will be automatically laid on-top and your button will be un-clickable.
In general, if you want to set the Image position arbitrary, you should host the Image control in a Canvas container.
However, in your case you should really change the Content element of your Button depending on the need for showing the lock or not.
<UserControl DataContext="{Binding Main, Source={StaticResource Locator}}
<Grid.Resources>
<converters:VisibilityConverter x:Key="VisibilityConverter" />
</Grid.Resources>
<Button Width="100" Height="23" IsEnabled="{Binding IsControlsEnabled}">
<Button.Content>
<StackPanel Orientation="Horizontal">
<Image Source="lock.png" Margin="10,0,10,0"
Visibility="{Binding IsControlsEnabled, Converter={StaticResource VisibilityConverter}}"/>
<TextBlock Text="Button"/>
</StackPanel>
</Button.Content>
</Button>
Additionally, you shouldn't really write the kind of code you have in your question in Silverlight. Learn how to use data binding. It's very powerful. Simply bind the IsEnabled property of your Buttons to an exposed Property instead.
An example of doing so using the MVVM Light toolkit (I recommend you learn the MVVM pattern for Silverlight/WPF development):
The View Model:
public class MainViewModel : ViewModelBase
{
private bool isControlsEnabled;
public bool IsControlsEnabled
{
get { return isControlsEnabled; }
set
{
if (IsControlsEnabled.Equals(value)) return;
isControlsEnabled = value;
RaisePropertyChanged(() => IsControlsEnabled);
}
}
}
The Visibility Converter:
public class VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (bool)value ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
And then you can simply enable/disable all controls that are bound, for example using a CheckBox:
<CheckBox IsChecked="{Binding IsControlsEnabled, Mode=TwoWay}" Content="Controls are enabled"/>
Related
i am using wpf tab control and setting the Icon and text in the tab header through style, i am able to set the text dynamically through getter and setter but can not able to set the image source. I tried to bind the image source through getter and setter but failed. Following is the style code in which i want to set the image source dynamically from the code behind.
-->
<Setter Property="HeaderTemplate" >
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!--<Image gif:ImageBehavior.AnimatedSource="{DynamicResource MyFillBrush}" Width="20" />-->
<Image gif:ImageBehavior.AnimatedSource="25.gif" Width="20" />
<Label Content="{Binding }"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
I know this is a bit old thread, but this is how you update the AnimatedSource from code:
var image = new BitmapImage();
image.BeginInit();
image.UriSource = new Uri(value);
image.EndInit();
ImageBehavior.SetAnimatedSource(Imagereference, image);
The 'value' is the location of your gif file such as "25.gif".
The Imagereference is the Image control you want to update. Also ensure you have the namespace referenced: using WpfAnimatedGif;
Maybe you can use Binding, something like
private string _dynamicGif = "test.gif";
public string DynamicGif
{
get { return _dynamicGif; }
private set
{
_dynamicGif = value;
RaisePropertyChanged("DynamicGif");
}
}
and
<Image gif:ImageBehavior.AnimatedSource="{Binding DynamicGif, UpdateSourceTrigger=PropertyChanged}" Width="20" />
Or, if it doesn't work, you can use MediaElement instead of Image, it won't require WPF Animated GIF. It's also pretty simple to use:
private Uri _imgSource = new Uri("test.gif");
public Uri ImgSource
{
get { return _imgSource; }
private set
{
_imgSource = value;
RaisePropertyChanged("ImgSource");
}
}
and
<MediaElement x:Name="gifImg" LoadedBehavior="Play" Width="20" Source="{Binding ImgSource}"/>
Hope this helps.
I am trying to convert a System.Windows.Shapes.Shape object into a System.Windows.Media.Geometry object.
With the Geometry object, I am going to render it multiple times with a custom graph control depending on a set of data points. This requires that each instance of the Geometry object has a unique TranslateTransform object.
Now, I am approaching the issue in two different ways, but neither seems to be working correctly. My custom control uses the following code in order to draw the geometry:
//Create an instance of the geometry the shape uses.
Geometry geo = DataPointShape.RenderedGeometry.Clone();
//Apply transformation.
TranslateTransform translation = new TranslateTransform(dataPoint.X, dataPoint.Y);
geo.Transform = translation;
//Create pen and draw geometry.
Pen shapePen = new Pen(DataPointShape.Stroke, DataPointShape.StrokeThickness);
dc.DrawGeometry(DataPointShape.Fill, shapePen, geo);
I have also tried the following alternate code:
//Create an instance of the geometry the shape uses.
Geometry geo = DataPointShape.RenderedGeometry;
//Apply transformation.
TranslateTransform translation = new TranslateTransform(dataPoint.X, dataPoint.Y);
dc.PushTransform(translation);
//Create pen and draw geometry.
Pen shapePen = new Pen(DataPointShape.Stroke, DataPointShape.StrokeThickness);
dc.DrawGeometry(DataPointShape.Fill, shapePen, geo);
dc.Pop(); //Undo translation.
The difference is that the second snippet doesn't clone or modify the Shape.RenderedGeometry property.
Oddly enough, I occasionally can view the geometry used for the data points in the WPF designer. However, the behavior is inconsistent and difficult to figure out how to make the geometry always appear. Also, when I execute my application, the data points never appear with the specified geometry.
EDIT: I have figured out how to generate the appearance of the geometry. But this only works in design-mode. Execute these steps:
Rebuild project.
Go to MainWindow.xaml and click in the custom shape object so that the shape's properties load into Visual Studio's property window. Wait until the property window renders what the shape looks like.
Modify the data points collection or properties to see the geometry rendered properly.
Here is what I want the control to ultimately look like for now:
How can I convert a Shape object to a Geometry object for rendering multiple times?
Your help is tremendously appreciated!
Let me give the full context of my problem, as well as all necessary code to understanding how my control is set up. Hopefully, this might indicate what problems exist in my method of converting the Shape object to a Geometry object.
MainWindow.xaml
<Window x:Class="CustomControls.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomControls">
<Grid>
<local:LineGraph>
<local:LineGraph.DataPointShape>
<Ellipse Width="10" Height="10" Fill="Red" Stroke="Black" StrokeThickness="1" />
</local:LineGraph.DataPointShape>
<local:LineGraph.DataPoints>
<local:DataPoint X="10" Y="10"/>
<local:DataPoint X="20" Y="20"/>
<local:DataPoint X="30" Y="30"/>
<local:DataPoint X="40" Y="40"/>
</local:LineGraph.DataPoints>
</local:LineGraph>
</Grid>
DataPoint.cs
This class just has two DependencyProperties (X & Y) and it gives a notification when any of those properties are changed. This notification is used to trigger a re-render via UIElement.InvalidateVisual().
public class DataPoint : DependencyObject, INotifyPropertyChanged
{
public static readonly DependencyProperty XProperty = DependencyProperty.Register("XProperty", typeof(double), typeof(DataPoint), new FrameworkPropertyMetadata(0.0d, DataPoint_PropertyChanged));
public static readonly DependencyProperty YProperty = DependencyProperty.Register("YProperty", typeof(double), typeof(DataPoint), new FrameworkPropertyMetadata(0.0d, DataPoint_PropertyChanged));
private static void DataPoint_PropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
DataPoint dp = (DataPoint)sender;
dp.RaisePropertyChanged(e.Property.Name);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public double X
{
get { return (double)GetValue(XProperty); }
set { SetValue(XProperty, (double)value); }
}
public double Y
{
get { return (double)GetValue(YProperty); }
set { SetValue(YProperty, (double)value); }
}
}
LineGraph.cs
This is the control. It contains the collection of data points and provides mechanisms for re-rendering the data points (useful for WPF designer). Of particular importance is the logic posted above which is inside of the UIElement.OnRender() method.
public class LineGraph : FrameworkElement
{
public static readonly DependencyProperty DataPointShapeProperty = DependencyProperty.Register("DataPointShapeProperty", typeof(Shape), typeof(LineGraph), new FrameworkPropertyMetadata(default(Shape), FrameworkPropertyMetadataOptions.AffectsRender, DataPointShapeChanged));
public static readonly DependencyProperty DataPointsProperty = DependencyProperty.Register("DataPointsProperty", typeof(ObservableCollection<DataPoint>), typeof(LineGraph), new FrameworkPropertyMetadata(default(ObservableCollection<DataPoint>), FrameworkPropertyMetadataOptions.AffectsRender, DataPointsChanged));
private static void DataPointShapeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
LineGraph g = (LineGraph)sender;
g.InvalidateVisual();
}
private static void DataPointsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{ //Collection referenced set or unset.
LineGraph g = (LineGraph)sender;
INotifyCollectionChanged oldValue = e.OldValue as INotifyCollectionChanged;
INotifyCollectionChanged newValue = e.NewValue as INotifyCollectionChanged;
if (oldValue != null)
oldValue.CollectionChanged -= g.DataPoints_CollectionChanged;
if (newValue != null)
newValue.CollectionChanged += g.DataPoints_CollectionChanged;
//Update the point visuals.
g.InvalidateVisual();
}
private void DataPoints_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{ //Collection changed (added/removed from).
if (e.OldItems != null)
foreach (INotifyPropertyChanged n in e.OldItems)
{
n.PropertyChanged -= DataPoint_PropertyChanged;
}
if (e.NewItems != null)
foreach (INotifyPropertyChanged n in e.NewItems)
{
n.PropertyChanged += DataPoint_PropertyChanged;
}
InvalidateVisual();
}
private void DataPoint_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//Re-render the LineGraph when a DataPoint has a property that changes.
InvalidateVisual();
}
public Shape DataPointShape
{
get { return (Shape)GetValue(DataPointShapeProperty); }
set { SetValue(DataPointShapeProperty, (Shape)value); }
}
public ObservableCollection<DataPoint> DataPoints
{
get { return (ObservableCollection<DataPoint>)GetValue(DataPointsProperty); }
set { SetValue(DataPointsProperty, (ObservableCollection<DataPoint>)value); }
}
public LineGraph()
{ //Provide instance-specific value for data point collection instead of a shared static instance.
SetCurrentValue(DataPointsProperty, new ObservableCollection<DataPoint>());
}
protected override void OnRender(DrawingContext dc)
{
if (DataPointShape != null)
{
Pen shapePen = new Pen(DataPointShape.Stroke, DataPointShape.StrokeThickness);
foreach (DataPoint dp in DataPoints)
{
Geometry geo = DataPointShape.RenderedGeometry.Clone();
TranslateTransform translation = new TranslateTransform(dp.X, dp.Y);
geo.Transform = translation;
dc.DrawGeometry(DataPointShape.Fill, shapePen, geo);
}
}
}
}
EDIT 2:In response to this answer by Peter Duniho, I would like to provide the alternate method to lying to Visual Studio in creating a custom control. For creating the custom control execute these steps:
Create folder in root of project named Themes
Create resource dictionary in Themes folder named Generic.xaml
Create a style in the resource dictionary for the control.
Apply the style from the control's C# code.
Generic.xamlHere is an example of for the SimpleGraph described by Peter.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomControls">
<Style TargetType="local:SimpleGraph" BasedOn="{StaticResource {x:Type ItemsControl}}">
<Style.Resources>
<EllipseGeometry x:Key="defaultGraphGeometry" Center="5,5" RadiusX="5" RadiusY="5"/>
</Style.Resources>
<Style.Setters>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type local:DataPoint}">
<Path Fill="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SimpleGraph}}, Path=DataPointFill}"
Stroke="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SimpleGraph}}, Path=DataPointStroke}"
StrokeThickness="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SimpleGraph}}, Path=DataPointStrokeThickness}"
Data="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SimpleGraph}}, Path=DataPointGeometry}">
<Path.RenderTransform>
<TranslateTransform X="{Binding X}" Y="{Binding Y}"/>
</Path.RenderTransform>
</Path>
</DataTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</ResourceDictionary>
Lastly, apply the style like so in the SimpleGraph constructor:
public SimpleGraph()
{
DefaultStyleKey = typeof(SimpleGraph);
DataPointGeometry = (Geometry)FindResource("defaultGraphGeometry");
}
I think that you are probably not approaching this in the best way. Based on the code you posted, it seems that you are trying to do manually things that WPF is reasonably good at handling automatically.
The main tricky part (at least for me…I'm hardly a WPF expert) is that you appear to want to use an actual Shape object as the template for your graph's data point graphics, and I'm not entirely sure of the best way to allow for that template to be replaced programmatically or declaratively without exposing the underlying transformation mechanic that controls the positioning on the graph.
So here's an example that ignores that particular aspect (I will comment on alternatives below), but which I believe otherwise serves your precise needs.
First, I create a custom ItemsControl class (in Visual Studio, I do this by lying and telling VS I want to add a UserControl, which gets me a XAML-based item in the project…I immediately replace "UserControl" with "ItemsControl" in both the .xaml and .xaml.cs files):
XAML:
<ItemsControl x:Class="TestSO28332278SimpleGraphControl.SimpleGraph"
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:TestSO28332278SimpleGraphControl"
mc:Ignorable="d"
x:Name="root"
d:DesignHeight="300" d:DesignWidth="300">
<ItemsControl.Resources>
<EllipseGeometry x:Key="defaultGraphGeometry" Center="5,5" RadiusX="5" RadiusY="5" />
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:DataPoint}">
<Path Data="{Binding ElementName=root, Path=DataPointGeometry}"
Fill="Red" Stroke="Black" StrokeThickness="1">
<Path.RenderTransform>
<TranslateTransform X="{Binding X}" Y="{Binding Y}"/>
</Path.RenderTransform>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
C#:
public partial class SimpleGraph : ItemsControl
{
public Geometry DataPointGeometry
{
get { return (Geometry)GetValue(DataPointShapeProperty); }
set { SetValue(DataPointShapeProperty, value); }
}
public static DependencyProperty DataPointShapeProperty = DependencyProperty.Register(
"DataPointGeometry", typeof(Geometry), typeof(SimpleGraph));
public SimpleGraph()
{
InitializeComponent();
DataPointGeometry = (Geometry)FindResource("defaultGraphGeometry");
}
}
The key here is that I have an ItemsControl class with a default ItemTemplate that has a single Path object. That object's geometry is bound to the controls DataPointGeometry property, and its RenderTransform is bound to the data item's X and Y values as offsets for a translation transform.
A simple Canvas is used for the ItemsPanel, as I just need a place to draw things, without any other layout features. Finally, there is a resource defining a default geometry to use, in case the caller doesn't provide one.
And about that caller…
Here is a simple example of how one might use the above:
<Window x:Class="TestSO28332278SimpleGraphControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestSO28332278SimpleGraphControl"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<PathGeometry x:Key="dataPointGeometry"
Figures="M 0.5000,0.0000
L 0.6176,0.3382
0.9755,0.3455
0.6902,0.5618
0.7939,0.9045
0.5000,0.7000
0.2061,0.9045
0.3098,0.5618
0.0245,0.3455
0.3824,0.3382 Z">
<PathGeometry.Transform>
<ScaleTransform ScaleX="20" ScaleY="20" />
</PathGeometry.Transform>
</PathGeometry>
</Window.Resources>
<Grid>
<Border Margin="3" BorderBrush="Black" BorderThickness="1">
<local:SimpleGraph Width="450" Height="300" DataPointGeometry="{StaticResource dataPointGeometry}">
<local:SimpleGraph.Items>
<local:DataPoint X="10" Y="10" />
<local:DataPoint X="25" Y="25" />
<local:DataPoint X="40" Y="40" />
<local:DataPoint X="55" Y="55" />
</local:SimpleGraph.Items>
</local:SimpleGraph>
</Border>
</Grid>
</Window>
In the above, the only truly interesting thing is that I declare a PathGeometry resource, and then bind that resource to the control's DataPointGeometry property. This allows the program to provide a custom geometry for the graph.
WPF handles the rest through implicit data binding and templating. If the values of any of the DataPoint objects change, or the data collection itself is modified, the graph will be updated automatically.
Here's what it looks like:
I will note that the above example only allows you to specify the geometry. The other shape attributes are hard-coded in the data template. This seems slightly different from what you asked to do. But note that you have a few alternatives here that should address your need without requiring the reintroduction of all the extra manual-binding/updating code in your example:
Simply add other properties, bound to the template Path object in a fashion similar to the DataPointGeometry property. E.g. DataPointFill, DataPointStroke, etc.
Go ahead and allow the user to specify a Shape object, and then use the properties of that object to populate specific properties bound to the properties of the template object. This is mainly a convenience to the caller; if anything, it's a bit of added complication in the graph control itself.
Go whole-hog and allow the user to specify a Shape object, which you then convert to a template by using XamlWriter to create some XAML for the object, add the necessary Transform element to the XAML and wrap it in a DataTemplate declaration (e.g. by loading the XAML as an in-memory DOM to modify the XAML), and then using XamlReader to then load the XAML as a template which you can then assign to the ItemTemplate property.
Option #3 seems the most complicated to me. So complicated in fact that I did not bother to prototype an example using it…I did a little research and it seems to me that it should work, but I admit that I did not verify for myself that it does. But it would certainly be the gold standard in terms of absolute flexibility for the caller.
In my app, I have a Left Grid and a Right Grid. Initially, the Left Grid is fully expanded (GridLength = 7*) while the Right Grid is not visible (Width = 0*).
If a Button in the LeftGrid is clicked and the Right Grid is NOT expanded, the Right Grid should expand to a Width of 3*.
If the Right Grid is expanded and a Button in the LeftGrid is clicked twice successively, then the RightGrid should shrink back to a Width of 0*.
These expansions/contractions should be Animated.
When a Button is clicked, three things must happen.
1) The id of the selected Button should be stored in a Property in the ViewModel.
2) The Width to which the RightGrid will be set, is stored in a Property in the ViewModel. This Command takes care of the two successive click case.
3) Finally, the Animation should run.
I am experiencing a few issues:
1) How do I bind the two Commands to a single Button?
<Button DockPanel.Dock="Top"
Command="{Binding DataContext.SetSelectedItemCommand,
RelativeSource={RelativeSource
AncestorType={x:Type ItemsControl}}}"
CommandParameter="{Binding id}"
Command="{Binding ElementName=root, Path=DataContext.ChangeRightGridWidthCommand}"
>
This is not allowed.
I've been looking at this article (http://www.codeproject.com/Articles/25808/Aggregating-WPF-Commands-with-CommandGroup).
However, I'm not certain if this will work for me as I am defining my Commands in my ViewModel.
2) I am using a Custom GridLength Animation class as I need to use * for my Widths.
public class GridLengthAnimation : AnimationTimeline
{
public static readonly DependencyProperty ToProperty =
DependencyProperty.Register(
"To", typeof(GridLength), typeof(GridLengthAnimation));
public GridLength To
{
get { return (GridLength)GetValue(ToProperty); }
set { SetValue(ToProperty, value); }
}
public override Type TargetPropertyType
{
get { return typeof(GridLength); }
}
protected override Freezable CreateInstanceCore()
{
return new GridLengthAnimation();
}
public override object GetCurrentValue(
object defaultOriginValue, object defaultDestinationValue,
AnimationClock animationClock)
{
return new GridLength(To.Value,
GridUnitType.Star);
}
}
I plan on using something like this in my XAML:
<proj:GridLengthAnimation
Storyboard.TargetName="rightGrid"
Storyboard.TargetProperty="Width"
To="RightGridWidth" Duration="0:0:2" />
<Grid.ColumnDefinitions>
<ColumnDefinition Name="leftGrid" Width="7*"/>
<ColumnDefinition Name="rightGrid" Width="{Binding RightGridWidth}"/>
</Grid.ColumnDefinitions>
Should I put the above XAML code within:
<EventTrigger RoutedEvent="Button.Click">
If so, will my Commands or the EventTrigger be run first? I need my Commands to be run first as only then will the To value have been set correctly when the Storyboard runs.
I'm quite confused as to how to put all this together.
Any help/advice would be greatly appreciated!!
Quick and dirty with what you already have, i.e. the EventTrigger that runs the animation.
Add a RightGridWidth property (with change notification) to your view model:
public GridLength RightGridWidth
{
get { return rightGridWidth; }
set
{
rightGridWidth = value;
OnPropertyChanged("RightGridWidth");
}
}
Toggle the property value in the SetSelectedItemCommand's execute handler:
if (RightGridWidth.Value == 0d)
{
RightGridWidth = new GridLength(3d, GridUnitType.Star);
}
else
{
RightGridWidth = new GridLength(0d, GridUnitType.Star);
}
Run the animation in the Click EventTrigger:
<Button Content="Click" Command="{Binding SetSelectedItemCommand}">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<local:GridLengthAnimation
Storyboard.Target="{Binding ElementName=col2}"
Storyboard.TargetProperty="Width"
Duration="0:0:1" To="{Binding RightGridWidth}"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
Finally, use a working implementation of the animation's GetCurrentValue, which has to return a value that depends on the CurrentProgress:
public override object GetCurrentValue(
object defaultOriginValue, object defaultDestinationValue,
AnimationClock animationClock)
{
var from = (GridLength)defaultOriginValue;
if (from.GridUnitType != To.GridUnitType ||
!animationClock.CurrentProgress.HasValue)
{
return from;
}
var p = animationClock.CurrentProgress.Value;
return new GridLength(
(1d - p) * from.Value + p * To.Value,
from.GridUnitType);
}
An entirely different and perhaps better approach would be the use of visual states. See VisualStateManager.
I have the following base class for MenuItems in my MVVM application
public class StandardMenuItem : MenuItemBase, IExecutableItem
{
...
public Image Icon { get; private set; }
...
}
where my initial idea was to use Image to back the icons I display on my MenuItems. I have now come to the point where I am starting to use these MenuItems in the front end of my application and have found a superb vector graphics library I want to use instead.
<ResourceDictionary x:Class="resources_icons_xaml"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Canvas x:Key="appbar_acorn" Width="48" Height="48" Clip="F1 M 0,0L 48,0L 48,48L 0,48L 0,0">
<Path Width="22.3248" Height="25.8518" Canvas.Left="13.6757" Canvas.Top="11.4012" Stretch="Fill" Fill="{DynamicResource BlackBrush}" Data="F1 M 16.6309,18.6563C 17.1309,8.15625 29.8809,14.1563 29.8809,14.1563C 30.8809,11.1563 34.1308,11.4063 34.1308,11.4063C 33.5,12 34.6309,13.1563 34.6309,13.1563C 32.1309,13.1562 31.1309,14.9062 31.1309,14.9062C 41.1309,23.9062 32.6309,27.9063 32.6309,27.9062C 24.6309,24.9063 21.1309,22.1562 16.6309,18.6563 Z M 16.6309,19.9063C 21.6309,24.1563 25.1309,26.1562 31.6309,28.6562C 31.6309,28.6562 26.3809,39.1562 18.3809,36.1563C 18.3809,36.1563 18,38 16.3809,36.9063C 15,36 16.3809,34.9063 16.3809,34.9063C 16.3809,34.9063 10.1309,30.9062 16.6309,19.9063 Z "/>
</Canvas>
...
<ResourceDictionary/>
My problem is, using these vector graphics via code does not seem straight forward. I know how to include such graphics in XAML
<!-- Include Resource Dictionary -->
<MenuItem Header="Show Difference Details"
ToolTip="Launch the grouped data file and analysis window."
IsEnabled="{Binding GroupedDataIsDifferent}"
Caliburn:Message.Attach="[Event Click] = [Action ShowDifferenceDetailsAsync()]">
<MenuItem.Icon>
<Rectangle Width="16" Height="16">
<Rectangle.Fill>
<VisualBrush Stretch="Uniform" Visual="{StaticResource appbar_column_two}" />
</Rectangle.Fill>
</Rectangle>
</MenuItem.Icon>
</MenuItem>
but this is not my problem. My questions are:
How can I use vector graphics from a resource dictionary in my Icons/Images for my StandardMenuItems?
If the answer to 1. is "you can't", how can I convert from a vector graphic to an Icon in code?
Thanks for your time.
Edit. I want to be able to pick up the graphics using code. So for my menu item I have a method
public StandardMenuItem WithIcon(Assembly source, string path)
{
var manager = IoC.Get<IResourceManager>();
var iconSource = manager.GetBitmap(path, source.GetAssemblyName());
if (source != null)
{
IconSource = path;
}
return this;
}
my issue now is getting the correct path to the vector image I want. Lets say in my solution I have my vector image in "Graphics/Icons.xaml" and the resource is called "appbar_acorn", how can I reference this?
here you go
start by changing the Icon property to string
eg
public string Icon { get; private set; }
assign the icon value as key of the icon you want to use
Icon = "appbar_acorn";
define the converter in resources
<l:StringToResourceConverter x:Key="StringToResourceConverter" />
l: refers to the converter's namespace eg xmlns:l="clr-namespace:CSharpWPF"
the usage
<MenuItem Icon="{Binding Icon,Converter={StaticResource StringToResourceConverter}}"
Header="Menu"/>
result
here is the converter class
namespace CSharpWPF
{
class StringToResourceConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Application.Current.FindResource(value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
you may need to adjust the icon size and placement on canvas, in the sample above I removed the Canvas.Left="13.6757" & Canvas.Top="11.4012" but still it is little big for the menu icon
i am trying to create an overlay like this when mouse move over image control. how to I do it using WPF ?
please advice
You create both the image and the overlay, and bind the visibility property of the overlay to the image's and the overlay's IsMouseOver property.
You can also do it with triggers instead of binding. It works too.
Update:
Here is sample code. The XAML can look like this:
<Grid>
<Grid.Resources>
<local:OverlayVisibilityConverter x:Key="OverlayVisibilityConverter" />
</Grid.Resources>
<Image x:Name="myImage" Source="MyImage.JPG" />
<Image x:Name="myOverlay"
Source="MyOverlay.jpg"
VerticalAlignment="Center"
Opacity="0.2">
<Image.Visibility>
<MultiBinding Converter="{StaticResource OverlayVisibilityConverter}">
<Binding ElementName="myOverlay" Path="IsMouseOver" />
<Binding ElementName="myImage" Path="IsMouseOver" />
</MultiBinding>
</Image.Visibility>
</Image>
</Grid>
Of course the overlay must not be an image and can be anything. I just used an image in the sample. The opacity can be anything between 0 and 1.
The code for the converter can look like this:
class OverlayVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var isMouseOverOverlay = (bool)values[0];
var isMouseOverImage = (bool)values[1];
if (isMouseOverImage || isMouseOverOverlay)
return Visibility.Visible;
else
return Visibility.Hidden;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I didn't like the complexity of the other answer as I felt it was too much for something so simple, so I tried it using MouseEnter and MouseLeave events and it seems to work quite well.
XAML:
<Grid MouseEnter="Grid_MouseEnter" MouseLeave="Grid_MouseLeave">
<Image x:Name="MainImage" Source="..." />
<Image x:Name="OverlayImage" Source="..." />
</Grid>
With accompanying code:
private void Grid_MouseEnter(object sender, MouseEventArgs e)
{
OverlayImage.Visibility = System.Windows.Visibility.Visible;
}
private void Grid_MouseLeave(object sender, MouseEventArgs e)
{
OverlayImage.Visibility = System.Windows.Visibility.Collapsed;
}
You don't need to use an image for the overlay, it can be anything. In my real case I actually had a StackPanel overlay which contained buttons for edit and delete (so the user could change/remove the image)