Value converter not loading images in Tree View - c#

I'm a beginner at C#. Trying to follow this tutorial : https://www.youtube.com/watch?v=6OwyNiLPDNw
I've currently created a TreeView to display all the folders/files in the system as described in the above video. I've added a valueConverter class to change the icon based on if the item is a drive/folder/file. Have three files for that accordingly. Now, the problem, I'm facing is that, when I run the program, there's no exception thrown, but the images are not displayed. If I add a static image file in the corresponding xaml file, I see that it's displayed. But if I used a valueConverter, it's not. I single stepped into the program, and I can see that my valueConverter function is being successfully called and the corresponding image is chosen as well. I don't understand why the image is not being displayed.
Any help is highly appreciated !
Code for my xaml:
<Window x:Class="Wpf_TreeView.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Wpf_TreeView"
Loaded="Window_Loaded"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TreeView x:Name="FolderView">
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Width="100" Margin="3"
Source="{Binding
RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type TreeViewItem}},
Path=Tag,
Converter={x:Static local:HeaderToImageConverter.Instance}}"/>
<TextBlock VerticalAlignment="Center" Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.Resources>
Code for my Value Converter:
namespace Wpf_TreeView
{
/// <summary>
/// Convert a full path to a specific image type of a drive,folder or a file
/// </summary>
[ValueConversion(typeof(string), typeof(BitMapImage))]
public class HeaderToImageConverter : IValueConverter
{
public static HeaderToImageConverter Instance = new HeaderToImageConverter();
//public static HeaderToImageConverter Instance { get => instance; set => instance = value; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//Get the full path
var path = (string)value;
//Check if path is null
if (path == null) return null;
//Get the name of the file/folder
var name = MainWindow.GetFileFolderName(path);
// By default we presume file image
var image = "Images/file.png";
// If the name is blank, we assume it's a drive (file/folder) cannot have blank name
if (string.IsNullOrEmpty(name))
{
image = "Images/drive.png";
}
else if (new FileInfo(path).Attributes.HasFlag(FileAttributes.Directory))
{
image = "Images/folder.jpg";
}
//the below statement does not seem to work although image contains the proper string as expected
return new BitmapImage(new Uri($"pack://application:,,,/{image}"));
}
}
}

The solution was to use BitmapImage instead of BitMapImage. Should be wary of using alt+enter without paying attention to what it does !

Related

WPF XAML serialize/deserialize

