Something like a resizeable WrapPannel in WPF - c#

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

Related

Access InkToolbar placed in parent element from programmatically generated child elements

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;
}

WPF Charting Toolkit: How to set a fixed X-axis range while adding real time data

I'm doing an application real time data charting basing on WPF Charting Toolkit. I get the datas via serial port.
The code of setting chart is below:
<chartingToolkit:Chart Margin="10,10,10,0" ClipToBounds="True" x:Name="chart1" Title="Chart Title">
<chartingToolkit:LineSeries IndependentValueBinding="{Binding Value1}" DependentValueBinding="{Binding Value2}" ItemsSource="{Binding}" Background="Transparent" Cursor="No">
<chartingToolkit:LineSeries.DataPointStyle>
<Style TargetType="{x:Type chartingToolkit:LineDataPoint}">
<Setter Property="Height" Value="0"/>
<Setter Property="Width" Value="0" />
<Setter Property="Background" Value="Green"/>
</Style>
</chartingToolkit:LineSeries.DataPointStyle>
</chartingToolkit:LineSeries>
</chartingToolkit:Chart>
It works good but I still need to set maximum and minimum values of X axis.
The X Values (Value1) are as number of received samples and the Y axis values (Value2) are obviously as concrete values of the received samples.
My question is about the X axis range.
Currently, I'm getting minimum as 0 and maximinum as the highest number of sample which the serial port received in current moment.
But I want to set a permanent range of X axis which I want to see.
For example I want to see on the X axis range of 500 samples.
It means that when the number of samples exceeds 500, the max should be as the highest sample number and the min should be max-500.
The main difficulty is how to set it with real time data in WPF??
Can anyone help me, please??
Updated question
I'm updating my question after #jstreet advise.
I have this method which is running in separate thread within MainWindow class, likes below.
public partial class MainWindow : Window
{
public SerialPort serialPort1 = new SerialPort();
public string rx_str = "";
public string rx_str_copy;
public int a;
public double x, y;
ObservableCollection<ChartData> chartData;
ChartData objChartData;
Thread myThread;
public MainWindow()
{
InitializeComponent();
string[] port = SerialPort.GetPortNames();
foreach (string a in port)
{
comboPorts.Items.Add(a);
}
Array.Sort(port);
comboPorts.Text = port[0];
objChartData = new ChartData();
chartData.Add(objChartData);
chart1.DataContext = chartData;
myThread = new Thread(new ThreadStart(Run));
}
public void Run()
{
while (true)
{
serialPort1.Write("a");
rx_str = serialPort1.ReadTo("b");
rx_str_copy = rx_str;
x = a;
y = Double.Parse(rx_str_copy, CultureInfo.InvariantCulture);
a++;
Dispatcher.Invoke(new Action(delegate
{
chartData.Add(new ChartData() { Value1 = x,
Value2= y });
}));
}
}
This Run() method is responsible for receiving datas and adding it to the chart.
In another class I have handle of reaction on comming datas and settings properties Valeu1 and Value2:
public class ChartData : INotifyPropertyChanged
{
double _Value1;
double _Value2;
public double Value1
{
get
{
return _Value1;
}
set
{
_Value1 = value;
OnPropertyChanged("Value1");
}
}
public double Value2
{
get
{
return _Value2;
}
set
{
_Value2 = value;
OnPropertyChanged("Value2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs(propertyName));
}
}
}
How can I adapt #jstreet's solution to my behind code example??
Create a MinValue dependency property in your view model and bind it to your axis Minimum property. Take a look:
XAML:
<Window
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:WpfApp31"
xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
x:Class="WpfApp31.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MyViewModel/>
</Window.DataContext>
<Grid>
<chartingToolkit:Chart Title="My Sample">
<chartingToolkit:Chart.Axes>
<chartingToolkit:LinearAxis Minimum="{Binding MinValue}" Orientation="X"></chartingToolkit:LinearAxis>
</chartingToolkit:Chart.Axes>
<chartingToolkit:LineSeries IndependentValueBinding="{Binding Value1}"
DependentValueBinding="{Binding Value2}"
ItemsSource="{Binding Data}">
</chartingToolkit:LineSeries>
</chartingToolkit:Chart>
</Grid>
</Window>
View Model:
public class MyViewModel : DependencyObject
{
public int MinValue
{
get { return (int)GetValue(MinValueProperty); }
set { SetValue(MinValueProperty, value); }
}
// Using a DependencyProperty as the backing store for MinValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MinValueProperty =
DependencyProperty.Register("MinValue", typeof(int), typeof(MyViewModel), new PropertyMetadata(default(int)));
public ObservableCollection<MyDataModel> Data { get; set; }
private Timer serialPort;
private Random y;
private int x;
private int range;
public MyViewModel()
{
range = 10;
Data = new ObservableCollection<MyDataModel>();
y = new Random(DateTime.Now.Millisecond);
serialPort = new Timer(DataReceived, null, 500, 500);
}
private void DataReceived(object state)
{
Application.Current.Dispatcher.Invoke(() => {
Data.Add(new MyDataModel { Value1 = x, Value2 = y.Next(10, 90) });
MinValue = x < range ? 0 : x - range;
x++;
});
}
}
EDIT: For the record, I would probably not write this code quite like below. I'm doing it here just so you can move forward with it.
XAML:
<Window
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:WpfApp1"
xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
x:Class="WpfApp1.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<chartingToolkit:Chart Grid.Row="0" Margin="10,10,10,0" ClipToBounds="True" x:Name="chart1" Title="Chart Title">
<chartingToolkit:Chart.Axes>
<chartingToolkit:LinearAxis Minimum="{Binding MinValue}" Orientation="X"></chartingToolkit:LinearAxis>
</chartingToolkit:Chart.Axes>
<chartingToolkit:LineSeries IndependentValueBinding="{Binding Value1}" DependentValueBinding="{Binding Value2}" ItemsSource="{Binding chartData}" Background="Transparent" Cursor="No">
<chartingToolkit:LineSeries.DataPointStyle>
<Style TargetType="{x:Type chartingToolkit:LineDataPoint}">
<Setter Property="Height" Value="0"/>
<Setter Property="Width" Value="0" />
<Setter Property="Background" Value="Green"/>
</Style>
</chartingToolkit:LineSeries.DataPointStyle>
</chartingToolkit:LineSeries>
</chartingToolkit:Chart>
<Button Grid.Row="1" x:Name="btn1" Click="btn1_Click">START</Button>
</Grid>
CS:
public partial class MainWindow : Window
{
public double MinValue
{
get { return (double)GetValue(MinValueProperty); }
set { SetValue(MinValueProperty, value); }
}
// Using a DependencyProperty as the backing store for MinValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MinValueProperty =
DependencyProperty.Register("MinValue", typeof(double), typeof(MainWindow), new PropertyMetadata(default(double)));
//public SerialPort serialPort1 = new SerialPort();
//public string rx_str = "";
//public string rx_str_copy;
//public int a;
public double x, y;
public ObservableCollection<ChartData> chartData { get; set; }
ChartData objChartData;
Thread myThread;
Random r;
int range = 50;
public MainWindow()
{
InitializeComponent();
r = new Random();
DataContext = this;
/*
string[] port = SerialPort.GetPortNames();
foreach (string a in port)
{
comboPorts.Items.Add(a);
}
Array.Sort(port);
comboPorts.Text = port[0];
*/
objChartData = new ChartData();
chartData = new ObservableCollection<ChartData>();
chartData.Add(objChartData);
//chart1.DataContext = chartData;
myThread = new Thread(new ThreadStart(Run));
}
private void btn1_Click(object sender, RoutedEventArgs e)
{
myThread.Start();
}
public void Run()
{
while (true)
{
//serialPort1.Write("a");
//rx_str = serialPort1.ReadTo("b");
//rx_str_copy = rx_str;
//x = a;
//y = Double.Parse(rx_str_copy, CultureInfo.InvariantCulture);
//a++;
Dispatcher.Invoke(new Action(delegate
{
chartData.Add(new ChartData()
{
Value1 = x,
Value2 = r.NextDouble(),
//Value2 = y
});
MinValue = x < range ? 0 : x - range;
x++;
}));
Thread.Sleep(50);
}
}
}

Making a NxN tic tac toe GUI wpf c#

I am making a NxN tic tac toe game in WPF c#. I want user to enter Rows and Column count(NxN), and my code will generate those number of columns and rows. I can't figure it out, how will I produce that number of rows and columns dynamically. I have posted my XAML code, is there a way that I can loop my XAML code?
thanks
<Grid x:Name="Container">
<!-- First here i want to make N columns-->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Here i want to make N rows-->
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Then here i want to add that number of buttons in N x N columns and rows -->
<Button x:Name="Button0_0" Grid.Row="0" Grid.Column="0" Click="Button_Click"/>
<Button x:Name="Button0_1" Grid.Row="0" Grid.Column="1" Click="Button_Click" />
<Button x:Name="Button0_2" Grid.Row="0" Grid.Column="2" Click="Button_Click"/>
<Button x:Name="Button1_0" Grid.Row="1" Grid.Column="0" Click="Button_Click"/>
<Button x:Name="Button1_1" Grid.Row="1" Grid.Column="1" Click="Button_Click"/>
<Button x:Name="Button1_2" Grid.Row="1" Grid.Column="2" Click="Button_Click"/>
<Button x:Name="Button2_0" Grid.Row="2" Grid.Column="0" Click="Button_Click"/>
<Button x:Name="Button2_1" Grid.Row="2" Grid.Column="1" Click="Button_Click"/>
<Button x:Name="Button2_2" Grid.Row="2" Grid.Column="2" Click="Button_Click"/>
</Grid>
ItemsControl + UniformGrid as a Panel is a good choice to display a rectangular game board. I have already posted similar solution (How to create and use matrix of (color) boxes C# WPF) but it is missing Rows and Columns binding. Here is a more elaborated example for TicTacToe.
Let's create view model classes:
public class BoardCell : INotifyPropertyChanged
{
private string _sign;
private bool _canSelect = true;
public string Sign
{
get { return _sign; }
set
{
_sign = value;
if (value != null)
CanSelect = false;
OnPropertyChanged();
}
}
public bool CanSelect
{
get { return _canSelect; }
set
{
_canSelect = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Board
{
public int Rows { get; set; }
public int Columns { get; set; }
private ObservableCollection<BoardCell> _cells;
public ObservableCollection<BoardCell> Cells
{
get
{
if (_cells == null)
_cells = new ObservableCollection<BoardCell>(Enumerable.Range(0, Rows*Columns).Select(i => new BoardCell()));
return _cells;
}
}
}
and then create a view:
<Grid x:Name="Container">
<Grid.DataContext>
<wpfDemos:Board Rows="8" Columns="8"/>
</Grid.DataContext>
<ItemsControl x:Name="Board" ItemsSource="{Binding Path=Cells}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate >
<UniformGrid Rows="{Binding Path=Rows}" Columns="{Binding Path=Columns}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Path=Sign}"
IsEnabled="{Binding Path=CanSelect}"
Click="CellClick"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
in the code-behind we have one method to allow 2 players make thier moves in turn:
private bool _firstPlayer = true;
private void CellClick(object sender, RoutedEventArgs e)
{
var cell = (sender as Button).DataContext as BoardCell;
cell.Sign = _firstPlayer ? "X" : "O";
_firstPlayer = !_firstPlayer;
// TODO check winner
}
to follow MVVM pattern this method should be wrapped into a command (ICommand), moved to Board class and attached to view via Button.Command binding.
summary: separate logic and presentation. work with data. let controls generate their content based on bindings and provided templates.
I have code for some AttachedProperties on my blog that does exactly this.
XAML code ends up looking like this :
<Grid local:GridHelpers.RowCount="{Binding RowCount}"
local:GridHelpers.ColumnCount="{Binding ColumnCount}" />
Here's the code for the Attached properties, in case the blog post link ever goes down. It also includes some properties to specify your Star rows/columns.
public class GridHelpers
{
#region RowCount Property
/// <summary>
/// Adds the specified number of Rows to RowDefinitions.
/// Default Height is Auto
/// </summary>
public static readonly DependencyProperty RowCountProperty =
DependencyProperty.RegisterAttached(
"RowCount", typeof(int), typeof(GridHelpers),
new PropertyMetadata(-1, RowCountChanged));
// Get
public static int GetRowCount(DependencyObject obj)
{
return (int)obj.GetValue(RowCountProperty);
}
// Set
public static void SetRowCount(DependencyObject obj, int value)
{
obj.SetValue(RowCountProperty, value);
}
// Change Event - Adds the Rows
public static void RowCountChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is Grid) || (int)e.NewValue < 0)
return;
Grid grid = (Grid)obj;
grid.RowDefinitions.Clear();
for (int i = 0; i < (int)e.NewValue; i++)
grid.RowDefinitions.Add(
new RowDefinition() { Height = GridLength.Auto });
SetStarRows(grid);
}
#endregion
#region ColumnCount Property
/// <summary>
/// Adds the specified number of Columns to ColumnDefinitions.
/// Default Width is Auto
/// </summary>
public static readonly DependencyProperty ColumnCountProperty =
DependencyProperty.RegisterAttached(
"ColumnCount", typeof(int), typeof(GridHelpers),
new PropertyMetadata(-1, ColumnCountChanged));
// Get
public static int GetColumnCount(DependencyObject obj)
{
return (int)obj.GetValue(ColumnCountProperty);
}
// Set
public static void SetColumnCount(DependencyObject obj, int value)
{
obj.SetValue(ColumnCountProperty, value);
}
// Change Event - Add the Columns
public static void ColumnCountChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is Grid) || (int)e.NewValue < 0)
return;
Grid grid = (Grid)obj;
grid.ColumnDefinitions.Clear();
for (int i = 0; i < (int)e.NewValue; i++)
grid.ColumnDefinitions.Add(
new ColumnDefinition() { Width = GridLength.Auto });
SetStarColumns(grid);
}
#endregion
#region StarRows Property
/// <summary>
/// Makes the specified Row's Height equal to Star.
/// Can set on multiple Rows
/// </summary>
public static readonly DependencyProperty StarRowsProperty =
DependencyProperty.RegisterAttached(
"StarRows", typeof(string), typeof(GridHelpers),
new PropertyMetadata(string.Empty, StarRowsChanged));
// Get
public static string GetStarRows(DependencyObject obj)
{
return (string)obj.GetValue(StarRowsProperty);
}
// Set
public static void SetStarRows(DependencyObject obj, string value)
{
obj.SetValue(StarRowsProperty, value);
}
// Change Event - Makes specified Row's Height equal to Star
public static void StarRowsChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is Grid) || string.IsNullOrEmpty(e.NewValue.ToString()))
return;
SetStarRows((Grid)obj);
}
#endregion
#region StarColumns Property
/// <summary>
/// Makes the specified Column's Width equal to Star.
/// Can set on multiple Columns
/// </summary>
public static readonly DependencyProperty StarColumnsProperty =
DependencyProperty.RegisterAttached(
"StarColumns", typeof(string), typeof(GridHelpers),
new PropertyMetadata(string.Empty, StarColumnsChanged));
// Get
public static string GetStarColumns(DependencyObject obj)
{
return (string)obj.GetValue(StarColumnsProperty);
}
// Set
public static void SetStarColumns(DependencyObject obj, string value)
{
obj.SetValue(StarColumnsProperty, value);
}
// Change Event - Makes specified Column's Width equal to Star
public static void StarColumnsChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is Grid) || string.IsNullOrEmpty(e.NewValue.ToString()))
return;
SetStarColumns((Grid)obj);
}
#endregion
private static void SetStarColumns(Grid grid)
{
string[] starColumns =
GetStarColumns(grid).Split(',');
for (int i = 0; i < grid.ColumnDefinitions.Count; i++)
{
if (starColumns.Contains(i.ToString()))
grid.ColumnDefinitions[i].Width =
new GridLength(1, GridUnitType.Star);
}
}
private static void SetStarRows(Grid grid)
{
string[] starRows =
GetStarRows(grid).Split(',');
for (int i = 0; i < grid.RowDefinitions.Count; i++)
{
if (starRows.Contains(i.ToString()))
grid.RowDefinitions[i].Height =
new GridLength(1, GridUnitType.Star);
}
}
}
As Zohar has mentioned you could use the code behind to add child GridColumn's to the Grid.ColumnDefinitions, and then add Button's to the Container Grid. Its an easy approach, but it means using code behind which causes many frowns.
Personally I would rather create a Behaviour and attach it to the Grid. Have your behaviour bind to an integer of Rows and Columns on your viewmodel, and then add ColumnDefinitions and RowDefinitions to the AssociatedObject. Behaviours are a great way to encapsulate reusable features, and eliminates the need for writing code behind.
This chap demonstrates how to create behaviours that bind to your model, and also how to attach them to an element in the view.
http://julmar.com/blog/programming/playing-with-wpf-behaviors-a-watermarktext-behavior/

