I am trying to bind the width and height of a Rect in a ViewPort like this:
<VisualBrush.Viewport>
<Rect Width="{Binding Path=MyWidth}" Height="{Binding Path=MyHeight}"/>
</VisualBrush.Viewport>
My binding works fine elsewhere but here I get the following error message:
A 'Binding' cannot be set on the 'Width' property of type 'Rect'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
Edit I understand the error message. My question is how to work around it. How do I bind the height and width of the rect?
Use a MultiBinding like this:
<VisualBrush.Viewport>
<MultiBinding>
<MultiBinding.Converter>
<local:RectConverter/>
</MultiBinding.Converter>
<Binding Path="MyWidth"/>
<Binding Path="MyHeight"/>
</MultiBinding>
</VisualBrush.Viewport>
with a multi-value converter like this:
public class RectConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return new Rect(0d, 0d, (double)values[0], (double)values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Updated Answer:
Rect is an struct and Height and Width are not dependency properties(see screen shot), so they can't be bound to anything.
Here is a way to do it using Dependency Property and Binding.
MyRect Class with Dependency Properties:
public class MyRect : DependencyObject,INotifyPropertyChanged
{
public MyRect()
{
this.Rect = new Rect(0d, 0d, (double)Width, (double)Height);
}
private Rect rect;
public Rect Rect
{
get { return rect; }
set
{
rect = value;
RaiseChange("Rect");
}
}
public double Height
{
get { return (double)GetValue(HeightProperty); }
set { SetValue(HeightProperty, value); }
}
// Using a DependencyProperty as the backing store for Height. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HeightProperty =
DependencyProperty.Register("Height", typeof(double), typeof(MyRect), new UIPropertyMetadata(1d, OnHeightChanged));
public static void OnHeightChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
var MyRect = (dp as MyRect);
var hight = Convert.ToDouble(e.NewValue);
MyRect.Rect = new Rect(0d, 0d, MyRect.Rect.Width, hight);
}
}
public double Width
{
get { return (double)GetValue(WidthProperty); }
set { SetValue(WidthProperty, value); }
}
// Using a DependencyProperty as the backing store for Width. This enables animation, styling, binding, etc...
public static readonly DependencyProperty WidthProperty =
DependencyProperty.Register("Width", typeof(double), typeof(MyRect), new UIPropertyMetadata(1d, OnWidthChanged));
public static void OnWidthChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
var MyRect = (dp as MyRect);
var width = Convert.ToDouble(e.NewValue);
MyRect.Rect = new Rect(0d, 0d, width, MyRect.Rect.Height);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseChange(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
}
View:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
MyRect = new TabControl.MyRect();
}
private MyRect myRect;
public MyRect MyRect
{
get { return myRect; }
set { myRect = value; RaiseChange("MyRect");}
}
private void MySlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (MyRect != null)
{
MyRect.Height = e.NewValue/10;
MyRect.Width = e.NewValue/10;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseChange(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
}
XAML:
<Window x:Class="TabControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TabControl"
Title="MainWindow" Height="450" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="80*"/>
<RowDefinition Height="80"/>
</Grid.RowDefinitions>
<Rectangle Name="recLogin" Height="300" Width="400" DataContext="{Binding MyRect}" >
<Rectangle.Fill>
<VisualBrush TileMode="None" Viewport="{Binding Rect}">
<VisualBrush.Visual>
<ScrollViewer Height="30" Width="100">
<Button Content="Transparent" Height="30" Width="80" />
</ScrollViewer>
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
<Slider Maximum="20" x:Name="MySlider" Value="4" TickFrequency="1" Grid.Row="1" TickPlacement="TopLeft" ValueChanged="MySlider_ValueChanged" />
</Grid>
Output:
Related
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();
}
}
All:
The more I search for solutions to this question the more confused I become. After spending 12-16 hours watching YouTube, reading StackOverflow and general goggling, I thought I'd plead for additional help.
I would like to create a custom control so I can write various apps to remote to my video switcher.
I've created my control with dependency properties and that part is working well.
This answer on SO seemed to get me close, but I still can't get my app to run.
How to wire up a click event for a custom usercontrol button? Should I use CustomControl?
What I simply want to do is click btnIn1 in the control and have it return a "1", btnIn2 returns a "2" and so on.
I've also read about delegates, ICommand, TemplateParts and MVVM patterns which all seem like incredibly complex ways to click a button within a group. Maybe there's just not a simple way to do it.
Here's what I have so far. I simplified everything to a 2x2 matrix switcher (rather than the 4x4 I'm working on)
Thanks for all your help.
Norm
Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VideoSwitcher"
xmlns:enk="clr-namespace:VideoSwitcher.Controls">
<Style TargetType="{x:Type enk:Matrix44}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type enk:Matrix44}">
<Grid x:Name="grdBase" HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40*"/>
<RowDefinition Height="100*"/>
<RowDefinition Height="40*"/>
<RowDefinition Height="100*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label x:Name="lblInputHeader" Content="Input"
Grid.Column="0"
Grid.Row="0"
Grid.ColumnSpan="4"
Visibility="{TemplateBinding HeaderVisible}"
FontFamily="{TemplateBinding HeaderFont}"
FontSize="{TemplateBinding HeaderFontSize}"/>
<StackPanel Orientation="Horizontal"
Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="4">
<Button x:Name="btnIn1"
Margin="{TemplateBinding ButtonMargin}"
Height="{TemplateBinding ButtonHeight}"
Width="{TemplateBinding ButtonWidth}"
Content="{TemplateBinding Input1Label}"
Click="btnIn1Click"/>
<Button x:Name="btnIn2"
Margin="{TemplateBinding ButtonMargin}"
Height="{TemplateBinding ButtonHeight}"
Width="{TemplateBinding ButtonWidth}"
Content="{TemplateBinding Input2Label}"
Click="btnIn2Click"/>
</StackPanel>
<Label x:Name="lblOutputHeader" Content="Output"
Grid.Column="0"
Grid.Row="2"
Grid.ColumnSpan="4"
Visibility="{TemplateBinding HeaderVisible}"
FontFamily="{TemplateBinding HeaderFont}"
FontSize="{TemplateBinding HeaderFontSize}"/>
<StackPanel Orientation="Horizontal"
Grid.Column="0"
Grid.Row="3"
Grid.ColumnSpan="4">
<Button x:Name="btnOut1"
Margin="{TemplateBinding ButtonMargin}"
Height="{TemplateBinding ButtonHeight}"
Width="{TemplateBinding ButtonWidth}"
Content="{TemplateBinding Output1Label}"
Click="btnOut1Click"/>
<Button x:Name="btnOut2"
Margin="{TemplateBinding ButtonMargin}"
Height="{TemplateBinding ButtonHeight}"
Width="{TemplateBinding ButtonWidth}"
Content="{TemplateBinding Output2Label}"
Click="btnOut2Click"/>
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Custom Control (Matrix44.cs)
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.ComponentModel;
namespace VideoSwitcher.Controls
{
public class Matrix44 : Control
{
#region Events - Go here if I can ever find out how to use them
#endregion
//This does not work --------
public event RoutedEventHandler Click;
void btnIn1Click(object sender, RoutedEventArgs e)
{
if (this.Click != null)
{
this.Click(this, e);
}
}
void btnIn2Click(object sender, RoutedEventArgs e)
{
if (this.Click != null)
{
this.Click(this, e);
}
}
void btnOut1Click(object sender, RoutedEventArgs e)
{
if (this.Click != null)
{
this.Click(this, e);
}
}
void btnOut2Click(object sender, RoutedEventArgs e)
{
if (this.Click != null)
{
this.Click(this, e);
}
}
#region Properties - Exposed to the user in the Properties panel, XAML or code-behind
#region Switcher Appearance Properies (Height, Width, Margin)
[Category("Switcher Appearance Properties")]
public double ButtonHeight
{
get { return (double)GetValue(ButtonHeightProperty); }
set { SetValue(ButtonHeightProperty, value); }
}
public static readonly DependencyProperty ButtonHeightProperty =
DependencyProperty.Register(nameof(ButtonHeight), typeof(double), typeof(Matrix44), new PropertyMetadata(50.0));
[Category("Switcher Appearance Properties")]
public double ButtonWidth
{
get { return (double)GetValue(ButtonWidthProperty); }
set { SetValue(ButtonWidthProperty, value); }
}
public static readonly DependencyProperty ButtonWidthProperty =
DependencyProperty.Register(nameof(ButtonWidth), typeof(double), typeof(Matrix44), new PropertyMetadata(50.0));
[Category("Switcher Appearance Properties")]
public Thickness ButtonMargin
{
get { return (Thickness)GetValue(ButtonMarginProperty); }
set { SetValue(ButtonMarginProperty, value); }
}
public static readonly DependencyProperty ButtonMarginProperty =
DependencyProperty.Register("ButtonMargin", typeof(Thickness), typeof(Matrix44));
#endregion
#region Labels
[Category("Switcher Label Properties")]
public string Input1Label
{
get { return (string)GetValue(Input1LabelProperty); }
set { SetValue(Input1LabelProperty, value); }
}
public static readonly DependencyProperty Input1LabelProperty =
DependencyProperty.Register(nameof(Input1Label), typeof(string), typeof(Matrix44), new PropertyMetadata("Input 1"));
[Category("Switcher Label Properties")]
public string Input2Label
{
get { return (string)GetValue(Input2LabelProperty); }
set { SetValue(Input2LabelProperty, value); }
}
public static readonly DependencyProperty Input2LabelProperty =
DependencyProperty.Register(nameof(Input2Label), typeof(string), typeof(Matrix44), new PropertyMetadata("Input 2"));
[Category("Switcher Label Properties")]
public string Output1Label
{
get { return (string)GetValue(Output1LabelProperty); }
set { SetValue(Output1LabelProperty, value); }
}
public static readonly DependencyProperty Output1LabelProperty =
DependencyProperty.Register(nameof(Output1Label), typeof(string), typeof(Matrix44), new PropertyMetadata("Output 1"));
[Category("Switcher Label Properties")]
public string Output2Label
{
get { return (string)GetValue(Output2LabelProperty); }
set { SetValue(Output2LabelProperty, value); }
}
public static readonly DependencyProperty Output2LabelProperty =
DependencyProperty.Register(nameof(Output2Label), typeof(string), typeof(Matrix44), new PropertyMetadata("Output 2"));
#endregion
#region Header Properties
[Category("Switcher Header Properties")]
public Visibility HeaderVisible
{
get { return (Visibility)GetValue(HeaderVisibleProperty); }
set { SetValue(HeaderVisibleProperty, value); }
}
public static readonly DependencyProperty HeaderVisibleProperty =
DependencyProperty.Register("HeaderVisible", typeof(Visibility), typeof(Matrix44));
[Category("Switcher Header Properties")]
public FontFamily HeaderFont
{
get { return (FontFamily)GetValue(HeaderFontProperty); }
set { SetValue(HeaderFontProperty, value); }
}
public static readonly DependencyProperty HeaderFontProperty =
DependencyProperty.Register("HeaderFont", typeof(FontFamily), typeof(Matrix44));
[Category("Switcher Header Properties")]
public double HeaderFontSize
{
get { return (double)GetValue(HeaderFontSizeProperty); }
set { SetValue(HeaderFontSizeProperty, value); }
}
public static readonly DependencyProperty HeaderFontSizeProperty =
DependencyProperty.Register("HeaderFontSize", typeof(double), typeof(Matrix44));
#endregion
#region Channel Properties
[Category("Switcher Channel Properties")]
//Channel Property - use to extend switcher tool capabilties; e.g. add new bank of ins/outs and remap input 1 to input 5 on 2nd bank
public int Input1Channel
{
get { return (int)GetValue(Input1ChannelProperty); }
set { SetValue(Input1ChannelProperty, value); }
}
public static readonly DependencyProperty Input1ChannelProperty =
DependencyProperty.Register("Input1Channel", typeof(int), typeof(Matrix44), new PropertyMetadata(1));
[Category("Switcher Channel Properties")]
public bool Input1Enabled
{
get { return (bool)GetValue(Input1EnabledProperty); }
set { SetValue(Input1EnabledProperty, value); }
}
public static readonly DependencyProperty Input1EnabledProperty =
DependencyProperty.Register("Input1Enabled", typeof(bool), typeof(Matrix44), new PropertyMetadata(false));
[Category("Switcher Channel Properties")]
public int Input2Channel
{
get { return (int)GetValue(Input2ChannelProperty); }
set { SetValue(Input2ChannelProperty, value); }
}
public static readonly DependencyProperty Input2ChannelProperty =
DependencyProperty.Register("Input2Channel", typeof(int), typeof(Matrix44), new PropertyMetadata(2));
[Category("Switcher Channel Properties")]
public bool Input2Enabled
{
get { return (bool)GetValue(Input1EnabledProperty); }
set { SetValue(Input1EnabledProperty, value); }
}
[Category("Switcher Channel Properties")]
public int Output1Channel
{
get { return (int)GetValue(Output1ChannelProperty); }
set { SetValue(Output1ChannelProperty, value); }
}
//Output channels
public static readonly DependencyProperty Output1ChannelProperty =
DependencyProperty.Register("Output1Channel", typeof(int), typeof(Matrix44), new PropertyMetadata(1));
[Category("Switcher Channel Properties")]
public int Output2Channel
{
get { return (int)GetValue(Output2ChannelProperty); }
set { SetValue(Output2ChannelProperty, value); }
}
public static readonly DependencyProperty Output2ChannelProperty =
DependencyProperty.Register("Output2Channel", typeof(int), typeof(Matrix44), new PropertyMetadata(2));
#endregion
#endregion
public Matrix44()
{
DefaultStyleKey = typeof(Matrix44);
}
}
}
MainWindow.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:VideoSwitcher"
xmlns:Controls="clr-namespace:VideoSwitcher.Controls" x:Class="VideoSwitcher.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="200">
<Grid Margin="0,1,0,0">
<Controls:Matrix44 x:Name="swtMatrix"
HorizontalAlignment="Left"
Margin="10,10,0,0"
VerticalAlignment="Top"
ButtonHeight="65"
ButtonMargin="4"
ButtonWidth="65"/>
<Button x:Name="btnTake"
Content="Take"
HorizontalAlignment="Left"
Margin="10,213,0,0"
VerticalAlignment="Top"
Width="146"
Height="45" Click="btnTake_Click"/>
</Grid>
MainWindow.xaml.cs
using System.Windows;
namespace VideoSwitcher
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public int VideoInputChannel { get; set; }
public int VideoOutputChannel { get; set; }
public MainWindow()
{
InitializeComponent();
AddLabelsToMatrix();
}
public void AddLabelsToMatrix()
{
swtMatrix.Input1Label = "DVR1";
swtMatrix.Input2Label = "DVR2";
swtMatrix.Output1Label = "Videowall";
swtMatrix.Output2Label = "US Right";
}
private void btnTake_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Input channel: " + VideoInputChannel + " routed to Output: " + VideoOutputChannel);
}
/* switcher button psuedo-code
btnIn1_Click (object sender, RoutedEventArgs e)
{
//get input channel of matrix switcher
VideoInputChannel = swtMatrix.btnIn1.Channel;
}
btnIn2_Click (object sender, RoutedEventArgs e)
{
//get input channel of matrix switcher
VideoInputChannel = swtMatrix.btnIn2.Channel;
}
btnOut1_Click (object sender, RoutedEventArgs e)
{
//get output channel of matrix switcher
VideoInputChannel = swtMatrix.btnIn1.Channel;
}
btnOut2_Click (object sender, RoutedEventArgs e)
{
//get output channel of matrix switcher
VideoInputChannel = swtMatrix.btnIn2.Channel;
}
*/
}
}
You could override the OnApplyTemplate() method to get a reference to each Button and then hook up the event handlers:
public class Matrix44 : Control
{
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Button btnOut1 = this.Template.FindName("btnOut1", this) as Button;
if (btnOut1 != null)
btnOut1.Click += btnIn1Click;
//...and so on for each Button
}
}
You could then either raise a specific event for each Button or define a custom EventArgs that can be used to identify which Button that was clicked in an event handler:
C# event with custom arguments
similar to my Labeled TextBox, which issues are resolved in:
Labeled TextBox in Windows Universal App
I got two issues in my Labeled Combobox, but first the Code:
Generic.xaml:
<Style TargetType="template:LabeledComboBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="template:LabeledComboBox">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="{TemplateBinding Label}" FontWeight="Bold" VerticalAlignment="Center" Margin="10,0" />
<ComboBox x:Name="PART_ComboBox" ItemsSource="{TemplateBinding ItemsSource}" SelectedIndex="{TemplateBinding SelectedIndex}" SelectedValue="{TemplateBinding SelectedValue}" SelectedValuePath="{TemplateBinding SelectedValuePath}" DisplayMemberPath="{TemplateBinding DisplayMemberPath}" VerticalAlignment="Center" Margin="20,0,10,0" Grid.Row="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
LabeledComboBox.cs:
[TemplatePart(Name = "PART_ComboBox", Type = typeof(ComboBox))]
public sealed class LabeledComboBox : Control, IParameterReturnable
{
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register("Label", typeof(string), typeof(LabeledComboBox), new PropertyMetadata(""));
public string Label
{
get { return GetValue(LabelProperty).ToString(); }
set { SetValue(LabelProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(object), typeof(LabeledComboBox), new PropertyMetadata(null));
public object ItemsSource
{
get { return GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty SelectedIndexProperty = DependencyProperty.Register("SelectedIndex", typeof(int), typeof(LabeledComboBox), new PropertyMetadata(default(int)));
public int SelectedIndex
{
get { return (int) GetValue(SelectedIndexProperty); }
set { SetValue(SelectedIndexProperty, value); }
}
public static readonly DependencyProperty SelectedValueProperty = DependencyProperty.Register("SelectedValue", typeof(object), typeof(LabeledComboBox), new PropertyMetadata(null));
public object SelectedValue
{
get { return GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
public static readonly DependencyProperty SelectedValuePathProperty = DependencyProperty.Register("SelectedValuePath", typeof(string), typeof(LabeledComboBox), new PropertyMetadata(default(string)));
public string SelectedValuePath
{
get { return GetValue(SelectedValuePathProperty).ToString(); }
set { SetValue(SelectedValuePathProperty, value); }
}
public static readonly DependencyProperty DisplayMemberPathProperty = DependencyProperty.Register("DisplayMemberPath", typeof(string), typeof(LabeledComboBox), new PropertyMetadata(default(string)));
public string DisplayMemberPath
{
get { return GetValue(DisplayMemberPathProperty).ToString(); }
set { SetValue(DisplayMemberPathProperty, value); }
}
private ComboBox _comboBox;
public LabeledComboBox()
{
this.DefaultStyleKey = typeof(LabeledComboBox);
}
public LabeledComboBox(List<Parameter> parameterList)
{
this.Label = parameterList[0].DisplayName ?? "";
this.ItemsSource = parameterList;
this.SelectedValuePath = "DefaultValue";
this.DisplayMemberPath = "DefaultValue";
this.SelectedIndex = 0;
this.DefaultStyleKey = typeof(LabeledComboBox);
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
_comboBox = GetTemplateChild("PART_ComboBox") as ComboBox;
if (_comboBox != null)
{
_comboBox.SelectionChanged += OnComboBoxSelectionChanged;
if (_comboBox.Items != null)
{
this.SelectedIndex = 0;
_comboBox.SelectedValue = _comboBox.Items[this.SelectedIndex];
}
}
}
private void OnComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedValue = _comboBox.SelectedValue;
}
public string GetKey()
{
return Label;
}
public string GetValue()
{
return SelectedValue.ToString();
}
}
It will be called in two different ways:
Dynamically in C#:
stackPanel.Add(new LabeledComboBox(parameterList));
Static in Xaml:
<templates:LabeledComboBox Label="Kategorien:" ItemsSource="{Binding ElementName=pageRoot, Path=FeedCategories}" DisplayMemberPath="Name" SelectedValuePath="Name" />
As I said before I got two issues with it:
How can I bind the SelectionChangedEvent to access it in Xaml || C#
As you can see, I try to preselect the first Item, which does not work and I don't know how to do it right
Thank you very much for all helpful and well meant answers in advance!
Instead of creating a custom control and recreating all needed dependency properties, I would suggest you use the Header and HeaderTemplate properties of the built in ComboBox, which will be displayed, just like in your LabeledComboBox, above the selection menu. Additionally the SelectionChanged event will be available.
So the usage in XAML would look like the following:
<ComboBox
DisplayMemberPath="Name"
Header="Kategorien:"
ItemsSource="{Binding ElementName=pageRoot, Path=FeedCategories}"
SelectedValuePath="Name"
SelectionChanged="OnSelectionChanged">
<ComboBox.HeaderTemplate>
<DataTemplate>
<TextBlock
Margin="10,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding}" />
</DataTemplate>
</ComboBox.HeaderTemplate>
</ComboBox>
But if you don't want to use the above method, to expose the selection changed event in your LabeledComboBox, add the following code:
private void OnComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedValue = _comboBox.SelectedValue;
this.RaiseSelectionChanged(e);
}
public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
private void RaiseSelectionChanged(SelectionChangedEventArgs args)
{
if (SelectionChanged != null)
{
SelectionChanged(this, args);
}
}
Then you can use the created SelectionChanged event from XAML.
I'd like to create my own UserControl (let's call it "RectAtCoordinates") that would work similarly to Canvas, however it should:
-Store collection of (x,y) integer coordinates
-Draw rectangle (with arbitrary chosen size and fill) on canvas for each (x,y) pair. (x,y) specify position of rectangle.
First of all, I've created simple Coordinates class:
class Coordinates : DependencyObject
{
public static readonly DependencyProperty XProperty =
DependencyProperty.Register("X", typeof(int), typeof(Coordinates));
public static readonly DependencyProperty YProperty =
DependencyProperty.Register("Y", typeof(int), typeof(Coordinates));
public int X
{
get { return (int)GetValue(XProperty); }
set { SetValue(XProperty, value); }
}
public int Y
{
get { return (int)GetValue(YProperty); }
set { SetValue(YProperty, value); }
}
public Coordinates(int x, int y)
{
this.X = x;
this.Y = y;
}
}
Here's my RectAtCoordinates UserControl (.xaml):
<UserControl x:Class="RectAtPoint.RectAtCoordinates"
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">
<ItemsControl ItemsSource="{Binding Path=Coos, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="300" Height="300" Background="White"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Fill="Black" Width="50" Height="50"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</UserControl>
And finally code behind RectAtCoordinates:
public partial class RectAtCoordinates : UserControl
{
public static readonly DependencyProperty CoosProperty =
DependencyProperty.Register("Coos", typeof(Coordinates), typeof(RectAtCoordinates));
private ObservableCollection<Coordinates> Coos
{
get { return (ObservableCollection<Coordinates>)GetValue(CoosProperty); }
set { SetValue(CoosProperty, value); }
}
public RectAtCoordinates()
{
InitializeComponent();
Coos = new ObservableCollection<Coordinates>();
}
public void AddRectangleAtPosition(int x, int y)
{
Coos.Add(new Coordinates(x, y));
}
}
However, after building my project it crashes. I get CLR20r3 problem. After further inspection I've changed RectAtCoordinates constructor into:
public RectAtCoordinates()
{
InitializeComponent();
try
{
Coos = new ObservableCollection<Coordinates>();
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}
And got this error:
System.ArgumentException:
'System.Collections.ObjectModel.ObservableCollection`1[RectAtPoint.Coordinates]'
is not a valid value for property 'Coos'.
at
System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp,
Object value, PropertyMetadata metadata, Boolean
coerceWithDeferredReference, Boolean coerceWithCurrentValue,
OperationType operationType, Boolean isInternal)
at System.Windows.DependencyObject.SetValue(DependencyProperty dp,
Object value)
at RectAtPoint.RectAtCoordinates.set_Coos(ObservableCollection`1
value) in c:...\RectAtCoordinates.xaml.cs:line 28
at RectAtPoint.RectAtCoordinates..ctor() in
c:...\RectAtCoordinates.xaml.cs:line 36
I'm novice considering WPF, binding and Dependency Properties, so please, take into consideration that I could've made some basic mistakes. I've found many similar problems, but I couldn't fully understand solutions and therefore properly apply them.
I think that your problem is in here:
public static readonly DependencyProperty CoosProperty =
DependencyProperty.Register("Coos", typeof(Coordinates), typeof(RectAtCoordinates));
Is should be:
public static readonly DependencyProperty CoosProperty =
DependencyProperty.Register("Coos", typeof(ObservableCollection<Coordinates>), typeof(RectAtCoordinates));
Give it a try :)
=== EDIT === For the coordinates.
I think you can do something like:
public void AddRectangleAtPosition(int x, int y)
{
Coos.Add(new Coordinates(x, y));
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Coos"));
}
}
Then your class should have:
public partial class RectAtCoordinates : UserControl, INotifyPropertyChanged
And after that you can have a region like I usually have for the NotifyPropertyChanged as this:
#region notifypropertychanged region
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
#endregion
}
Give a try :)
I've finally found a solution: data context was not set properly.
I had to add
this.DataContext = this;
to UserControl's constructor or add:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
to UserControl's xaml definition.
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