my application is basically taking one WPF Canvas containing other controls and serialize it to an XML file and then deserialize it to display the serialized data or a previously saved one. The serialization/deserialization is working fine everything is saved and restored back. The issue is that after deserialization if I try to change an image source with the bellow code it doesn't work:
testLogo.Source = new BitmapImage(new Uri(file.FileName));
The Image is referrenced in the XAML as bellow:
<Canvas Name="mainCanva" Margin="0,0,12,0" Width="1729" Height="150" VerticalAlignment="Top">
<Border BorderThickness="3" BorderBrush="#FF009B80" FlowDirection="LeftToRight" VerticalAlignment="Top" HorizontalAlignment="Left" Width="1729" Height="150">
<Grid Margin="0" Background="White" Height="Auto" Width="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Uid="">
<Grid HorizontalAlignment="Left" Margin="3,28,0,57">
<Image HorizontalAlignment="Left" Margin="0,0,0,0" x:Name="testLogo" Stretch="UniformToFill" Width="317" Source="file:///C:/Users/logo.jpg" />
</Grid>
</Grid>
</Border>
</Canvas>
The Deserialization code is as bellow:
Canvas canvas = DeSerializeXAML(appPath + "\\tmp\\mainCanva.xml") as Canvas;
mainCanva.Children.Clear();
while (canvas.Children.Count > 0)
{
UIElement obj = canvas.Children[0];
canvas.Children.Remove(obj);
mainCanva.Children.Add(obj); // Add to canvas
}
Another point to note is that I tried to find out what was happening using Snoop, after Deserialization Snoop is also unable to change the image source although if I reconnect Snoop to the app by drag and dropping the crosshair Snoop is now able to change the Image source. The 'old' Snoop window can see the image source being updated from the testLogo.Source = command. WPF inspector doesn't have this issue it is immediately updating itself when the deserialization is happening. My guess is that there is something wrong with the visual tree ... and as WPF can do it I think it can be sorted.
Thanks for the help guys.
As requested the Serialize/Deserialize functions:
public static void SerializeToXAML(UIElement element, string filename)
{
string strXAML = System.Windows.Markup.XamlWriter.Save(element);
using (System.IO.FileStream fs = System.IO.File.Create(filename))
{
using (System.IO.StreamWriter streamwriter = new System.IO.StreamWriter(fs))
{
streamwriter.Write(strXAML);
}
}
}
public static UIElement DeSerializeXAML(string filename)
{
using (System.IO.FileStream fs = System.IO.File.Open(filename, System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
return System.Windows.Markup.XamlReader.Load(fs) as UIElement;
}
}
You need to update your variable reference.
When you call mainCanva.Children.Clear(), it removes all the children including testLogo. Your variable testLogo will still be pointing at the original testLogo object even though it's not part of the UI anymore.
Try this:
Canvas canvas = DeSerializeXAML(appPath + "\\tmp\\mainCanva.xml") as Canvas;
mainCanva.Children.Clear();
while (canvas.Children.Count > 0)
{
UIElement obj = canvas.Children[0];
canvas.Children.Remove(obj);
mainCanva.Children.Add(obj); // Add to canvas
}
testLogo = mainCanva.FindName("testLogo") as Image;
I ended up using Application resources:
<Application.Resources>
<BitmapImage x:Key="testLogo" >file:///C:/Users/test_B&W.png</BitmapImage>
</Application.Resources>
then in the code:
Resources["AVItestLogoic"] = ...
This way no matter what happens to the Visual Tree, the application resource always point to the right item

How to extend a WPF window for a certain condition?

I have a WPF window that only has a ComboBox (drop down list). If I choose index 1 (second item on the drop down list), how can I extend that WPF window to show more buttons, textbox, etc? Would I need to use the selectedIndex property? If so how do I make the window extend in the XAML.
I have used a IValueConverter in the past to accomplish this. Here is a sample converter:
public class MyConverter : System.Windows.Data.IValueConverter {
public object Convert ( object value , Type targetType , object parameter , CultureInfo culture ) {
if ( value == null )
return System.Windows.Visibility.Hidden;
if ( parameter == null )
return System.Windows.Visibility.Hidden;
if ( value.ToString().Equals ( parameter ) )
return System.Windows.Visibility.Visible;
return System.Windows.Visibility.Hidden;
}
public object ConvertBack ( object value , Type targetType , object parameter , CultureInfo culture ) {
throw new NotImplementedException ( );
}
}
What this does is, it takes the value that is passed to it, I am expecting a number such as the SelectedIndex of an items control. I then compare it to the parameter that is passed. If they are equal, I return Visibility.Visible. In all other instances, I return Visibility.Hidden.
Now, you can take that and plug it into the XAML like this:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:MyConverter x:Key="vConv"/>
</Window.Resources>
<Grid>
<ComboBox x:Name="comboBox" HorizontalAlignment="Left" Margin="25,52,0,0" VerticalAlignment="Top" Width="120">
<ComboBoxItem>Hidden</ComboBoxItem>
<ComboBoxItem>Visible</ComboBoxItem>
</ComboBox>
<Label x:Name="label" Content="Label" HorizontalAlignment="Left" Margin="219,92,0,0" VerticalAlignment="Top" Visibility="{Binding ElementName=comboBox, Path=SelectedIndex, Converter={StaticResource vConv}, ConverterParameter=1, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>
You can see that we created an instance of our MyConverter class in the Window.Resources. When we use this in our binding, we can show/hide my label based on whatever index is selected. Now this is very basic and you can add a lot to this to get all the functionality you need, bu this should get you started.
You can use the MaxHeight and MaxWidth property of the window on selection of the comboBox Item. Like this:
On Selection Change event of combobox.Use this
MainWindow obj= new MainWindow();
if(mycombobox.SelectedIndex==0)
{
obj.MaxWidth="600";
obj.MinWidth="600";
}
if(mycombobox.SelectedIndex==1)
{
obj.MaxWidth="200";
obj.MinWidth="200";
}
or you also can do this
if(mycombobox.SelectedIndex==0)
{
this.MaxWidth="600";
this.MinWidth="600";
}
if(mycombobox.SelectedIndex==1)
{
this.MaxWidth="200";
this.MinWidth="200";
}

Convert Shape into reusable Geometry in WPF

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.

Assign an Image for each Pivot Item Header

I am trying to assign an image for each of my pivot items's header instead of a Text .
I tried several methods (one was given by this post http://social.msdn.microsoft.com/Forums/wpapps/en-US/e7b5fd17-3465-4a94-81af-5c056c992c11/add-image-to-pivot-title?forum=wpdevelop )
I managed to assign the same image for my pivot but not one image for each header.
This is what I tried :
<phone:Pivot.HeaderTemplate >
<DataTemplate>
<Image Source="21.jpg" Height="55" Width="55"/>
</DataTemplate>
</phone:Pivot.HeaderTemplate>
This obviously gave me the same image for each headers ,
So i wanted to try something like this :
<phone:Pivot.HeaderTemplate >
<DataTemplate>
<Image Source="{Binding}" Height="55" Width="55"/>
</DataTemplate>
</phone:Pivot.HeaderTemplate>
[...]
<phone:PivotItem ??? >
<// phone:PivotItem >
But then i don't know what to add my image path.
i used this method when i wanted to assign a text as a header and it worked :
<phone:Pivot.HeaderTemplate >
<DataTemplate>
<TextBlock Text="{Binding }" FontSize="88" />
</DataTemplate>
</phone:Pivot.HeaderTemplate>
<phone:PivotItem Header = "Title1" />
How can i assign an image for each of my Header ?
You should be able to simply provide the image source in the Header:
<phone:PivotItem Header = "21.jpg" />
This sets the data context to use for the HeaderTemplate for that particular item.
You need to use a Converter Class to solve this issue.
namespace MyImageConvertor
{
public class MyValueConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
var uri = new Uri((string)(value), UriKind.RelativeOrAbsolute);
var img = new BitmapImage(uri);
return img;
}
catch
{
return new BitmapImage();
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var img = value as BitmapImage;
return img.UriSource.AbsoluteUri;
}
#endregion
}
}
Then use the this convertor in your xaml.
<UserControl x:Class="ValueConverter.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:this="clr-namespace:MyImageConvertor">
<UserControl.Resources>
<this:MyValueConverter x:Key="ImageConverter"/>
</UserControl.Resources>
<phone:Pivot>
<phone:Pivot.HeaderTemplate>
<DataTemplate>
<Image Source="{Binding ImageUrlProperty, Converter={StaticResource ImageConverter},Mode=TwoWay}"></Image>
</DataTemplate>
</phone:Pivot.HeaderTemplate>
</phone:Pivot>
Make sure you have the full image path in the ImageUrlProperty value like ..\Images\logo.png.

