Tiff image with bitmapsource, Slower than JPEG loading - wpf

I have the below code in UI
<ListBox x:Name="scannedImageslist" ItemContainerStyle="{StaticResource ListBoxCustom}" Background="Gray" SelectionMode="Multiple" Grid.Column="1" Grid.Row="1" ItemsSource="{Binding }" ScrollViewer.PanningMode="Both" ScrollViewer.HorizontalScrollBarVisibility="Disabled" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<!--<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsSelected" Value="true" >
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Background" Value="Orange" />
<Setter Property="Foreground" Value="Black" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>-->
<!--<ListBox.Resources>
<gif:ImageConverter x:Key="UriToBitmapConverter" />
</ListBox.Resources>-->
<!--<ListBox.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black"/>
<GradientStop Color="#FF1E2A2F" Offset="1"/>
</LinearGradientBrush>
</ListBox.Background>-->
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" IsItemsHost="True" >
</WrapPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<!--<Border BorderBrush="#FFFF9800" BorderThickness="3" Width="120" Height="120" Padding="10" Margin="15" CornerRadius="10">-->
<!--Bind Image Path in Image Control-->
<!--<CheckBox Grid.Column="0" Grid.Row="0" VerticalAlignment="Top" HorizontalAlignment="Left" ToolTip="{Binding FullPath}" IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}, Path=IsSelected}"></CheckBox>-->
<Image Grid.Column="1" Grid.Row="0" Stylus.IsPressAndHoldEnabled="True" Stretch="None" HorizontalAlignment="Center" ToolTip="{Binding FullPath}" MouseRightButtonDown="Image_MouseRightButtonDown" >
<Image.Source>
<BitmapImage UriSource="{Binding FullPath}" DecodePixelWidth="200" CreateOptions="IgnoreImageCache" RenderOptions.BitmapScalingMode="HighQuality" VirtualizingPanel.VirtualizationMode="Recycling" CacheOption="OnLoad"> </BitmapImage>
</Image.Source>
<Image.LayoutTransform>
<TransformGroup>
<ScaleTransform CenterX="0" CenterY="0" ScaleX="{Binding ElementName=sliderzoom,Path=Value}" ScaleY="{Binding ElementName=sliderzoom,Path=Value}" />
</TransformGroup>
</Image.LayoutTransform>
</Image>
<TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding ImageName}" TextAlignment="Center" VerticalAlignment="Bottom" Typography.SlashedZero="True" Typography.NumeralStyle="OldStyle" TextWrapping="Wrap" HorizontalAlignment="Center"></TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Binding class and properties are
public class ImageItem : INotifyPropertyChanged
{
#region Properties
public string FullPath { get; set; }
public string ImageName { get; set; }
public ImageItem(string fullPath)
{
FullPath = fullPath;
//SetImage(fullPath);
//Image = new BitmapImage(new Uri(#fullPath));
ImageName = System.IO.Path.GetFileNameWithoutExtension(fullPath);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Private Helpers
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
With the above code, the Jpegs are loading 10 times faster than Tiff images. Whats wrong with Tiff handling here and what would be correct approach in terms of performance?
Update:
I have used a converter and it is pretty fast just like Jpegs its loading Tiff, however the problem is not decode, the memory is eaten up.
public class UriConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (Path.GetExtension(value.ToString()).ToUpper() == ".TIF")
{
Stream tiff = new System.IO.FileStream(value.ToString(), FileMode.Open, FileAccess.Read, FileShare.Read);
TiffBitmapDecoder tiffdec = new TiffBitmapDecoder(tiff, BitmapCreateOptions.DelayCreation, BitmapCacheOption.Default);
return tiffdec.Frames[0];
//return tiffdec.Frames[0].Thumbnail;
//GdPictureImaging obj = new GdPictureImaging();
//int m_CurrentImage = obj.CreateGdPictureImageFromFile(value.ToString());
//int thumnail= obj.CreateThumbnail(m_CurrentImage, 96, 96);
//TiffBitmapEncoder encoder = new TiffBitmapEncoder();
//MemoryStream memoryStream = new MemoryStream();
//BitmapImage bImg = new BitmapImage();
//encoder.Frames.Add(BitmapFrame.Create(tiffibitmapsource));
//encoder.Save(memoryStream);
//bImg.BeginInit();
//bImg.DecodePixelWidth = 100;
//bImg.CacheOption = BitmapCacheOption.OnLoad;
//bImg.StreamSource = new MemoryStream(memoryStream.ToArray());
//bImg.EndInit();
//memoryStream.Close();
//return bImg;
}
else
{
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.DecodePixelWidth = 100;
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.UriSource = new Uri(value.ToString());
bi.EndInit();
return bi;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
How to convert Bitmapsource to BitmapImage and then decode pixels!!!
TIFF doesnt have thumbnails, is there a way to create and store along, then display only thumbnail??
Update 2
As per MSDN
Is there a way TIF s to work as same as JPEG without conversion just for loading...
The JPEG and Portable Network Graphics (PNG) codecs natively decode
the image to the specified size; other codecs decode the image at its
original size and scale the image to the desired size.

Related

WPF own bar chart using grid

Let's say we have 25 products A and 14 products B. I want to create chart that is representing them using rectangles and grid. I wrote this code below and it works, but the chart generated with it is very inaccurate. Any ideas how to fix it?
<StackPanel Orientation="Horizontal">
<!--Products A-->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.2*"/>
<RowDefinition Height="percentage1*"/>
</Grid.RowDefinitions>
<Rectangle Fill="Red" HorizontalAlignment="Stretch" Grid.Row="1"/>
</Grid>
<!--Products B-->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.2*"/>
<RowDefinition Height="percentage2*"/>
</Grid.RowDefinitions>
<Rectangle Fill="Red" HorizontalAlignment="Stretch" Grid.Row="1"/>
</Grid>
</StackPanel>
percentage1 = 25 products / (25 products + 14 products)
percentage2 = 14 products / (25 products + 14 products)
You should not control the size of the Rectangle with the width of a Grid row. Instead host the Rectangle elements in a ItemsControl and calculate the final rendered width based on the available space:
rectangle_width = ratio * available_width
= value / max_value_in_chart * available_width
It's best to move the logic and layout to a custom Control or UserControl. And because the Rectangle bars are hosted in an ItemsControl, you can as many bars as you need to without any hassle (opposed to modifying a Grid to add more rows):
Usage
<local:SimpleBarChart x:Name="BarChart"
BarThickness="64"
Orientation="Horizontal"/>
public MainWindow()
{
InitializeComponent();
// Better use data binding
this.BarChart.BarValues = new List<double> { 12, 24, 36 };
}
SimpleBarChart.xaml.cs
public partial class SimpleBarChart : UserControl
{
public IList<double> BarValues
{
get => (IList<double>)GetValue(BarValuesProperty);
set => SetValue(BarValuesProperty, value);
}
public static readonly DependencyProperty BarValuesProperty = DependencyProperty.Register(
"BarValues",
typeof(IList<double>),
typeof(SimpleBarChart),
new PropertyMetadata(default));
public double BarThickness
{
get => (double)GetValue(BarThicknessProperty);
set => SetValue(BarThicknessProperty, value);
}
public static readonly DependencyProperty BarThicknessProperty = DependencyProperty.Register(
"BarThickness",
typeof(double),
typeof(SimpleBarChart),
new PropertyMetadata(default));
public SimpleBarChart()
{
InitializeComponent();
}
}
SimpleBarChart.xaml
<UserControl>
<UserControl.Resources>
<local:BarValueToLengthConverter x:Key="BarValueToLengthConverter" />
</UserControl.Resources>
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=BarValues}"
HorizontalAlignment="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=HorizontalContentAlignment}"
HorizontalContentAlignment="Stretch"
VerticalAlignment="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=VerticalalContentAlignment}"
VerticalContentAlignment="Stretch">
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Margin"
Value="0,0,0,12" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Fill="Orange"
Height="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=BarThickness}"
HorizontalAlignment="Left">
<Rectangle.Width>
<MultiBinding Converter="{StaticResource BarValueToLengthConverter}">
<Binding RelativeSource="{RelativeSource AncestorType=UserControl}"
Path="BarValues" />
<Binding />
<Binding RelativeSource="{RelativeSource AncestorType=UserControl}"
Path="BarThickness" />
<Binding RelativeSource="{RelativeSource AncestorType=ItemsControl}"
Path="ActualWidth" />
</MultiBinding>
</Rectangle.Width>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
BarValueToLengthConverter.cs
public class BarValueToLengthConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
IList<double>? barValues = values.OfType<IList<double>>().FirstOrDefault();
IList<double>? doubleValues = values.OfType<double>().ToList();
double barValue = doubleValues[0];
double barThickness = doubleValues[1];
double barHostWidth = doubleValues[2];
return barValue / barValues.Max() * barHostWidth;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
}

WPF Change icon depending on binding in Control Template

I have a control template that I've defined for a DevExpress TextEdit control and I want to change the Image Source property in the template depending on a binding (e.g. IsIncrease).
<ControlTemplate x:Key="WarningTextEdit" TargetType="dxe:TextEdit">
<Grid>
<TextBox Text="{TemplateBinding Text}"/>
<Image Margin="0,0,5,0"
Source="pack://application:,,,/DevExpress.Xpf.Core.v17.2;component/Core/ConditionalFormatting/Images/IconSets/Symbols3_2.png"
Width="17"
Height="16"
RenderOptions.BitmapScalingMode="NearestNeighbor"
HorizontalAlignment="Right"/>
</Grid>
</ControlTemplate>
If the property IsIncrease was set to true then one particular icon should be shown and if the property was set to false then another particular icon should be shown. Anyone know how to do this?
Thanks
You can do it using a converter, you have two choices, either you include 2 Images inside your Grid and you hide/show them using the converter or you use one Image and use the converter to change the Source property (which is probably a better solution). Here's a converter that can handle both situation, followed by both solutions.
BoolConverter :
public class BoolConverter : MarkupExtension, IValueConverter
{
public object TrueValue { get; set; } = Binding.DoNothing;
public object FalseValue { get; set; } = Binding.DoNothing;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is bool))
return Binding.DoNothing;
return (bool)value ? TrueValue : FalseValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == TrueValue)
return true;
if (value == FalseValue)
return false;
return Binding.DoNothing;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
MainWindow.xaml (solution 1 : two Images) :
<ControlTemplate x:Key="WarningTextEdit" TargetType="dxe:TextEdit">
<Grid>
<TextBox Text="{TemplateBinding Text}"/>
<Image Margin="0,0,5,0"
Source="pack://application:,,,/DevExpress.Xpf.Core.v17.2;component/Core/ConditionalFormatting/Images/IconSets/Symbols3_2.png"
Width="17"
Height="16"
RenderOptions.BitmapScalingMode="NearestNeighbor"
HorizontalAlignment="Right"
Visibility="{Binding IsIncrease, Converter={local:BoolConverter TrueValue=Collapsed, FalseValue=Visible}}"/>
<Image Margin="0,0,5,0"
Source="pack://application:,,,/DevExpress.Xpf.Core.v17.2;component/Core/ConditionalFormatting/Images/IconSets/TrafficLights3_1.png"
Width="17"
Height="16"
RenderOptions.BitmapScalingMode="NearestNeighbor"
HorizontalAlignment="Right"
Visibility="{Binding IsIncrease, Converter={local:BoolConverter TrueValue=Visible, FalseValue=Collapsed}}"/>
</Grid>
</ControlTemplate>
MainWindow.xaml (solution 2 : one Image) :
<ControlTemplate x:Key="WarningTextEdit" TargetType="dxe:TextEdit">
<Grid>
<TextBox Text="{TemplateBinding Text}"/>
<Image Margin="0,0,5,0"
Source="{Binding IsIncrease,
Converter={local:BoolConverter TrueValue='pack://application:,,,/DevExpress.Xpf.Core.v17.2;component/Core/ConditionalFormatting/Images/IconSets/Symbols3_2.png',
FalseValue='pack://application:,,,/DevExpress.Xpf.Core.v17.2;component/Core/ConditionalFormatting/Images/IconSets/TrafficLights3_1.png'}}"
Width="17"
Height="16"
RenderOptions.BitmapScalingMode="NearestNeighbor"
HorizontalAlignment="Right"/>
</Grid>
</ControlTemplate>
To achieve that you need 2 properties to store source for image.I'm gonna use Tag property and write an attached property to store 2 image source and use trigger to change the source
public class AttachedProperty
{
public static readonly DependencyProperty AltSourceProperty =
DependencyProperty.RegisterAttached("AltSource",
typeof(string), typeof(AttachedProperty),
new PropertyMetadata());
public static string GetAltSource(DependencyObject obj)
{
return (string)obj.GetValue(AltSourceProperty);
}
public static void SetAltSource(DependencyObject obj, string value)
{
obj.SetValue(AltSourceProperty, value);
}
}
ControlTemplate
<ControlTemplate x:Key="WarningTextEdit" TargetType="dxe:TextEdit">
<Grid>
<TextBox Text="{TemplateBinding Text}" />
<Image x:Name="image"
Width="17"
Height="16"
Margin="0,0,5,0"
HorizontalAlignment="Right"
RenderOptions.BitmapScalingMode="NearestNeighbor" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsIncrease " Value="True">
<Setter TargetName="image" Property="Source" Value="{Binding Path=Tag, RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
<Trigger Property="IsIncrease " Value="False">
<Setter TargetName="image" Property="Source" Value="{Binding Path=(local:AttachedProperty.AltSource), RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<dxe:TextEdit Tag="Image1.jpg" local:AttachedProperty.AltSource="Image2.jpg"/>

