Is there a way to create cpu perfmon dashboard using WPF .net? - c#

I have created it using winforms by just adding permon classes and downloading some realtime dasboards but i find it quite difficult in WPF

There's a lot of tools that allows to draw various graphs in WPF.
But since i didn't find any manual graph drawing implementation, I've created the example - how to draw a graph in WPF using MVVM programming pattern.
0. Helper classes
For proper and easy MVVM implementation I will use 2 following classes.
NotifyPropertyChanged.cs - to notify UI on changes.
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
RelayCommand.cs - for easy commands use (for Button)
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
=> (_execute, _canExecute) = (execute, canExecute);
public bool CanExecute(object parameter)
=> _canExecute == null || _canExecute(parameter);
public void Execute(object parameter)
=> _execute(parameter);
}
1. Data implementation
As graph consists of contant amount of points an they're just turning around, I've implemented the Round-Robin Collection that has only one method Push().
RoundRobinCollection.cs
public class RoundRobinCollection : NotifyPropertyChanged
{
private readonly List<float> _values;
public IReadOnlyList<float> Values => _values;
public RoundRobinCollection(int amount)
{
_values = new List<float>();
for (int i = 0; i < amount; i++)
_values.Add(0F);
}
public void Push(float value)
{
_values.RemoveAt(0);
_values.Add(value);
OnPropertyChanged(nameof(Values));
}
}
2. Values collection to Polygon points Converter
Used in View markup
PolygonConverter.cs
public class PolygonConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
PointCollection points = new PointCollection();
if (values.Length == 3 && values[0] is IReadOnlyList<float> dataPoints && values[1] is double width && values[2] is double height)
{
points.Add(new Point(0, height));
points.Add(new Point(width, height));
double step = width / (dataPoints.Count - 1);
double position = width;
for (int i = dataPoints.Count - 1; i >= 0; i--)
{
points.Add(new Point(position, height - height * dataPoints[i] / 100));
position -= step;
}
}
return points;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => null;
}
3. View Model
The main class containing all business logic of the App
public class MainViewModel : NotifyPropertyChanged
{
private bool _graphEnabled;
private float _lastCpuValue;
private ICommand _enableCommand;
public RoundRobinCollection ProcessorTime { get; }
public string ButtonText => GraphEnabled ? "Stop" : "Start";
public bool GraphEnabled
{
get => _graphEnabled;
set
{
if (value != _graphEnabled)
{
_graphEnabled = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ButtonText));
if (value)
ReadCpu();
}
}
}
public float LastCpuValue
{
get => _lastCpuValue;
set
{
_lastCpuValue = value;
OnPropertyChanged();
}
}
public ICommand EnableCommand => _enableCommand ?? (_enableCommand = new RelayCommand(parameter =>
{
GraphEnabled = !GraphEnabled;
}));
private async void ReadCpu()
{
try
{
using (PerformanceCounter cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total"))
{
while (GraphEnabled)
{
LastCpuValue = cpuCounter.NextValue();
ProcessorTime.Push(LastCpuValue);
await Task.Delay(1000);
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
public MainViewModel()
{
ProcessorTime = new RoundRobinCollection(100);
}
}
Disclamer: async void is not recommended approach to make something async but here the usage is safe because any possible Exception will be handled inside. For more information about why async void is bad, refer to the documentation - Asynchronous Programming.
4. View
The whole UI markup of the app
<Window x:Class="CpuUsageExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CpuUsageExample"
mc:Ignorable="d"
Title="MainWindow" Height="400" Width="800" >
<Window.DataContext>
<local:MainViewModel/><!-- attach View Model -->
</Window.DataContext>
<Window.Resources>
<local:PolygonConverter x:Key="PolygonConverter"/><!-- attach Converter -->
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<WrapPanel>
<Button Margin="5" Padding="10,0" Content="{Binding ButtonText}" Command="{Binding EnableCommand}"/>
<TextBlock Margin="5" Text="CPU:"/>
<TextBlock Margin="0, 5" Text="{Binding LastCpuValue, StringFormat=##0.##}" FontWeight="Bold"/>
<TextBlock Margin="0, 5" Text="%" FontWeight="Bold"/>
</WrapPanel>
<Border Margin="5" Grid.Row="1" BorderThickness="1" BorderBrush="Gray" SnapsToDevicePixels="True">
<Canvas ClipToBounds="True">
<Polygon Stroke="CadetBlue" Fill="AliceBlue">
<Polygon.Resources>
<Style TargetType="Polygon">
<Setter Property="Points">
<Setter.Value>
<MultiBinding Converter="{StaticResource PolygonConverter}">
<Binding Path="ProcessorTime.Values"/>
<Binding Path="ActualWidth" RelativeSource="{RelativeSource AncestorType=Canvas}"/>
<Binding Path="ActualHeight" RelativeSource="{RelativeSource AncestorType=Canvas}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</Polygon.Resources>
</Polygon>
</Canvas>
</Border>
</Grid>
</Window>
P.S. There's no code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}

Related

How to manually increment/decrement a WPF progress bar using MVVM

I have searched in Google using a progress bar in WPF that will be manually incremented and decremented using a plus and minus button. But, to no avail haven't found one that is similar to what I want though.
How do I implement a WPF progress bar that will be manually incremented or decremented (using buttons) in an MVVM way. The screen capture below shows the mock-up UI design.
The image show that when the user click the plus button, the progress bar will be incremented by 10 minutes. On the other hand, the minus button when clicked, decrements the progress bar by 10 minutes.
I'm just starting to learn WPF and MVVM. Any help is greatly appreciated.
I created a simple example which uses WPF and MVVM to show how one model can be displayed with different views. Here in xaml I placed on a form Slider and ProgressBar - they are Views for our ViewModel. The properties we need (Minimum, Maximum, Value) are binded to the ViewModel's properties. "Plus" and "Minus" buttons' properties "Command" are also binded to the corresponding props in the ViewModel(IncreaseCommand, DecreaseCommand).
<Window>
<StackPanel Orientation="Horizontal">
<Button Width="50" Height="40" Content="-" Command="{Binding DecreaseCommand}"/>
<StackPanel Width="400" Orientation="Vertical">
<Slider Height="40" Margin="0,50,0,0" Minimum="{Binding Minimum}" Maximum="{Binding Maximum}" Value="{Binding Value}"/>
<ProgressBar Height="40" Margin="0,100,0,0" Minimum="{Binding Minimum}" Maximum="{Binding Maximum}" Value="{Binding Value}"/>
<TextBlock TextAlignment="Center" Margin="0,50,0,0" Text="{Binding Value}"/>
</StackPanel>
<Button Width="50" Height="40" Content="+" Command="{Binding IncreaseCommand}"/>
</StackPanel>
</Window>
For implementing the commands functionality in ViewModel you will need to create an implementation of ICommand interface:
public class RelayCommand : ICommand
{
private Predicate<object> _canExecute;
private Action<object> _execute;
public RelayCommand(Predicate<object> canExecute, Action<object> execute)
{
_canExecute = canExecute;
_execute = execute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
And here's the ViewModel class, it implements INotifyPropertyChanged interface to keep views updated.
public class ViewModel:INotifyPropertyChanged
{
public ViewModel()
{
_value = 0;
_minimum = 0;
_maximum = 180;
_step = 10;
}
private int _step;
private int _minimum;
private int _maximum;
private ICommand _increaseCommand;
public ICommand IncreaseCommand
{
get
{
if (_increaseCommand == null)
{
_increaseCommand = new RelayCommand(
p => _value + _step <= _maximum,
Increase);
}
return _increaseCommand;
}
}
private ICommand _decreaseCommand;
public ICommand DecreaseCommand
{
get
{
if (_decreaseCommand == null)
{
_decreaseCommand = new RelayCommand(
p => _value - _step >= _minimum,
Decrease);
}
return _decreaseCommand;
}
}
private void Increase(object param)
{
Value = Value + _step;
}
private void Decrease(object param)
{
Value = Value - _step;
}
private int _value;
public int Value
{
get { return _value; }
set { _value = value; InvokePropertyChanged(new PropertyChangedEventArgs("Value")); }
}
public int Minimum
{
get { return _minimum; }
}
public int Maximum
{
get { return _maximum; }
}
public event PropertyChangedEventHandler PropertyChanged;
public void InvokePropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
}
And the last thing to get it all working is to create new ViewModel and set DataContext of a window to this model:
public MainWindow()
{
InitializeComponent();
var model = new ViewModel();
DataContext = model;
}
I think you should solve that by use custom Slider control of WPF instead Progress bar.
This link can help you : http://www.codescratcher.com/wpf/custom-slider-control-in-wpf/

Calculating textbox with two selected combobox items

My problem: Id like to , after two combobox variables are selected, to divide these two and set the Textbox to the result of the calculation.
The two Comboboxes: Körpergröße & Gewicht
The textbox: BMI
First of all, the code im using ( which apparently isnt working now)
private void fillTextBox(float value1, float value2)
{
BMI.Text = (value1 / value2).ToString();
}
private void Körpergröße_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
float a;
float b;
//In this way you can compare the value and if it is possible to convert into an integer.
if (float.TryParse(Körpergröße.SelectedItem.ToString(), out a) && float.TryParse(Gewicht.SelectedItem.ToString(), out b))
{
fillTextBox(a, b);
}
}
private void Gewicht_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
float a;
float b;
//In this way you can compare the value and if it is possible to convert into an integer.
if (float.TryParse(Körpergröße.SelectedItem.ToString(), out a) && float.TryParse(Gewicht.SelectedItem.ToString(), out b))
{
fillTextBox(a, b);
}
}
The default values of the two comboboxes are strings.. ("Bitte auswählen")
Pictures how it looks like now. After two int values are selected, the result of the calculation should appear in the BMI Textbox, but its still blank. it looks like the ToString() Methods do not save into a, and neither into b..therefore the values cant be used in the fillTextBox method
It would be nice if someone could answer me with a code with some sidenotes, in order for me to understand..
Thanks in advance!
Here's an example of how you would write a trivial BMI calculator in WPF. This is the way XAML is intended to be used: All of the logic is in the View Model class, BMIViewModel. The View (XAML file) wraps a UI around that logic. The codebehind file is only used if you need to provide some special logic unique to the view itself. Very often it is not used at all. Here, it's not used.
This is very different from what you may be used to and it's a steep learning curve in a lot of ways, but I've learned to like it very much. If you break up program logic into sensible chunks in various View Models, you can present those View Models in the UI in various differing ways. You get tremendous freedom and power. Once you've got a debugged and tested View Model, you can write new UI for it without touching the program logic at all.
If you study and understand this code, you will have a rudimentary but solid basis to start learning XAML. The bindings are important, and OnPropertyChanged is very important: That notification is how the view model lets the bindings know that a value has changed.
I don't like writing this much code for a beginner, but your code (which is not your code -- it's entirely copied from bad answers to previous iterations of this question), is unusable.
XAML:
<Window
x:Class="TestApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestApplication"
Title="MainWindow" Height="350" Width="525"
>
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Grid>
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<Style x:Key="FieldLabel" TargetType="Label">
<Setter Property="Width" Value="120" />
</Style>
<Style TargetType="ComboBox">
<Setter Property="Width" Value="140" />
</Style>
<Style TargetType="TextBox">
<Setter Property="Width" Value="140" />
</Style>
</StackPanel.Resources>
<StackPanel Orientation="Horizontal">
<Label Content="Height" Style="{StaticResource FieldLabel}" />
<ComboBox
ItemsSource="{Binding Heights}"
DisplayMemberPath="Name"
SelectedValuePath="Value"
SelectedValue="{Binding Height}"
/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Weight" Style="{StaticResource FieldLabel}" />
<ComboBox
ItemsSource="{Binding Weights}"
DisplayMemberPath="Name"
SelectedValuePath="Value"
SelectedValue="{Binding Weight}"
/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="BMI" Style="{StaticResource FieldLabel}" />
<TextBox IsReadOnly="True" Text="{Binding BMI}" />
</StackPanel>
</StackPanel>
</Grid>
</Window>
C# code behind (I added no code at all here):
using System;
using System.Windows;
namespace TestApplication
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
BMIViewModel.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace TestApplication
{
public class BMIListItem
{
public BMIListItem(string name, float value)
{
Name = name;
Value = value;
}
public BMIListItem(float value)
{
Name = value.ToString();
Value = value;
}
public String Name { get; set; }
public float Value { get; set; }
}
public class BMIViewModel : INotifyPropertyChanged
{
public BMIViewModel()
{
// Of course you would write loops for these in real life.
// You should not need help with that.
Heights = new List<BMIListItem>
{
new BMIListItem("Bitte auswählen", float.NaN),
new BMIListItem("Dummy", 0),
new BMIListItem(150),
new BMIListItem(151),
new BMIListItem(152),
// etc.
};
Weights = new List<BMIListItem>
{
new BMIListItem("Bitte auswählen", float.NaN),
new BMIListItem("Dummy", 0),
new BMIListItem(40),
new BMIListItem(41),
new BMIListItem(42),
// etc.
};
}
public List<BMIListItem> Heights { get; private set; }
public List<BMIListItem> Weights { get; private set; }
#region BMI Property
private float _bmi = 0;
public float BMI
{
get { return _bmi; }
set
{
if (value != _bmi)
{
_bmi = value;
OnPropertyChanged("BMI");
}
}
}
#endregion BMI Property
#region Height Property
private float _height = float.NaN;
public float Height
{
get { return _height; }
set
{
if (value != _height)
{
_height = value;
UpdateBMI();
OnPropertyChanged("Height");
}
}
}
#endregion Height Property
#region Weight Property
private float _weight = float.NaN;
public float Weight
{
get { return _weight; }
set
{
if (value != _weight)
{
_weight = value;
UpdateBMI();
OnPropertyChanged("Weight");
}
}
}
#endregion Weight Property
private void UpdateBMI()
{
if (float.IsNaN(Weight) || float.IsNaN(Height) || Height == 0)
{
BMI = 0;
}
else
{
BMI = Weight / Height;
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(String propName)
{
var handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propName));
}
}
#endregion INotifyPropertyChanged
}
}

DataTemplate binding doesn't work?

I'm write the following code for binding some properties
<StackPanel x:Name="channelsRecordTimeData" Orientation="Vertical">
<ItemsControl x:Name="channelRecordTimeItems" ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid x:Name="gridChannelRecordTimeItem" Width="{Binding Path=ChannelRecordTimeItemWidth}"
Height="{Binding Path=ChannelRecordTimeItemHeight}" Margin="{Binding Path=ChannelRecordTimeItemsMargin}"
HorizontalAlignment="Left" DataContext="{Binding Path=ListRecordTime}">
<Grid.Background>
<ImageBrush x:Name="gridChannelRecordTimeItemBgr" ImageSource="..\Resources\playback_grid_channel_record_time_item_bgr_normal.png"/>
</Grid.Background>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
public class DATA
{
public double ChannelRecordTimeItemWidth { set; get; }
public double ChannelRecordTimeItemHeight { set; get; }
public Thickness ChannelRecordTimeItemsMargin { set; get; }
public List<RecordTime> ListRecordTime { set; get; }
public DATA()
{
ChannelRecordTimeItemWidth = 1000;
ChannelRecordTimeItemHeight = 20;
ChannelRecordTimeItemsMargin = new System.Windows.Thickness(0, 0, 0, 0);
ListRecordTime = null;
}
}
public static List<DATA> listDATA = new List<DATA>();
for(int i = 0 ; i < 10 ; i++)
{
DATA data = new DATA();
listDATA.Add(data);
}
channelRecordTimeItems.ItemsSource = listDATA;
channelRecordTimeItems.Items.Refresh();
At above code, I have added 10 items in StackPanel, but I don't see any items added when run app.
But when I replace Width="{Binding Path=ChannelRecordTimeItemWidth}" by Width="1000" and replace Height="{Binding Path=ChannelRecordTimeItemHeight}" by Height="20", then it work fine!
I think, this is problem of binding, but I don't know why.
Someone can tell me how to make it work?
Many thanks,
T&T
Update your DATA class to implement INotifyPropertyChanged like so:
public class DATA : : INotifyPropertyChanged
{
private double _channelRecordTimeItemWidth;
private double _channelRecordTimeItemHeight;
private Thickness _channelRecordTimeItemsMargin;
private List<RecordTime> _listRecordTime;
public double ChannelRecordTimeItemWidth
{
get { return _channelRecordTimeItemWidth; }
set
{
_channelRecordTimeItemWidth = value;
OnPropertyChanged("ChannelRecordTimeItemWidth");
}
}
public double ChannelRecordTimeItemHeight
{
get { return _channelRecordTimeItemHeight; }
set
{
_channelRecordTimeItemHeight = value;
OnPropertyChanged("ChannelRecordTimeItemHeight");
}
}
public Thickness ChannelRecordTimeItemsMargin
{
get { return _channelRecordTimeItemsMargin; }
set
{
_channelRecordTimeItemsMargin = value;
OnPropertyChanged("ChannelRecordTimeItemsMargin");
}
}
public List<RecordTime> ListRecordTime
{
get { return _listRecordTime; }
set
{
_listRecordTime = value;
OnPropertyChanged("ListRecordTime");
}
}
public DATA()
{
ChannelRecordTimeItemWidth = 1000;
ChannelRecordTimeItemHeight = 20;
ChannelRecordTimeItemsMargin = new System.Windows.Thickness(0, 0, 0, 0);
ListRecordTime = null;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
This will notify the XAML to update the bounded value.
The DataContext should also be set correctly. First remove the bound DataContext of the Grid:
<DataTemplate>
<Grid x:Name="gridChannelRecordTimeItem" Width="{Binding Path=ChannelRecordTimeItemWidth}"
Height="{Binding Path=ChannelRecordTimeItemHeight}" Margin="{Binding Path=ChannelRecordTimeItemsMargin}"
HorizontalAlignment="Left">
<Grid.Background>
<ImageBrush x:Name="gridChannelRecordTimeItemBgr" ImageSource="..\Resources\playback_grid_channel_record_time_item_bgr_normal.png"/>
</Grid.Background>
</Grid>
</DataTemplate>
and make sure that the DataContext for the XAML (whether it is a UserControl, Window, etc), is set to your DATA class.
Your solution cannot work because of this line
DataContext="{Binding Path=ListRecordTime}"
This line sets datacontext for the grid, then you are trying to get ChannelRecordTimeItemHeight from datacontext - list of recordtimes.
Delete this line and see what happens

Keep Canvas elements positioned relative to background image

I'm trying to position elements in my Canvas relative to my background.
Window is re-sized keeping the aspect ratio.
Background is stretched with window size.
The problem is once window is re-sized the element positions are incorrect. If window is re-sized just a little, elements will adjust their size a bit and would be still in the correct position, but if window is re-sized to double it's size then positioning is completely off.
So far I used Grid, but it was to no avail as well. Here is the XAML
<Window x:Class="CanvasTEMP.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" ResizeMode="CanResizeWithGrip" SizeToContent="WidthAndHeight" MinHeight="386" MinWidth="397.5" Name="MainWindow1"
xmlns:c="clr-namespace:CanvasTEMP" Loaded="onLoad" WindowStartupLocation="CenterScreen" Height="386" Width="397.5" WindowStyle="None" AllowsTransparency="True" Topmost="True" Opacity="0.65">
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas Height="77" Width="218">
<Label Content="{Binding OwnerData.OwnerName}" Height="36" Canvas.Left="8" Canvas.Top="55" Width="198" Padding="0" HorizontalAlignment="Left" HorizontalContentAlignment="Center" VerticalAlignment="Center"/>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas>
<Canvas.Background>
<ImageBrush ImageSource="Resources\default_mapping.png" Stretch="Uniform"/>
</Canvas.Background>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding OwnerData.left}" />
<Setter Property="Canvas.Top" Value="{Binding OwnerData.top}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
Class that is used for data binding
public class Owner : INotifyPropertyChanged
{
public double _left;
public double _top;
public string OwnerName { get; set; }
public double top { get { return _top; }
set
{
if (value != _top)
{
_top = value;
OnPropertyChanged();
}
}
}
public double left
{
get { return _left; }
set
{
if (value != _left)
{
_left = value;
OnPropertyChanged();
}
}
}
public string icon { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "none passed")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ForDisplay
{
public Owner OwnerData { get; set; }
public int Credit { get; set; }
}
And here is the code that is run every second to keep elements' position relative to window size
items[0].OwnerData.left = this.Width * (10 / Defaul_WindowSize_Width);
items[0].OwnerData.top = this.Height * (55 / Defaul_WindowSize_Height);
10 and 50 are default Canvas.Left and Canvas.Top that are used when window is first initialized.
Would appreciate if anyone can point out what I'm doing wrong.
You need an attach property:
public static readonly DependencyProperty RelativeProperty =
DependencyProperty.RegisterAttached("Relative", typeof(double), typeof(MyControl));
public static double GetRelative(DependencyObject o)
{
return (double)o.GetValue(RelativeProperty);
}
public static void SetRelative(DependencyObject o, double value)
{
o.SetValue(RelativeProperty, value);
}
and a converter:
public class RelativePositionConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var rel = (double)values[0];
var width = (double)values[1];
return rel * width;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
and when you add child to Canvas you need like this:
var child = new Child();
SetRelative(child, currentPosition / ActualWidth);
var multiBinding = new MultiBinding { Converter = new RelativePositionConverter() };
multiBinding.Bindings.Add(new Binding { Source = child, Path = new PropertyPath(RelativeProperty) });
multiBinding.Bindings.Add(new Binding { Source = canvas, Path = new PropertyPath(ActualWidthProperty) });
BindingOperations.SetBinding(child, LeftProperty, multiBinding);
Children.Add(child);
If you need, you can change Relative value of child separate of Canvas

2D grid with selectable items in c# Wforms?

How can be done a 2D of 10x10 selectable elements each element with text in a windows form?
Is there an easy way of doing this?
I need to select some elements in an orderable way (indexed) in the grid in order to send new positions to my robot. I mean: 1st: go to first selected element of the grid (labeled as 1 when selected)
2nd: go to the second selected element of the grid (labeled as 2 when selected) ... and so on...
The grid would look like this:
(source: xmswiki.com)
I am trying to avoid putting 100 checkboxes close to each other...
Posting this as an answer because the OP asked for it:
<Window x:Class="MiscSamples.GridRobot"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="GridRobot" Height="500" Width="600">
<DockPanel>
<DockPanel DockPanel.Dock="Top">
<TextBlock Text="Size:" Margin="2" DockPanel.Dock="Left"/>
<TextBox Text="{Binding Size}" IsReadOnly="True" DockPanel.Dock="Left" Margin="2" Width="50"/>
<Slider Maximum="20" Minimum="2" Value="{Binding Size}"/>
</DockPanel>
<StackPanel DockPanel.Dock="Left" Width="100" Margin="2">
<TextBlock Text="Route:" TextAlignment="Center" FontWeight="Bold"/>
<ItemsControl ItemsSource="{Binding Route}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock TextAlignment="Center">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0:D2},{1:D2}">
<Binding Path="Row"/>
<Binding Path="Column"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<Grid>
<ItemsControl ItemsSource="{Binding Squares}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding Size}" Columns="{Binding Size}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="DarkGray" BorderThickness="1">
<Button Command="{Binding DataContext.GoToCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding}">
<Button.Template>
<ControlTemplate>
<Border Background="#05FFFFFF">
<Viewbox>
<TextBlock Text="{Binding PathIndex}"
TextAlignment="Center" VerticalAlignment="Center"/>
</Viewbox>
</Border>
</ControlTemplate>
</Button.Template>
</Button>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Canvas>
<!-- I was about to add the Robot Peg here and animate it -->
</Canvas>
</Grid>
</DockPanel>
</Window>
Code Behind:
public partial class GridRobot : Window
{
public GridRobot()
{
InitializeComponent();
DataContext = new GridRobotViewModel();
}
}
View Model:
public class GridRobotViewModel: PropertyChangedBase
{
private int _size;
public int Size
{
get { return _size; }
set
{
_size = value;
OnPropertyChanged("Size");
CreateItems();
}
}
private ObservableCollection<GridItem> _squares;
public ObservableCollection<GridItem> Squares
{
get { return _squares ?? (_squares = new ObservableCollection<GridItem>()); }
}
private ObservableCollection<GridItem> _route;
public ObservableCollection<GridItem> Route
{
get { return _route ?? (_route = new ObservableCollection<GridItem>()); }
}
private void CreateItems()
{
Squares.Clear();
Route.Clear();
for (int i = 0; i < Size; i++)
{
for (int j = 0; j < Size; j++)
{
Squares.Add(new GridItem() {Row = i, Column = j});
}
}
}
private Command<GridItem> _goToCommand;
public Command<GridItem> GoToCommand
{
get { return _goToCommand ?? (_goToCommand = new Command<GridItem>(Goto){IsEnabled = true}); }
}
private void Goto(GridItem item)
{
if (item.PathIndex == null)
{
item.PathIndex = Squares.Max(x => x.PathIndex ?? 0) + 1;
Route.Add(item);
}
}
}
Data Item:
public class GridItem: PropertyChangedBase
{
public int Row { get; set; }
public int Column { get; set; }
private int? _pathIndex;
public int? PathIndex
{
get { return _pathIndex; }
set
{
_pathIndex = value;
OnPropertyChanged("PathIndex");
}
}
}
Support Classes for MVVM:
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
Application.Current.Dispatcher.BeginInvoke((Action) (() =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}));
}
}
public class Command<T>: ICommand
{
public Action<T> Action { get; set; }
public void Execute(object parameter)
{
if (Action != null && parameter is T)
Action((T)parameter);
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
private bool _isEnabled;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged;
public Command(Action<T> action)
{
Action = action;
}
}
Result:
Just copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.
You said 10 x 10, but I went a step further and added a Slider to make the grid size customizable.
Clicking on any cell will make it be queued as part of the Route.
Fully Resolution Independent.
I was about to start putting some really nice stuff on it (animations, robot movement represented by an ellipse, Lines for the Path, etc).
Forget winforms, it's useless.

Categories

Resources