I have developed a user control. The user control is like a magnifier glass .
The user control has an image button which shows images cropped pixel by pixel .
StorageFile storageFile =
await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/wallpaper.jpg", UriKind.RelativeOrAbsolute));
using (Windows.Storage.Streams.IRandomAccessStream fileStream = await storageFile.OpenAsync(FileAccessMode.Read))
{
BitmapImage bitmapImage = new BitmapImage();
await bitmapImage.SetSourceAsync(fileStream);
WriteableBitmap writeableBitmap =
new WriteableBitmap(bitmapImage.PixelWidth, bitmapImage.PixelHeight);
fileStream.Seek(0);
await writeableBitmap.SetSourceAsync(fileStream);
writeableBitmap = writeableBitmap.Crop(Convert.ToInt32(xValue), Convert.ToInt32(yValue), 100, 100);
MagnifyTip.image1.ImageSource = writeableBitmap;
Now the MagnifyTip.image1 has an image source that is set to a cropped image .
My requirenment is to zoom the cropped region and then assign it to the image source.
The user control looks like this
Help would be appreciated
Maybe this works for you, it is as efficient as WPF allows I suppose since there is no image cropping in the code, it just uses the RenderTransform to do the magic. Run the code below and press the mouse over the image so the magnifying glass appears like this:
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Width="512"
Height="512">
<Grid>
<Canvas MouseDown="FullImage_OnMouseDown"
MouseMove="FullImage_OnMouseMove"
MouseUp="FullImage_OnMouseUp">
<Image Name="FullImage"
Source="http://www.mupin.it/wp-content/uploads/2012/06/lenna1.png" />
<Border Name="BorderZoom"
Visibility="Visible"
Width="{Binding ImageZoomSize, FallbackValue='200'}"
Height="{Binding ImageZoomSize, FallbackValue='200'}">
<Border.Clip>
<EllipseGeometry RadiusX="{Binding ImageZoomSizeHalf, FallbackValue=100}"
RadiusY="{Binding ImageZoomSizeHalf, FallbackValue=100}"
Center="{Binding CenterPoint, FallbackValue='100,100'}">
</EllipseGeometry>
</Border.Clip>
<Image Source="{Binding ElementName=FullImage, Path=Source}"
RenderTransformOrigin="0.5,0.5">
<Image.RenderTransform>
<TransformGroup>
<TranslateTransform X="{Binding Xt}"
Y="{Binding Yt}" />
<ScaleTransform ScaleX="{Binding ZoomFactor, FallbackValue='8'}"
ScaleY="{Binding ZoomFactor, FallbackValue='8'}" />
</TransformGroup>
</Image.RenderTransform>
</Image>
</Border>
</Canvas>
</Grid>
</Window>
And this is the code behind:
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Imaging;
namespace WpfApplication1
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
ZoomFactor = 8;
ImageZoomSize = 200;
InitializeComponent();
BorderZoom.Visibility = Visibility.Hidden;
}
public double Xt { get; private set; }
public double Yt { get; private set; }
public double ZoomFactor { get; private set; }
public int ImageZoomSize { get; private set; }
public int ImageZoomSizeHalf { get { return ImageZoomSize/2; } }
public Point CenterPoint { get { return new Point(ImageZoomSizeHalf, ImageZoomSizeHalf);} }
private void FullImage_OnMouseDown(object sender, MouseButtonEventArgs e)
{
BorderZoom.Visibility = Visibility.Visible;
FullImage_OnMouseMove(sender, e);
}
private void FullImage_OnMouseMove(object sender, MouseEventArgs e)
{
if (BorderZoom.Visibility == Visibility.Visible)
{
BorderZoom.Visibility = Visibility.Visible;
var pos = e.GetPosition(FullImage);
Canvas.SetLeft(BorderZoom, pos.X - ImageZoomSizeHalf);
Canvas.SetTop(BorderZoom, pos.Y - ImageZoomSizeHalf);
var isrc = FullImage.Source as BitmapSource;
if(isrc == null) return;
var h = (double)isrc.PixelHeight;
var w = (double)isrc.PixelWidth;
Xt = pos.X* (-ImageZoomSize/w) + ImageZoomSize/2.0;
Yt = pos.Y * (-ImageZoomSize / h) + ImageZoomSize / 2.0;
OnNotifyPropertyChanged("Xt");
OnNotifyPropertyChanged("Yt");
}
}
private void FullImage_OnMouseUp(object sender, MouseButtonEventArgs e)
{
BorderZoom.Visibility = Visibility.Hidden;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnNotifyPropertyChanged(string propName)
{
if(PropertyChanged!= null) PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
UPDATE
As requested see the code below wrapping the magnifying tip in a user control that looks like this:
XAML for MagifiyingTipCtrl:
<UserControl x:Class="WpfApplication1.MagifiyingTipCtrl"
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"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid Name="ZoomedArea" VerticalAlignment="Top"
Visibility="Visible"
Margin="15,15"
Width="{Binding ZoomWidth, FallbackValue='136'}"
Height="{Binding ZoomHeight, FallbackValue='128'}">
<Grid.Clip>
<EllipseGeometry RadiusX="{Binding ZoomWidthHalf, FallbackValue=68}"
RadiusY="{Binding ZoomHeightHalf, FallbackValue=64}"
Center="{Binding CenterPoint, FallbackValue='100,100'}">
</EllipseGeometry>
</Grid.Clip>
<Image Source="{Binding SourceImage}"
RenderTransformOrigin="0.5,0.5">
<Image.RenderTransform>
<TransformGroup>
<TranslateTransform X="{Binding Xt}"
Y="{Binding Yt}" />
<ScaleTransform ScaleX="{Binding ZoomFactor, FallbackValue='8'}"
ScaleY="{Binding ZoomFactor, FallbackValue='8'}" />
</TransformGroup>
</Image.RenderTransform>
</Image>
</Grid>
<Path Data="M25.533,0C15.457,0,7.262,8.199,7.262,18.271c0,9.461,13.676,19.698,17.63,32.338 c0.085,0.273,0.34,0.459,0.626,0.457c0.287-0.004,0.538-0.192,0.619-0.467c3.836-12.951,17.666-22.856,17.667-32.33 C43.803,8.199,35.607,0,25.533,0z M25.533,32.131c-7.9,0-14.328-6.429-14.328-14.328c0-7.9,6.428-14.328,14.328-14.328 c7.898,0,14.327,6.428,14.327,14.328C39.86,25.702,33.431,32.131,25.533,32.131z"
Fill="#FFF4F4F5"
Stretch="Fill"
Stroke="Black"
UseLayoutRounding="False"
Height="227"
Width="171" />
</Grid>
</UserControl>
Code-behind for MagifiyingTipCtrl:
using System.Windows.Media.Imaging;
namespace WpfApplication1
{
public partial class MagifiyingTipCtrl : UserControl
{
public MagifiyingTipCtrl()
{
ZoomFactor = 8;
ZoomWidth = 136;
ZoomHeight = 128;
InitializeComponent();
}
public static readonly DependencyProperty SourceImageProperty =
DependencyProperty.Register("SourceImage", typeof (BitmapSource), typeof (MagifiyingTipCtrl));
public static readonly DependencyProperty XtProperty =
DependencyProperty.Register("Xt", typeof(double), typeof(MagifiyingTipCtrl));
public static readonly DependencyProperty YtProperty =
DependencyProperty.Register("Yt", typeof(double), typeof(MagifiyingTipCtrl));
public BitmapSource SourceImage
{
get { return (BitmapSource)GetValue(SourceImageProperty); }
set { SetValue(SourceImageProperty, value); }
}
public double Xt
{
get { return (double)GetValue(XtProperty); }
set { SetValue(XtProperty, value); }
}
public double Yt
{
get { return (double)GetValue(YtProperty); }
set { SetValue(YtProperty, value); }
}
public void SetPosition(Point pos)
{
if (SourceImage == null) return;
var h = (double)SourceImage.PixelHeight;
var w = (double)SourceImage.PixelWidth;
Xt = pos.X * (-ZoomWidth / w) + ZoomWidth / 2.0;
Yt = pos.Y * (-ZoomHeight / h) + ZoomHeight / 2.0;
}
public double ZoomFactor { get; private set; }
public int ZoomWidth { get; private set; }
public int ZoomHeight { get; private set; }
public int ZoomWidthHalf { get { return ZoomWidth / 2; } }
public int ZoomHeightHalf { get { return ZoomHeight / 2; } }
public Point CenterPoint { get { return new Point(ZoomWidthHalf, ZoomHeightHalf); } }
}
}
XAML for the MainWindow:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Width="512"
Height="512">
<Grid>
<Canvas MouseDown="FullImage_OnMouseDown"
MouseMove="FullImage_OnMouseMove"
MouseUp="FullImage_OnMouseUp">
<Image Name="FullImage"
Source="http://www.mupin.it/wp-content/uploads/2012/06/lenna1.png" />
<wpfApplication1:MagifiyingTipCtrl x:Name="MagnifiyingTip"
SourceImage="{Binding ElementName=FullImage, Path=Source}" />
</Canvas>
</Grid>
</Window>
Code-behind for MainWindow:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void FullImage_OnMouseDown(object sender, MouseButtonEventArgs e)
{
MagnifiyingTip.Visibility = Visibility.Visible;
FullImage_OnMouseMove(sender, e);
}
private void FullImage_OnMouseMove(object sender, MouseEventArgs e)
{
if (MagnifiyingTip.Visibility == Visibility.Visible)
{
MagnifiyingTip.Visibility = Visibility.Visible;
var pos = e.GetPosition(FullImage);
Canvas.SetLeft(MagnifiyingTip, pos.X - MagnifiyingTip.ActualWidth/2);
Canvas.SetTop(MagnifiyingTip, pos.Y - MagnifiyingTip.ActualHeight);
MagnifiyingTip.SetPosition(pos);
}
}
private void FullImage_OnMouseUp(object sender, MouseButtonEventArgs e)
{
MagnifiyingTip.Visibility = Visibility.Hidden;
}
}
}
Just as i wrote in my comment, a quick demoapp in WPF for your PictureZoom.
https://github.com/hrkrx/PictureZoomExample
its just an examle so there can be a lot optimized, but i hope it is helping you
Related
I am looking for a wrappanel or rather something that works similarly. The wrappanel grabs the Uielement in a new line, but the elements are not dynamic in width, but static. The setting »HorizontalAlignment=" Stretch"« does not work and the elements look squeezed together. That means you have to put the width manually »Width="200"« and that is very static. So when I change the width of my window, more elements come into one line, but the existing elements do not adapt to the width of the window. I would have liked to fill out the elements for the entire width. Overall, it also sees much better.
I made a user control that fits my purpose but it can’t be virtualized and is really slow with the resize.
Here is my code for the adaptable UserControl.
<UserControl x:Class="ItemViewer"
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"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800"
Loaded="UserControl_Loaded"
x:Name="uc">
<Grid DataContext="{Binding ElementName=uc}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*"
x:Name="SecondRow" />
</Grid.RowDefinitions>
<TextBlock x:Name="Header"
Text="{Binding HeaderText,Mode=OneWay}"
Foreground="White"
HorizontalAlignment="Left"
Margin="0,0,0,10"
FontSize="28"
FontWeight="Medium" />
<ItemsControl Grid.Row="2"
x:Name="ListViewProducts"
SizeChanged="Grid_SizeChanged"
HorizontalAlignment="Stretch"
ItemsSource="{Binding Items,Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Height="{Binding Path=DataContext.ItemHeight, RelativeSource={RelativeSource AncestorType=ItemsControl},Mode=OneTime}"
MaxWidth="{Binding Path=DataContext.ItemMaxWidth, RelativeSource={RelativeSource AncestorType=ItemsControl},Mode=OneTime}"
Margin="0,0,10,10">
<Border Background="Beige"
CornerRadius="10" />
<!-- The specific UserControl or an element -->
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="2"
Initialized="UniformGrid_Initialized" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="HorizontalAlignment"
Value="Stretch" />
<Setter Property="VerticalAlignment"
Value="Top" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
</UserControl>
The code behind:
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Interop;
namespace MVVM.View.HomeView
{
/// <summary>
/// Interaktionslogik für ItemViewer.xaml
/// </summary>
public partial class ItemViewer : UserControl
{
private UniformGrid? _itemUniformGrid;
public static readonly DependencyProperty HeaderTextProperty = DependencyProperty.Register(nameof(HeaderText), typeof(string), typeof(ItemViewer));
public string HeaderText
{
get { return (string)GetValue(HeaderTextProperty); }
set { SetValue(HeaderTextProperty, value); }
}
public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register("ItemHeight", typeof(double), typeof(ItemViewer));
public double ItemHeight
{
get { return (double)GetValue(ItemHeightProperty); }
set { SetValue(ItemHeightProperty, value); }
}
public static readonly DependencyProperty ItemMinWidthProperty = DependencyProperty.Register("ItemMinWidth", typeof(double), typeof(ItemViewer));
public double ItemMinWidth { get { return (double)GetValue(ItemMinWidthProperty); } set { SetValue(ItemMinWidthProperty, value); List_SizeChanged(); } }
public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register("ItemWidth", typeof(double), typeof(ItemViewer));
public double ItemWidth { get { return (double)GetValue(ItemWidthProperty); } set { SetValue(ItemWidthProperty, value); List_SizeChanged(); } }
public static readonly DependencyProperty ItemMaxWidthProperty = DependencyProperty.Register("ItemMaxWidth", typeof(double), typeof(ItemViewer));
public double ItemMaxWidth { get { return (double)GetValue(ItemMaxWidthProperty); } set { SetValue(ItemMaxWidthProperty, value); List_SizeChanged(); } }
private double row = 0;
public double Row { get { return row; } set { row = (value >= 0 ? value : 0); if (Row != 0) { SecondRow.MaxHeight = 10 * (Row - 1) + ItemHeight * Row; } } }
public ItemViewer()
{
InitializeComponent();
}
private int itemCounter = 0;
public ObservableCollection<object> Items
{
get { return (ObservableCollection<object>)GetValue(ItemsProperty); }
set => SetValue(ItemsProperty, value);
}
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(ObservableCollection<object>), typeof(ItemViewer));
private const int WmExitSizeMove = 0x232;
private IntPtr HwndMessageHook(IntPtr wnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case WmExitSizeMove:
List_SizeChanged();
handled = true;
break;
}
return IntPtr.Zero;
}
private void List_SizeChanged()
{
if (Items == null || Items.Count == 0 || _itemUniformGrid == null)
return;
itemCounter = Items.Count;
int columns = (int)(ListViewProducts.ActualWidth / (10 + ItemWidth));
if (columns <= itemCounter && ListViewProducts.ActualWidth < (ItemMaxWidth + 10) * columns)
{
if (_itemUniformGrid.HorizontalAlignment != HorizontalAlignment.Stretch)
{
_itemUniformGrid.ClearValue(WidthProperty);
_itemUniformGrid.HorizontalAlignment = HorizontalAlignment.Stretch;
}
if (columns != _itemUniformGrid.Columns)
_itemUniformGrid.Columns = columns;
}
else
{
if (columns >= itemCounter)
{
double newWidth;
newWidth = ListViewProducts.ActualWidth / itemCounter;
newWidth = newWidth > ItemMaxWidth ? ItemMaxWidth : newWidth;
_itemUniformGrid.Width = (newWidth + 10) * itemCounter;
_itemUniformGrid.HorizontalAlignment = HorizontalAlignment.Left;
_itemUniformGrid.Columns = itemCounter;
}
else
{
_itemUniformGrid.HorizontalAlignment = HorizontalAlignment.Left;
_itemUniformGrid.Columns = (int)(ListViewProducts.ActualWidth / (ItemMaxWidth + 10));
_itemUniformGrid.Width = (ItemMaxWidth + 10) * _itemUniformGrid.Columns;
}
}
}
private void UniformGrid_Initialized(object sender, EventArgs e) =>
_itemUniformGrid = (UniformGrid)sender;
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
List_SizeChanged();
if (Application.Current.MainWindow == null)
return;
WindowInteropHelper? helper = new(Application.Current.MainWindow);
HwndSource? source = HwndSource.FromHwnd(helper.Handle);
if (source != null)
source.AddHook(HwndMessageHook);
}
private int sizeP = 0;
private void Grid_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (sizeP - e.NewSize.Width is < (-200) or > 200)
{
sizeP = (int)e.NewSize.Width;
List_SizeChanged();
}
else if (Application.Current.MainWindow.WindowState == WindowState.Maximized && sizeP != (int)e.NewSize.Width)
{
sizeP = (int)e.NewSize.Width;
List_SizeChanged();
}
}
}
}
Implementation:
<local:ItemViewer Items="{Binding Objects}"
HeaderText="Recent"
ItemMinWidth="200"
ItemWidth="350"
ItemHeight="150"
ItemMaxWidth="500"
Row="1" />
To make it more responsive I call the resized only when the resize finished. I think it doesn't break with mvvm because it is only for the UI. But in the end, it is quite a bad implementation.
So, is there a better way or good code on GitHub that has a similar behaviour? Here are a few Images of an implementation of my code:
The Width is set dynamically and it wraps when there is enough space.
Here my implementation of my UserControl. The Broders have a dynamic width and also can wrap, but is realy slow.
Here the same with a WrapPanel. Static width and fast wrapping
I have a UserControl which contains a InkToolbar. In this control I'm generating another set of UserControls based on user input. In each of those programmatically generated controls, contains a InkCanvas.
This is the parent UserControl & the code behind.
Main.xaml
<UserControl
x:Class="uwp.Main"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:uwp_mvvm"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0">
<InkToolbar x:Name="InkToolbar" Background="Black" Margin="10 0 0 0" VerticalAlignment="Center"/>
</StackPanel>
<ScrollViewer ZoomMode="Enabled" Background="DarkGray" x:Name="ScrollViewer" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" MinZoomFactor="0.25" Width="Auto" Height="Auto" MaxZoomFactor="4" Grid.Row="1">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
<ItemsControl x:Name="PagesItemsControl" ItemsSource="{x:Bind Pages, Mode=OneWay}" HorizontalAlignment="Center" VerticalAlignment="Top">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:TestControl x:Name="TestControl" Page="{Binding}" Margin="4 4" InkToolbarControl="{Binding Path=InkToolbar, ElementName=InkToolbar}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</Grid>
</UserControl>
Main.xaml.cs
public sealed partial class MainViewer : UserControl
{
public MainViewer()
{
this.InitializeComponent();
}
private void EventTriggerChanged(object sender, PropertyChangedEventArgs e)
{
var trigger = sender as EventTriggerTestControl;
if (trigger != null)
RaiseChanged(e.PropertyName);
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
}
private PdfDocument pdfDocument = null;
private string password = string.Empty;
private ObservableCollection<PdfPage> pages = new ObservableCollection<PdfPage>();
public string Password
{
private get { return password; }
set { if (value != password) { password = value; } }
}
public ObservableCollection<PdfPage> Pages
{
get { return pages; }
set { if (value != pages) { pages = value; } }
}
public StorageFile File
{
get { return (StorageFile)GetValue(FileProperty); }
set { SetValue(FileProperty, value); }
}
public static readonly DependencyProperty FileProperty =
DependencyProperty.Register(nameof(File), typeof(StorageFile), typeof(MainViewer), new PropertyMetadata(null, new PropertyChangedCallback(OnDocumentChanged)));
private static void OnDocumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (object.Equals(e.NewValue, e.OldValue) || e.NewValue is null)
return;
d.RegisterPropertyChangedCallback(FileProperty, CaptureDocument);
}
private async static void CaptureDocument(DependencyObject sender, DependencyProperty dp) => await (sender as MainViewer).OpenFileAsync(sender.GetValue(dp) as StorageFile);
private async Task OpenFileAsync(StorageFile file)
{
try
{
if (file == null)
throw new ArgumentNullException(nameof(file));
var files = await ApplicationData.Current.LocalFolder.GetFilesAsync(CommonFileQuery.OrderByName);
if (files.Where(x => x.Name == file.Name).ToList().Count == 0)
await file.CopyAsync(ApplicationData.Current.LocalFolder, file.Name, NameCollisionOption.ReplaceExisting);
file = await ApplicationData.Current.LocalFolder.GetFileAsync(file.Name);
pdfDocument = new PdfDocument(file.Path, Password);
pdfDocument.Pages.ToList().ForEach(x => Pages.Add(x));
}
catch (Exception ex) { throw ex; }
}
}
I want to use the InkToolbar which is in the parent control inside the TestControl
TestControl.xaml
<UserControl
x:Class="uwp.TestControl"
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="using:uwp_mvvm.ViewModels" xmlns:converter="using:uwp_mvvm"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.Resources>
<converter:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
</UserControl.Resources>
<Grid x:Name="Viewport" VerticalAlignment="Top" HorizontalAlignment="Center">
<Border x:Name="ViewportBorder" Background="White" BorderThickness="2, 2, 2, 2" BorderBrush="#FF353334" />
<Image x:Name="Image" Margin="0" UseLayoutRounding="True" ManipulationMode="Scale"/>
<Canvas x:Name="SelectionCanvas" CompositeMode="MinBlend" Opacity="1" />
<InkCanvas x:Name="InkCanvas" />
</Grid>
</UserControl>
TastControl.xaml.cs
public sealed partial class TestControl : UserControl
{
private const float DPI = 256;
public PdfPage Page
{
get { return (PdfPage)GetValue(PageProperty); }
set { SetValue(PageProperty, value); }
}
public static readonly DependencyProperty PageProperty =
DependencyProperty.Register(nameof(Page), typeof(PdfPage), typeof(TestControl), new PropertyMetadata(null, new PropertyChangedCallback(OnPageChanged)));
private static void OnPageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (object.Equals(e.NewValue, e.OldValue) || e.NewValue is null)
return;
d.RegisterPropertyChangedCallback(PageProperty, CapturePage);
}
private static void CapturePage(DependencyObject sender, DependencyProperty dp) => (sender as TestControl).RenderPage(sender.GetValue(dp) as PdfPage);
public InkToolbar InkToolbarControl
{
get { return (InkToolbar)GetValue(InkToolbarControlProperty); }
set { SetValue(InkToolbarControlProperty, value); }
}
public static readonly DependencyProperty InkToolbarControlProperty =
DependencyProperty.Register(nameof(InkToolbarControl), typeof(InkToolbar), typeof(TestControl), new PropertyMetadata(null));
public TestControl()
{
this.InitializeComponent();
ConfigureInkCanvas();
}
private void ConfigureInkCanvas()
{
InkCanvas.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Pen | CoreInputDeviceTypes.Touch;
InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.None;
InkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(
new InkDrawingAttributes
{
IgnorePressure = false,
FitToCurve = true,
Color = Colors.Black,
PenTip = PenTipShape.Circle
});
InkCanvas.InkPresenter.UnprocessedInput.PointerEntered -= InkCanvasUnprocessedInputPointerEntered;
InkCanvas.InkPresenter.UnprocessedInput.PointerEntered += InkCanvasUnprocessedInputPointerEntered;
InkCanvas.InkPresenter.UnprocessedInput.PointerExited -= InkCanvasUnprocessedInputPointerExited;
InkCanvas.InkPresenter.UnprocessedInput.PointerExited += InkCanvasUnprocessedInputPointerExited;
}
private void InkCanvasUnprocessedInputPointerExited(InkUnprocessedInput sender, PointerEventArgs args)
{
//InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.Inking;
//InkToolbarControl.TargetInkCanvas = InkCanvas;
}
private void InkCanvasUnprocessedInputPointerEntered(InkUnprocessedInput sender, PointerEventArgs args)
{
//InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.None;
//InkToolbarControl.TargetInkCanvas = null;
}
private void RenderPage(PdfPage page, float dpi = DPI)
{
if (page is null)
return;
this.DataContext = page;
var width = (DataContext as PdfPage).Width;
var height = (DataContext as PdfPage).Height;
var writableBitmap = new WriteableBitmap((int)(width / 72f * dpi), (int)(height / 72f * dpi));
(DataContext as PdfPage).Render(writableBitmap, PageOrientations.Normal, RenderingFlags.Annotations);
Image.Width = width;
Image.Height = height;
Image.Source = writableBitmap;
SelectionCanvas.Width = width;
SelectionCanvas.Height = height;
InkCanvas.Width = width;
InkCanvas.Height = height;
Canvas.SetZIndex(Image, 0);
ConfigureInkCanvas();
}
}
I tried to use a DependencyProperty to send the InkToolbar to the TestControl but it didn't seem to work.
Could someone please help me with this? I'm not sure whether it is possible to set the TargetInkCanvas like this also. So, if there is a better approach for this any suggestions are also appreciated.
Any help is much appreciated. Thanks.
Access InkToolbar placed in parent element from programmatically generated child elements
Above solution that use DependencyProperty to pass parent InkToolBar is correct. However you bind wrong value for InkToolbarControl property.
Please bind ElementName without path directly.
<local:TestControl
x:Name="TestControl"
Margin="4,4"
InkToolbarControl="{Binding ElementName=InkToolbar}" />
Then update InkCanvasUnprocessedInputPointerExited and InkCanvasUnprocessedInputPointerEntered process logic.
private void InkCanvasUnprocessedInputPointerExited(InkUnprocessedInput sender, PointerEventArgs args)
{
InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.Inking;
InkToolbarControl.TargetInkCanvas = null;
}
private void InkCanvasUnprocessedInputPointerEntered(InkUnprocessedInput sender, PointerEventArgs args)
{
InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.None;
InkToolbarControl.TargetInkCanvas = InkCanvas;
}
My solution has two projects: one is called ChartControl and the other is LineGridViewCh4. LineGridViewCh4 contains the view and viewmodel that uses the control ChartControl. I can change the property Lines and the view gets updated and updates the Lines dependency property and Lines property in ChartStyle.cs. This is working fine. What I want to make happen is communication in the other direction. For example. If the Lines property were to change (by mousedown in the red square to set the Lines property to 3) in ChartStyle.cs, I'd like to have the property Lines (in ChartStyle.cs) update the dependency property in LineChart.xaml.cs and the change reflected in the view LineGridControlView.xaml. I tried creating a binding statement in the ctor of LineChart.xaml.cs but I'm not sure how to do this or if this is the right approach. In LineChart.xaml.cs method SetLineChart() has a statement cs.Line = Line; and can only work in one direction. It seems like I need Line = cs.Line; somewhere for the reverse direction. Any help would be appreciated.
ChartStyle.cs
using Prism.Mvvm;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace ChartControl.ChartModel
{
public class ChartStyle : BindableBase
{
public Canvas ChartCanvas { get; set; }
public double Lines { get; set; }
public void AddChartStyle()
{
for (int i = 1; i <= Lines; i++)
{
double dx = i * 10;
Line gridline = new Line();
gridline.Stroke = Brushes.Black;
gridline.StrokeThickness = 4;
gridline.X1 = new Point(dx, 0).X;
gridline.Y1 = new Point(dx, 0).Y;
gridline.X2 = new Point(dx, 100).X;
gridline.Y2 = new Point(dx, 100).Y;
ChartCanvas.Children.Add(gridline);
}
Rectangle setLines = new Rectangle
{
Fill = Brushes.Red,
StrokeThickness = 2
};
setLines.Width = 30;
setLines.Height = 30;
Canvas.SetTop(setLines, 110);
ChartCanvas.Children.Add(setLines);
setLines.MouseDown += new MouseButtonEventHandler(SetLines_MouseDown);
}
private void SetLines_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.Source is Rectangle)
{
Lines = 3;
}
}
}
}
LineChart.xaml
<UserControl x:Class="ChartControl.Views.LineChart"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid >
<Canvas Name="chartCanvas"/>
</Grid>
</UserControl>
LineChart.xaml.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using ChartControl.ChartModel;
namespace ChartControl.Views
{
public partial class LineChart : UserControl
{
private ChartStyle cs;
public LineChart()
{
InitializeComponent();
cs = new ChartStyle();
cs.ChartCanvas = chartCanvas;
//Binding csLine = new Binding("Lines");
//csLine.Source = cs.Lines;
//Lines.SetBinding(LinesProperty, csLine);
}
protected override void OnRender(DrawingContext drawingContext)
{
SetLineChart();
}
private void SetLineChart()
{
cs.Lines = Lines;
RedrawLineChart();
}
private void RedrawLineChart()
{
chartCanvas.Children.Clear();
cs.AddChartStyle();
}
public static DependencyProperty LinesProperty =
DependencyProperty.Register("Lines", typeof(int),
typeof(LineChart),
new FrameworkPropertyMetadata(12, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |
FrameworkPropertyMetadataOptions.AffectsRender));
public int Lines
{
get { return (int)GetValue(LinesProperty); }
set { SetValue(LinesProperty, value); }
}
}
}
LineGridControlView.xaml
<Window x:Class="LineGridViewCh4.Views.LineGridControlView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
xmlns:local="clr-namespace:ChartControl.Views;assembly=ChartControl"
prism:ViewModelLocator.AutoWireViewModel="True"
Height="220" Width="400">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical" Grid.Column="1" Margin="5">
<StackPanel Orientation="Horizontal">
<Label Content="Lines" Width="60" Height="25"/>
<TextBox x:Name="tbLines" Width="50" Height="25" Text="{Binding Lines, ElementName=myChart}"/>
<Button Content="Set" Command="{Binding ChangeLinesCommand}" CommandParameter="{Binding Text, ElementName=tbLines}"/>
</StackPanel>
</StackPanel>
<local:LineChart x:Name="myChart" Margin="10"/>
</Grid>
</Window>
LineGridControlViewModel.cs
using Prism.Commands;
using Prism.Mvvm;
using System;
namespace LineGridViewCh4.ViewModels
{
public class LineGridControlViewModel : BindableBase
{
public DelegateCommand<string> ChangeLinesCommand { get; set; }
public LineGridControlViewModel()
{
ChangeLinesCommand = new DelegateCommand<string>(ChangeLines);
}
private int lines;
public int Lines
{
get { return lines; }
set { SetProperty(ref lines, value); }
}
public void ChangeLines(string numberOfLines)
{
Lines = Convert.ToInt32(numberOfLines);
}
}
}
Just add a PropertyChangedCallback Delegate, which represents the callback that is invoked when the effective property value of a dependency property changes.
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/dependency-property-callbacks-and-validation?view=netframeworkdesktop-4.8
I was able to address this problem through inheritance. My base class is UserControl which ChartStyle now inherits from. Below are the changes:
ChartStyle.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace ChartControl.ChartModel
{
public class ChartStyle : UserControl
{
public Canvas ChartCanvas { get; set; }
public static DependencyProperty LinesProperty =
DependencyProperty.Register("Lines", typeof(int),
typeof(ChartStyle),
new FrameworkPropertyMetadata(
12,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnPropertyChanged)));
public int Lines
{
get { return (int)GetValue(LinesProperty); }
set { SetValue(LinesProperty, value); }
}
private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
ChartStyle cs = sender as ChartStyle;
if (e.Property == LinesProperty)
cs.Lines = (int)e.NewValue;
}
public void AddChartStyle()
{
for (int i = 1; i <= Lines; i++)
{
double dx = i * 10;
Line gridline = new Line();
gridline.Stroke = Brushes.Black;
gridline.StrokeThickness = 4;
gridline.X1 = new Point(dx, 0).X;
gridline.Y1 = new Point(dx, 0).Y;
gridline.X2 = new Point(dx, 100).X;
gridline.Y2 = new Point(dx, 100).Y;
ChartCanvas.Children.Add(gridline);
}
Rectangle setLines = new Rectangle
{
Fill = Brushes.Red,
StrokeThickness = 2
};
setLines.Width = 30;
setLines.Height = 30;
Canvas.SetTop(setLines, 110);
ChartCanvas.Children.Add(setLines);
setLines.MouseDown += new MouseButtonEventHandler(SetLines_MouseDown);
}
private void SetLines_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.Source is Rectangle)
{
Lines = 3;
}
}
}
}
LineChart.xaml.cs
using ChartControl.ChartModel;
using System.Windows.Media;
namespace ChartControl.Views
{
public partial class LineChart : ChartStyle
{
public LineChart()
{
InitializeComponent();
ChartCanvas = chartCanvas;
}
protected override void OnRender(DrawingContext drawingContext)
{
RedrawLineChart();
}
private void RedrawLineChart()
{
chartCanvas.Children.Clear();
AddChartStyle();
}
}
}
LineChart.xaml
<local:ChartStyle x:Class="ChartControl.Views.LineChart"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
xmlns:prism="http://prismlibrary.com/"
xmlns:local="clr-namespace:ChartControl.ChartModel"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid >
<Canvas Name="chartCanvas"/>
</Grid>
</local:ChartStyle>
I try to use System.Windows.Interactivity to bind mouse events of elements on the screen to some command logic.
I have a simple Canvas with three circles. A command is implemented that decreases the radius of the circles. This works fine when bound to the command property of a Button.
Unfortunately, when I try to bind this command to the PreviewMouseDown event of the Canvas, it doesn't work anymore. What am I missing?
Here is the MainWindow.xaml:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:test="clr-namespace:Test"
Title="MainWindow" Height="550" Width="525">
<Window.Resources>
<test:ViewModel x:Key="viewobj"/>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding CircleItems, Source={StaticResource viewobj}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Black" ClipToBounds="True" HorizontalAlignment="Left" Height="400" Margin="50,20,0,0" VerticalAlignment="Top" Width="400">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseDown" >
<i:InvokeCommandAction Command="{Binding StartCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Width="{Binding Radius}" Height="{Binding Radius}" Fill="Red"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Button" Command="{Binding StartCommand, Source={StaticResource viewobj}}" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="189,474,0,0"/>
</Grid>
</Window>
The MainWindow.xaml.cs is empty except for initialization in accordance to MVVM principles:
using System.Windows;
namespace Test
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
This is the ViewModel.cs:
using System.Collections.ObjectModel;
using System.ComponentModel;
using Test.Model;
namespace Test
{
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<CircleItem> CircleItems { get; set; }
private ButtonCommand _StartCommand;
public ButtonCommand StartCommand
{
get { return _StartCommand; }
}
public ViewModel()
{
_StartCommand = new ButtonCommand(UpdateMap, () => {return true;});
CircleItems = new ObservableCollection<CircleItem>();
CircleItems.Add(new CircleItem(20, 20, 40));
CircleItems.Add(new CircleItem(60, 60, 50));
CircleItems.Add(new CircleItem(120, 100, 30));
}
public void UpdateMap()
{
CircleItem.UpdateMap(CircleItems);
}
internal void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
{ PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
The CircleItem.cs class:
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace Test.Model
{
public class CircleItem : INotifyPropertyChanged
{
private double _x;
public double X
{
get { return _x; }
set
{
if (_x != value)
{
_x = value;
RaisePropertyChanged("X");
}
}
}
private double _y;
public double Y
{
get { return _y; }
set
{
if (_y != value)
{
_y = value;
RaisePropertyChanged("Y");
}
}
}
private double _radius;
public double Radius
{
get { return _radius; }
set
{
if (_radius != value)
{
_radius = value;
RaisePropertyChanged("Radius");
}
}
}
public CircleItem(double x, double y, double radius)
{
this.X = x;
this.Y = y;
this.Radius = radius;
}
public static void UpdateMap(ObservableCollection<CircleItem> coll)
{
foreach (var item in coll)
{
item.Radius -= 1;
}
}
internal void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
{ PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
And a simple RelayCommand.cs class:
using System;
using System.Windows.Input;
namespace Test
{
public class ButtonCommand : ICommand
{
private Action WhattoExecute;
private Func<bool> WhentoExecute;
public ButtonCommand(Action What, Func<bool> When)
{
WhattoExecute = What;
WhentoExecute = When;
}
public bool CanExecute(object parameter)
{
return WhentoExecute();
}
public void Execute(object parameter)
{
WhattoExecute();
}
public event EventHandler CanExecuteChanged;
}
}
Please note that the NuGet Package "System.Windows.Interactivity v4.0 for WPF" has to be installed for this example to work.
You forgot to set the Source of the Binding:
<Canvas Background="Black" ClipToBounds="True" HorizontalAlignment="Left" Height="400" Margin="50,20,0,0" VerticalAlignment="Top" Width="400">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseDown">
<i:InvokeCommandAction Command="{Binding StartCommand, Source={StaticResource viewobj}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Canvas>
I need show two line series on single area. They are have share X-axis (DateTime) and different Y-axis.
Use CategoryXAxis
If I used CategoryXAxis type for X-axis than I see two series, but they are not synchronized by X-axis (you can see it on tooltip).
_categoryXAxis = new CategoryXAxis()
{
ItemsSource = enumerable,
FontSize = 10,
};
Use CategoryDateTimeXAxis
If I using CategoryDateTimeXAxis type for X-axis than I see SINGLE series, and I see two tooltip, but they are not synchronized by X-axis (you can see it on tooltip).
_categoryXAxis = new CategoryDateTimeXAxis()
{
ItemsSource = enumerable,
DateTimeMemberPath = "DateTime",
DisplayType = TimeAxisDisplayType.Continuous,
FontSize = 10,
MinimumValue = new DateTime(2000, 1, 1),
MaximumValue = new DateTime(2017, 1, 1),
};
What can I do?
The example below demonstrates how to synchronize the DateTime axis for two series:
MainWindow.xaml
<Window x:Class="xamDataChartMultipleSeries.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ig="http://schemas.infragistics.com/xaml"
Title="XamDataChart" Height="350" Width="525" >
<Grid>
<Grid.Resources>
<DataTemplate x:Key="DataTrackerTemplate">
<Ellipse Stretch="Fill" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
MinWidth="15" MinHeight="15" StrokeThickness="0.5"
Fill="{Binding Path=ActualItemBrush}" Stroke="{Binding Path=Series.ActualMarkerOutline}" >
</Ellipse>
</DataTemplate>
</Grid.Resources>
<ig:XamDataChart Name="MultipleSeriesChart" Padding="5"
OverviewPlusDetailPaneVisibility="Collapsed"
OverviewPlusDetailPaneHorizontalAlignment="Left"
OverviewPlusDetailPaneVerticalAlignment="Bottom" >
<ig:XamDataChart.Axes>
<ig:CategoryDateTimeXAxis x:Name="xAxis" Title="TIME (min)" Label="{}{DateTime:MM/dd/yyyy}" DateTimeMemberPath="DateTime" DisplayType="Discrete" >
<ig:CategoryDateTimeXAxis.LabelSettings>
<ig:AxisLabelSettings Location="OutsideBottom" Angle="40" />
</ig:CategoryDateTimeXAxis.LabelSettings>
</ig:CategoryDateTimeXAxis>
<ig:NumericYAxis x:Name="yAxis" Title="" Label="{}{0:0.##}" Interval="0.25" />
</ig:XamDataChart.Axes>
<ig:XamDataChart.Series>
<ig:LineSeries x:Name="Series1" XAxis="{Binding ElementName=xAxis}" YAxis="{Binding ElementName=yAxis}" ValueMemberPath="Series1"
MarkerType="None" Thickness="2" IsTransitionInEnabled="True" IsHighlightingEnabled="True" Brush="Blue" >
<ig:LineSeries.ToolTip>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="X= " />
<TextBlock Text="{Binding Item.DateTime}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,4,0,0">
<TextBlock Text="Y= " />
<TextBlock Text="{Binding Item.Series1, StringFormat={}{0:#,0.000}}" />
</StackPanel>
</StackPanel>
</ig:LineSeries.ToolTip>
</ig:LineSeries>
<ig:LineSeries x:Name="Series2" XAxis="{Binding ElementName=xAxis}" YAxis="{Binding ElementName=yAxis}" ValueMemberPath="Series2"
MarkerType="None" Thickness="2" IsTransitionInEnabled="True" IsHighlightingEnabled="True" Brush="Green">
<ig:LineSeries.ToolTip>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="X= " />
<TextBlock Text="{Binding Item.DateTime}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,4,0,0">
<TextBlock Text="Y= " />
<TextBlock Text="{Binding Item.Series2, StringFormat={}{0:#,0.000}}" />
</StackPanel>
</StackPanel>
</ig:LineSeries.ToolTip>
</ig:LineSeries>
<ig:CategoryItemHighlightLayer x:Name="TackingLayer" Opacity="1" MarkerTemplate="{StaticResource DataTrackerTemplate}" UseInterpolation="True"
TransitionDuration="0:00:00.1" Canvas.ZIndex="11" />
<ig:ItemToolTipLayer x:Name="ToolTipLayer" Canvas.ZIndex="12" UseInterpolation="True" TransitionDuration="0:00:00.1" />
<ig:CrosshairLayer x:Name="CrosshairLayer" Opacity="1" Thickness="1" Canvas.ZIndex="15" UseInterpolation="True" TransitionDuration="0:00:00.1"/>
</ig:XamDataChart.Series>
</ig:XamDataChart>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Windows;
using System.ComponentModel;
namespace xamDataChartMultipleSeries
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
int points = 20;
List<Point> data = new List<Point>();
public MainWindow()
{
InitializeComponent();
double x = 0;
var step = 2 * Math.PI / points;
var now = DateTime.Now;
xAxis.MinimumValue = now;
xAxis.MaximumValue = now.AddDays(points);
xAxis.Interval = new TimeSpan(0,6,0,0);
var next = now;
for (var i = 0; i < points; i++, x += step)
{
next = next.AddDays(1);
Data.Add(new ChartDataItem() { DateTime=next, Series1 = Math.Sin(x), Series2 = Math.Cos(x) });
}
if (!DesignerProperties.GetIsInDesignMode(this))
{
xAxis.ItemsSource = Series1.ItemsSource = Series2.ItemsSource = Data;
}
}
// ChartDataCollection
public ChartDataCollection Data = new ChartDataCollection();
}
}
ChartData.cs
using System;
using System.Collections.ObjectModel;
using Infragistics.Samples.Shared.Models;
namespace xamDataChartMultipleSeries
{
public class ChartDataCollection : ObservableCollection<ChartDataItem>
{
public ChartDataCollection() { }
}
public class ChartDataItem : ObservableModel
{
public ChartDataItem() { }
public ChartDataItem(ChartDataItem dataPoint)
{
this.DateTime = dataPoint.DateTime;
this.Series1 = dataPoint.Series1;
this.Series2 = dataPoint.Series2;
}
private DateTime _dt;
public DateTime DateTime
{
get { return _dt; }
set { if (_dt == value) return; _dt = value; }
}
private double _series1;
public double Series1
{
get { return _series1; }
set { if (_series1 == value) return; _series1 = value; OnPropertyChanged("Series1"); }
}
private double _series2;
public double Series2
{
get { return _series2; }
set { if (_series2 == value) return; _series2 = value; OnPropertyChanged("Series2"); }
}
public new string ToString()
{
return base.ToString();
}
}
}
The helper class included to the Infragistics examples:
ObservableModel.cs
using System.ComponentModel;
using System.Runtime.Serialization;
namespace Infragistics.Samples.Shared.Models
{
[DataContract]
public abstract class ObservableModel : INotifyPropertyChanged
{
protected ObservableModel()
{
IsPropertyNotifyActive = true;
}
#region INotifyPropertyChanged
public bool IsPropertyNotifyActive { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected bool HasPropertyChangedHandler()
{
PropertyChangedEventHandler handler = this.PropertyChanged;
return handler != null;
}
protected void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null && IsPropertyNotifyActive)
handler(sender, e);
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
OnPropertyChanged(this, e);
}
protected void OnPropertyChanged(object sender, string propertyName)
{
OnPropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
protected delegate void PropertyChangedDelegate(object sender, string propertyName);
#endregion
}
}