UserControl multiple datatemplate + templateselector

I need to show data inside a usercontrol in different ways depending on a flag.
To achieve this i tried the following, but using this control in the main view shows nothing.
<UserControl DataContext="**self**">
<UserControl.Resources>
<DataTemplate x:Key="mouseInputTemplate">
<TextBlock HorizontalAlignment="Center"><Run Text="{Binding Text}" /></TextBlock>
</DataTemplate>
<DataTemplate x:Key="touchInputTemplate">
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Image Source="{Binding ImageUri}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Width="{Binding ImageWidth}" Height="{Binding ImageHeight}" />
<TextBlock HorizontalAlignment="Center"><Run Text="{Binding Text}" /></TextBlock>
</StackPanel>
</DataTemplate>
<local:InputModeDataTemplateSelector x:Key="inputModeTemplateSelector"
MouseInputModeTemplate="{StaticResource mouseInputTemplate}"
TouchInputModeTemplate="{StaticResource touchInputTemplate}" />
</UserControl.Resources>
<ContentControl HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Stretch" ContentTemplateSelector="{StaticResource inputModeTemplateSelector}" />
</UserControl>
What am i doing wrong?
Is there a better way to achieve that?
Thank to EdPlunkett and more research i found out it is better to
use a ContentPresenter here and instead of binding on DataContext=this bind like this (as alsways suggested when writing a UserControl)
DataContext="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type yourType}}}"
Code:
<UserControl.Resources>
<DataTemplate x:Key="touchInputTemplate">
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Image Source="{Binding ImageUri}" Width="64" Height="64" />
<TextBlock HorizontalAlignment="Center" Text="{Binding Text}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="mouseInputTemplate">
<TextBlock HorizontalAlignment="Center" Text="{Binding Text}" />
</DataTemplate>
<local:InputModeDataTemplateSelector x:Key="inputModeTemplateSelector"
MouseInputModeTemplate="{StaticResource mouseInputTemplate}"
TouchInputModeTemplate="{StaticResource touchInputTemplate}" />
</UserControl.Resources>
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type yourType}}}">
<ContentPresenter Content="{Binding}" ContentTemplateSelector="{StaticResource inputModeTemplateSelector}">
</Grid>
Your ContentPresenter idea is the correct way to do it with a DataTemplateSelector, and I should have thought of it myself.
But here's yet another way to do it, which unlike my first answer, actually solves all the problems you're having:
XAML (in practice the Style would probably be defined in a separate ResourceDictionary):
<Window
x:Class="TestApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestApplication"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
Title="MainWindow" Height="350" Width="525"
>
<Window.Resources>
<Style TargetType="local:TestControl">
<Setter Property="Background" Value="Gainsboro" />
<Style.Triggers>
<!-- The 0 value for the InputMode enum is Mouse, so this will be the default. -->
<Trigger Property="InputMode" Value="Mouse">
<Setter Property="Background" Value="Wheat" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TestControl}">
<Grid Background="{TemplateBinding Background}">
<TextBlock HorizontalAlignment="Center"><Run Text="{TemplateBinding Text}" /></TextBlock>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="InputMode" Value="Touch">
<Setter Property="Background" Value="LightSkyBlue" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TestControl}">
<Grid Background="{TemplateBinding Background}">
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Image
Source="{TemplateBinding ImageUri}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Width="{TemplateBinding ImageWidth}"
Height="{TemplateBinding ImageHeight}"
/>
<TextBlock HorizontalAlignment="Center"><Run Text="{TemplateBinding Text}" /></TextBlock>
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<local:TestControl
ImageHeight="100"
ImageWidth="100"
Text="This is the test control"
ImageUri="http://www.optimizeagency.com/wp-content/uploads/2015/09/GoogleLogo.jpg"
/>
</Grid>
</Window>
C#:
using System;
using System.Windows;
using System.Windows.Controls;
namespace TestApplication
{
class TestControl : Control
{
public TestControl()
{
// If input mode may change at runtime, you'll need an event that fires when that
// happens and updates this property.
// UIUtilities.GetInputMode() is just a stub in this example.
InputMode = UIUtilities.GetInputMode();
}
#region InputMode Property
public InputMode InputMode
{
get { return (InputMode)GetValue(InputModeProperty); }
set { SetValue(InputModeProperty, value); }
}
public static readonly DependencyProperty InputModeProperty =
DependencyProperty.Register("InputMode", typeof(InputMode), typeof(TestControl),
new PropertyMetadata(InputMode.Mouse));
#endregion InputMode Property
#region Text Property
public String Text
{
get { return (String)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(String), typeof(TestControl),
new PropertyMetadata(null));
#endregion Text Property
#region ImageUri Property
// The TemplateBinding in the template can't coerce a string to an
// ImageSource, so we have to make that happen elsewhere.
public ImageSource ImageUri
{
get { return (ImageSource)GetValue(ImageUriProperty); }
set { SetValue(ImageUriProperty, value); }
}
public static readonly DependencyProperty ImageUriProperty =
DependencyProperty.Register("ImageUri", typeof(ImageSource), typeof(TestControl),
new PropertyMetadata(null));
#endregion ImageUri Property
#region ImageHeight Property
public float ImageHeight
{
get { return (float)GetValue(ImageHeightProperty); }
set { SetValue(ImageHeightProperty, value); }
}
public static readonly DependencyProperty ImageHeightProperty =
DependencyProperty.Register("ImageHeight", typeof(float), typeof(TestControl),
new PropertyMetadata(float.NaN));
#endregion ImageHeight Property
#region ImageWidth Property
public float ImageWidth
{
get { return (float)GetValue(ImageWidthProperty); }
set { SetValue(ImageWidthProperty, value); }
}
public static readonly DependencyProperty ImageWidthProperty =
DependencyProperty.Register("ImageWidth", typeof(float), typeof(TestControl),
new PropertyMetadata(float.NaN));
#endregion ImageWidth Property
}
#region This stuff belongs in a different file
public static class UIUtilities
{
public static InputMode GetInputMode()
{
// Here you'd do whatever you're already doing to detect the input mode
return InputMode.Touch;
}
}
public enum InputMode
{
Mouse,
Touch
}
#endregion This stuff belongs in a different file
}

How to use ColumnDefinitions.Add correctly?

I copied an example about dock panel from "WPF 4.5 Unleashed" ch.5. In the example the dock is set to be in the right of the grid So when in the code behind layer0.ColumnDefinitions.Add(column1CloneForLayer0); called, a column with content column1CloneForLayer0 will be created in the right side of the grid.
In my project I almost did the same thing, but when I call the function to add a column with some content, some empty space's created at the right side.. which is not as expected.So what's the right thing to do?
1.How to Add a column in the left side of a grid?
2.In the example the column was created yet why it's empty? Maybe Do i need to set the Z Index?
Update
I added something similar in the original example in the book and get wrong result. Below is the the source codes. I just added a leftToolBox in the left.
https://www.dropbox.com/sh/61hm139j77kz9k1/AACKqhG5uXFkQgnt8fWi4NvNa?dl=0
Update2
Relevant codes: In this case I just add a stackpanel with button in the left side and and click to call DocakPane(3), instead of giving the new column in the left, it creates column in the right.
public void DockPane(int paneNumber)
{
if (paneNumber == 1)
{
pane1Button.Visibility = Visibility.Collapsed;
pane1PinImage.Source = new BitmapImage(new Uri("pin.gif", UriKind.Relative));
// Add the cloned column to layer 0:
layer0.ColumnDefinitions.Add(column1CloneForLayer0);
// Add the cloned column to layer 1, but only if pane 2 is docked:
if (pane2Button.Visibility == Visibility.Collapsed) layer1.ColumnDefinitions.Add(column2CloneForLayer1);
}
else if (paneNumber == 2)
{
pane2Button.Visibility = Visibility.Collapsed;
pane2PinImage.Source = new BitmapImage(new Uri("pin.gif", UriKind.Relative));
// Add the cloned column to layer 0:
layer0.ColumnDefinitions.Add(column2CloneForLayer0);
// Add the cloned column to layer 1, but only if pane 1 is docked:
if (pane1Button.Visibility == Visibility.Collapsed) layer1.ColumnDefinitions.Add(column2CloneForLayer1);
}
else
{
leftpane1Button.Visibility = Visibility.Collapsed;
pane3PinImage.Source = new BitmapImage(new Uri("pin.gif", UriKind.Relative));
layer0.ColumnDefinitions.Add(testcol);
}
}
Based on the sample project provided, here is a re-write of the same using MVVM and most of the problem related to hard-coding would are gone. It is not pure MVVM but it is mostly MVVM to get started with.
start by defining ViewModels
a class to represent the dock pane
class DockablePaneVM : ViewModelBase
{
public DockablePaneVM(DockHostVM host)
{
Host = host;
}
private string _title;
public string Title
{
get { return _title; }
set
{
_title = value;
RaisePropertyChanged("Title");
}
}
private bool _isPinned;
public bool IsPinned
{
get { return _isPinned; }
set
{
_isPinned = value;
Host.PinModeChanged(this);
RaisePropertyChanged("IsPinned");
}
}
private object _content;
public object Content
{
get { return _content; }
set
{
_content = value;
RaisePropertyChanged("Content");
}
}
public DockHostVM Host { get; private set; }
public Dock Dock { get; set; }
}
host for the dockpanes, I have used collectionview for simplifying the location
class DockHostVM : ViewModelBase
{
public DockHostVM()
{
Panes = new ObservableCollection<DockablePaneVM>();
LeftPanes = new CollectionViewSource() { Source = Panes }.View;
RightPanes = new CollectionViewSource() { Source = Panes }.View;
FlotingLeftPanes = new CollectionViewSource() { Source = Panes }.View;
FlotingRightPanes = new CollectionViewSource() { Source = Panes }.View;
LeftPanes.Filter = o => Filter(o, Dock.Left, true);
RightPanes.Filter = o => Filter(o, Dock.Right, true);
FlotingLeftPanes.Filter = o => Filter(o, Dock.Left, false);
FlotingRightPanes.Filter = o => Filter(o, Dock.Right, false);
}
private bool Filter(object obj, Dock dock, bool isPinned)
{
DockablePaneVM vm = obj as DockablePaneVM;
return vm.Dock == dock && vm.IsPinned == isPinned;
}
public ObservableCollection<DockablePaneVM> Panes { get; set; }
public ICollectionView LeftPanes { get; set; }
public ICollectionView RightPanes { get; set; }
public ICollectionView FlotingLeftPanes { get; set; }
public ICollectionView FlotingRightPanes { get; set; }
private object _content;
public object Content
{
get { return _content; }
set { _content = value; }
}
public void PinModeChanged(DockablePaneVM paneVM)
{
LeftPanes.Refresh();
RightPanes.Refresh();
FlotingLeftPanes.Refresh();
FlotingRightPanes.Refresh();
}
//sample generator
public static DockHostVM GetSample()
{
DockHostVM vm = new DockHostVM();
vm.Panes.Add(new DockablePaneVM(vm) { Title = "Left Toolbox", Content = new ToolBoxVM() });
vm.Panes.Add(new DockablePaneVM(vm) { Title = "Solution Explorer", Content = new SolutionExplorerVM(), Dock = Dock.Right });
vm.Panes.Add(new DockablePaneVM(vm) { Title = "Toolbox", Content = new ToolBoxVM(), Dock = Dock.Right });
return vm;
}
}
then styles, to give provide the view for the classes
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:VisualStudioLikePanes">
<DataTemplate DataType="{x:Type l:DockablePaneVM}">
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<TextBlock Padding="8,4"
Text="{Binding Title}"
TextTrimming="CharacterEllipsis"
Background="{DynamicResource {x:Static SystemColors.ActiveCaptionBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.ActiveCaptionTextBrushKey}}" />
<ToggleButton IsChecked="{Binding IsPinned}"
Grid.Column="1">
<Image Name="pinImage"
Source="pinHorizontal.gif" />
</ToggleButton>
<ContentControl Content="{Binding Content}"
Grid.Row="1"
Grid.ColumnSpan="2" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsPinned}"
Value="True">
<Setter Property="Source"
TargetName="pinImage"
Value="pin.gif" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<Style TargetType="TabItem">
<Setter Property="Header"
Value="{Binding Title}" />
</Style>
<Style TargetType="TabItem"
x:Key="FloterItem"
BasedOn="{StaticResource {x:Type TabItem}}">
<Setter Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="90" />
</Setter.Value>
</Setter>
</Style>
<Style TargetType="TabControl">
<Setter Property="l:TabControlHelper.IsLastItemSelected"
Value="True" />
<Style.Triggers>
<Trigger Property="HasItems"
Value="false">
<Setter Property="Visibility"
Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="TabControl"
x:Key="AutoResizePane"
BasedOn="{StaticResource {x:Type TabControl}}">
<Style.Triggers>
<Trigger Property="IsMouseOver"
Value="false">
<Setter Property="Width"
Value="23" />
</Trigger>
</Style.Triggers>
</Style>
<DataTemplate DataType="{x:Type l:ToolBoxVM}">
<ListBox Padding="10"
Grid.Row="1">
<ListBoxItem>Button</ListBoxItem>
<ListBoxItem>CheckBox</ListBoxItem>
<ListBoxItem>ComboBox</ListBoxItem>
<ListBoxItem>Label</ListBoxItem>
<ListBoxItem>ListBox</ListBoxItem>
</ListBox>
</DataTemplate>
<DataTemplate DataType="{x:Type l:SolutionExplorerVM}">
<TreeView Grid.Row="2">
<TreeViewItem Header="My Solution" IsExpanded="True">
<TreeViewItem Header="Project #1" />
<TreeViewItem Header="Project #2" />
<TreeViewItem Header="Project #3" />
</TreeViewItem>
</TreeView>
</DataTemplate>
</ResourceDictionary>
I have also created some dummy classes to represent the toolbox and solution explorer
also a helper class to improve the usability of tab control which i have used to host the dockpanes
class TabControlHelper
{
public static bool GetIsLastItemSelected(DependencyObject obj)
{
return (bool)obj.GetValue(IsLastItemSelectedProperty);
}
public static void SetIsLastItemSelected(DependencyObject obj, bool value)
{
obj.SetValue(IsLastItemSelectedProperty, value);
}
// Using a DependencyProperty as the backing store for IsLastItemSelected. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsLastItemSelectedProperty =
DependencyProperty.RegisterAttached("IsLastItemSelected", typeof(bool), typeof(TabControlHelper), new PropertyMetadata(false, OnIsLastItemSelected));
private static void OnIsLastItemSelected(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TabControl tc = d as TabControl;
tc.Items.CurrentChanged += (ss, ee) =>
{
if (tc.SelectedIndex < 0 && tc.Items.Count > 0)
tc.SelectedIndex = 0;
};
}
}
this will keep an item selected any time, in this project it will be used when a dock pane is pinned/unpinned
now the main window, note that I have bounded the dock Panes to 4 tab controls, left, right, left float & right float
<Window x:Class="VisualStudioLikePanes.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="500"
Width="800"
WindowStartupLocation="CenterScreen"
xmlns:l="clr-namespace:VisualStudioLikePanes">
<Window.DataContext>
<ObjectDataProvider MethodName="GetSample"
ObjectType="{x:Type l:DockHostVM}" />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Menu>
<MenuItem Header="FILE" />
...
<MenuItem Header="HELP" />
</Menu>
<DockPanel LastChildFill="True"
Grid.Row="1">
<Border Width="23"
DockPanel.Dock="Left"
Visibility="{Binding Visibility, ElementName=LeftFloter}" />
<Border Width="23"
DockPanel.Dock="Right"
Visibility="{Binding Visibility, ElementName=RightFloter}" />
<TabControl ItemsSource="{Binding LeftPanes}"
DockPanel.Dock="Left" />
<TabControl ItemsSource="{Binding RightPanes}"
DockPanel.Dock="Right" />
<Grid Name="layer0">
... page content
</Grid>
</DockPanel>
<TabControl ItemsSource="{Binding FlotingLeftPanes}"
Grid.Row="1"
HorizontalAlignment="Left"
TabStripPlacement="Left"
Style="{StaticResource AutoResizePane}"
ItemContainerStyle="{StaticResource FloterItem}"
x:Name="LeftFloter" />
<TabControl ItemsSource="{Binding FlotingRightPanes}"
Grid.Row="1"
HorizontalAlignment="Right"
TabStripPlacement="Right"
Style="{StaticResource AutoResizePane}"
ItemContainerStyle="{StaticResource FloterItem}"
x:Name="RightFloter" />
</Grid>
</Window>
result is your expected behavior with MVVM approach, adding new panel is easy as Panes.Add(new DockablePaneVM(vm) { Title = "Left Toolbox", Content = new ToolBoxVM() }); rest is handled.
Demo
download the working sample VisualStudioLikePanes.zip