Zoom Cropped pixels in a user control

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

Using Custom Controls, with dependency properties in an Itemscontrol datatemplate

I am trying to create a polar diagram using MVVM. The radial axis is wind speed and radial angles is wind direction. I can create the radial circles (ellipses) and the radial speed axis labels. For the angular lines (like wheel spokes) I created a custom control that was simply a line with a textblock at the end (giving the angular axis value). However, I can not get my program to set the properties of this custom control and therefore you cannot see them on the canvas (though they are there but the default length is zero and default text is null)?
OK, So my diagram.xaml is as follows:
<Grid>
<Grid.DataContext>
<local:DiagramViewModel x:Name="ViewModel" />
</Grid.DataContext>
<Viewbox x:Name="myView"
Stretch="Uniform"
StretchDirection="Both">
<ItemsControl ItemsSource="{Binding Points}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Height="400" Width="400"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Right" Value="{Binding Right}"/>
<Setter Property="Canvas.Left" Value="{Binding Left}"/>
<Setter Property="Canvas.Top" Value="{Binding Top}"/>
<Setter Property="Canvas.Bottom" Value="{Binding Bottom}"/>
<Setter Property="Canvas.ZIndex" Value="{Binding ZIndex}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:EllipseItem}">
<Ellipse Stroke="{Binding Stroke}"
StrokeThickness="{Binding StrokeThickness}"
StrokeDashArray="{Binding StrokeDashArray}"
Height="{Binding Height}"
Width="{Binding Width}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:GridLineItem}">
<View:GridLineComponent Text="{Binding Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Length="{Binding Path=Length, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
GridHeight="{Binding Path=GridHeight, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
LineColour="{Binding Path=LineColour, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextColour="{Binding Path=TextColour, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
RenderTransform="{Binding RenderTransform}"
RenderTransformOrigin="{Binding RenderTransformOrigin}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:TextBlockItem}">
<TextBlock Text="{Binding Text}"
Foreground="{Binding Foreground}"
FontSize="{Binding FontSize}"/>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
</Viewbox>
</Grid>
My viewmodel (DiagramViewModel), as well as other things, contains the following method to create these lines:
private void CreateAngularLines(double originMargin)
{
IList<GridLineItem> angularLine = new List<GridLineItem>();
double increment , angularGridlineCount, angle, height, width;
if (this.minorAngularIncrement == null)
increment = (double)this.majorAngularIncrement;
else
increment = (double)this.minorAngularIncrement;
angularGridlineCount = 360 / increment;
height = 200;//half the length of the canvas
width = 200;
for (int i = 0; i < angularGridlineCount; i++)
{
angularLine.Add(new GridLineItem());
angle = i * increment;
if (angle == 0 || angle == 180)
angularLine[i].Text = angle.ToString("000");
else if (angle < 180)
angularLine[i].Text = "G" + angle.ToString("000");
else
angularLine[i].Text = "R" + (360 - angle).ToString("000");
angularLine[i].Length = height - originMargin;
angularLine[i].GridHeight = height;
angularLine[i].LineColour = this.LineColour;
angularLine[i].TextColour = this.TextColour;
angularLine[i].Left = width - 18;
angularLine[i].Top = 0;
angularLine[i].Right = width;
angularLine[i].Bottom = height;
angularLine[i].ZIndex = 1;
angularLine[i].RenderTransformOrigin = new Point(0.5, 1);
angularLine[i].RenderTransform = new RotateTransform(angle, 0, 0);
}
Points.Add(new CollectionContainer() { Collection = angularLine });
}
The GridLineItem class is a simple holder class with the following:
public class GridLineItem
{
public GridLineItem()
{
this.RenderTransformOrigin = new Point(0.5, 1);
this.RenderTransform = new RotateTransform(0, 0, 0);
this.Left = double.NaN;
this.Right = double.NaN;
this.Top = double.NaN;
this.Bottom = double.NaN;
}
public Brush LineColour { get; set; }
public Brush TextColour { get; set; }
public string Text { get; set; }
public double Length { get; set; }
public double GridHeight { get; set; }
public Point RenderTransformOrigin { get; set; }
public Transform RenderTransform { get; set; }
public int ZIndex { get; set; }
public double Left { get; set; }
public double Right { get; set; }
public double Top { get; set; }
public double Bottom { get; set; }
}
Now this is where I think I am going wrong. The GridlineComponent.xaml.cs file contains the following:
public partial class GridLineComponent : UserControl, INotifyPropertyChanged
{
public GridLineComponent()
{
InitializeComponent();
this.DataContext = this;
}
public GridLineComponent(string text, double length, double gridHeight) : this()
{
this.Text = text;
this.Length = length;
this.GridHeight = gridHeight;
}
public Brush LineColour
{
get { return (Brush)GetValue(LineColourProperty); }
set
{
SetValue(LineColourProperty, value);
NotifyPropertyChanged("LineColour");
}
}
public Brush TextColour
{
get { return (Brush)GetValue(TextColourProperty); }
set
{
SetValue(TextColourProperty, value);
NotifyPropertyChanged("TextColour");
}
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set
{
if (value == null)
SetValue(TextProperty, "");
else
SetValue(TextProperty, value); //"\u00B0";
NotifyPropertyChanged("Text");
}
}
public double Length
{
get { return (double)GetValue(LengthProperty); }
set
{
SetValue(LengthProperty, value);
NotifyPropertyChanged("Length");
}
}
public double GridHeight
{
get { return (double)GetValue(GridHeightProperty); }
set
{
SetValue(GridHeightProperty, value);
NotifyPropertyChanged("GridHeight");
}
}
public static readonly DependencyProperty TextColourProperty =
DependencyProperty.Register(
"TextColour",
typeof(Brush),
typeof(GridLineComponent),
new PropertyMetadata(default(Brush), OnItemsPropertyChanged));
public static readonly DependencyProperty LineColourProperty =
DependencyProperty.Register(
"LineColour",
typeof(Brush),
typeof(GridLineComponent),
new PropertyMetadata(default(Brush), OnItemsPropertyChanged));
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
"Text",
typeof(string),
typeof(GridLineComponent),
new PropertyMetadata(default(string), OnItemsPropertyChanged));
public static readonly DependencyProperty LengthProperty =
DependencyProperty.Register(
"Length",
typeof(double),
typeof(GridLineComponent),
new PropertyMetadata(default(double), OnItemsPropertyChanged));
public static readonly DependencyProperty GridHeightProperty =
DependencyProperty.Register(
"GridHeight",
typeof(double),
typeof(GridLineComponent),
new PropertyMetadata(default(double), OnItemsPropertyChanged));
private static void OnItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
GridLineComponent source = d as GridLineComponent;
// Do something...
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
}
Basically this GridLineComponent is not following the rules of MVVM. But I think that is ok here as it is such a basic type. The xaml for it is simply a grid containing a line and a TextBlock. The line's Y2 and stroke attributes are binded to the xaml.cs (Length and LineColour respectively) and so too are the textblock's foreground and text attributes (TextColour and Text respectively).
From what I have read, these lines should draw but do not. I think it may have something to do with the dependency properties or because of the empty OnItemsPropertyChanged handler. ALso, when the program is run, only the non argument constructor, for the GridlineComponent.xaml.cs is accessed (non of the properties are set)?

Categories

Resources