Binding an Image in WPF?

I wanna show an image in WPF that is created by a process,
e.g : we have a method that is named createWPFImage()
Image createWPFImage() { ... }
So, the output of createWPFImage() is an Image.
In the XAML code, we have a something like below :
<StackPanel.ToolTip>
<StackPanel Orientation="Horizontal">
<Image Width="64" Height="64" Margin="0 2 4 0" />
<TextBlock Text="{Binding Path=Description}" VerticalAlignment="Center" />
</StackPanel>
</StackPanel.ToolTip>
Now, How can I bind the output of createWPFImage() to the Image in XAML code ?
I would be appreciate if you guide me.
Say you have class "MyClass" with method "CreateWpfImage" (see example below).
In your XAML you can create MyClass, and then call CreateWpfImage, using ObjectDataProvider in a Resources section (See Bea Stollnitz blog article ObjectDataProvider).
XAML
<Window x:Class="MyApplicationNamespace.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:MyApplicationNamespace="clr-namespace:MyApplicationNamespace"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<ObjectDataProvider ObjectType="{x:Type MyApplicationNamespace:MyClass}" x:Key="MyClass" />
<ObjectDataProvider ObjectInstance="{StaticResource MyClass}" MethodName="CreateWpfImpage" x:Key="MyImage" />
</Window.Resources>
<StackPanel>
<Image Source="{Binding Source={StaticResource MyImage}, Path=Source}"/>
</StackPanel>
Example MyClass code to create an image for the XAML to use -
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace MyApplicationNamespace
{
public class MyClass
{
public Image CreateWpfImpage()
{
GeometryDrawing aGeometryDrawing = new GeometryDrawing();
aGeometryDrawing.Geometry = new EllipseGeometry(new Point(50, 50), 50, 50);
aGeometryDrawing.Pen = new Pen(Brushes.Red, 10);
aGeometryDrawing.Brush = Brushes.Blue;
DrawingImage geometryImage = new DrawingImage(aGeometryDrawing);
Image anImage = new Image();
anImage.Source = geometryImage;
return anImage;
}
}
}
If you have a path to your image and just want to be able to change the image on the fly, then bind to a dependency property of type string and in your method, set the value of the dependency property.
<Image Source="{Binding MyImagePath}" />
public static readonly DependencyProperty MyImagePathProperty = DependencyProperty.Register("MyImagePath", typeof(string), typeof(ClassName), new PropertyMetadata("pack://application:,,,/YourAssembly;component//icons/icon1.png"));
public string MyImagePath
{
get { return (string)GetValue(MyImagePathhProperty); }
set { SetValue(MyImagePathProperty, value); }
}

Categories