Changing color of a Chart Dynamically in WPF using MVVM and Databinding

I'm trying to build a Chart, with Columns. The columns will have different colors by value.
I'm using MVVM with WPF and Databinding.
How i'm trying to do (Into my ViewModel):
private Int16 colorValue;
public Int16 ColorValue
{
get { return colorValue; }
set
{
colorValue = value;
if (ColorValue < 20)
ColorType = new SolidColorBrush { Color = Colors.Aqua };
if (ColorValue < 40)
ColorType = new SolidColorBrush { Color = Colors.Gray };
if (ColorValue >= 41)
ColorType = new SolidColorBrush { Color = Colors.Black };
}
}
private Brush colorType;
public Brush ColorType
{
get { return colorType; }
set
{
if (value != null)
{
colorType = value;
OnPropertyChanged("ColorType");
}
}
}
Into My Xaml (This is the Static Resource to change the Column Color Attribute):
<Style x:Key="ColorByGradeColumn" TargetType="DVC:ColumnDataPoint">
<Setter Property="Background" Value="DarkGray"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate
TargetType="DVC:ColumnDataPoint">
<Border
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid Background="{Binding ColorType}">
<Rectangle>
<Rectangle.Fill>
<LinearGradientBrush>
<GradientStop Color="#77ffffff" Offset="0"/>
<GradientStop Color="#00ffffff" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Border BorderBrush="#ccffffff" BorderThickness="1">
<Border BorderBrush="#77ffffff" BorderThickness="1"/>
</Border>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
My Chart into Xaml:
<Grid Grid.Column="2" Height="368" HorizontalAlignment="Left" Name="grid1" VerticalAlignment="Bottom" Width="1009" Grid.ColumnSpan="4" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="198*" />
<ColumnDefinition Width="191*" />
</Grid.ColumnDefinitions>
<DVC:Chart x:Name="ColumnChart"
Grid.ColumnSpan="2">
<DVC:ColumnSeries
AnimationSequence="FirstToLast"
FlowDirection="LeftToRight"
Title="Largura"
ItemsSource="{Binding Path=Placas, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ToolTip="{Binding Path=Slab.InfThick}"
DependentValueBinding="{Binding Path=Slab.InfThick}"
IndependentValueBinding="{Binding Path=Slab.SlabId}"
DataPointStyle="{StaticResource ColorByGradeColumn}">
</DVC:ColumnSeries>
</DVC:Chart>
</Grid>
So... My Chart using ColumnSeries get it's attribute by the Static Resource defined into DataPointStyle. StaticResource 'ColorByGradeColumn' i've made a binding to my property ColorType.
Here's the question... Why isn't working? I've followed the steps explained in this link:
Columns of a different color [Customizing the appearance of Silverlight charts with re-templating and MVVM]
And I really don't know what i'm missing.
Thanks in advance.
Maybe I'm misunderstanding, but aren't you covering your grid background colour with a fixed gradient fill?
Ok, we've figured out how to fix that annoying thing:
We've created a converter than will receive the value, and returns the color than we wanted. Before, I was trying to do with a Property:
#region Converters
/// <summary>
/// Retorna a cor do estado da placa
/// </summary>
public class RetornaCorEstadoBarra : DependencyObject, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
var ColorValue = (Int32)value;
if (ColorValue < 800)
return "Aqua";
else if (ColorValue < 1000)
return "Gray";
else //if (ColorValue > 1001)
return "Black";
}
catch
{
return "Black";
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
#endregion
Created a Resource inside xaml file:
<vm:RetornaCorEstadoBarra x:Key="RetornaCorEstadoBarra" />
And created a Style a little below that Resource:
<Style x:Key="ColorByGradeColumn" TargetType="DVC:ColumnDataPoint">
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="{x:Type DVC:ColumnDataPoint}">
<Border
BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
Background="{Binding Slab.InfThick,Converter={StaticResource RetornaCorEstadoBarra}}"
>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Ok, now's the catch:
<DVC:Chart x:Name="ColumnChart" Grid.ColumnSpan="2" Width="{Binding Path=GridWidthSize}" >
<DVC:ColumnSeries
AnimationSequence="FirstToLast" FlowDirection="LeftToRight" Title="Largura" ItemsSource="{Binding Path=Placas, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ToolTip="{Binding Path=Slab.SlabId}" DependentValueBinding="{Binding Path=Slab.InfThick}" IndependentValueBinding="{Binding Path=Slab.SlabId}"
DataPointStyle="{StaticResource ColorByGradeColumn}">
<DVC:ColumnSeries.IndependentAxis>
<DVC:CategoryAxis Orientation="X" Visibility="Visible" Foreground="Transparent"/>
</DVC:ColumnSeries.IndependentAxis>
</DVC:ColumnSeries>
</DVC:Chart>
</ScrollViewer>
Well, the problem is, the color into the Chart is a Static Resource. It doesn't change a second time. So, with a 'improvised' dynamic resource, the problem is fixed, right here:
Background="{Binding Slab.InfThick,Converter={StaticResource RetornaCorEstadoBarra}}"
We pass the parameter to that Converter RetornaCorEstadoBarra. It will receive the parameter, and return a color value. Then, inside my Chart, than Binds to my Resource, will populate the Chart with the value received by my Converter. But the Chart only gets the value once. However, my converter will always return a value when he receive a value. That's the catch.
Thanks for the help :)

Resources