I have a problem, and I have not found the solution yet. I woud like to create a base custom control and use it in another custom control. The base control works fine when I use it in a window, but when I use it in the other custom control, the binding does not work.
What's wrong with my code?
Code:
Model:
public class ElementModel
{
public string Name { get; set; }
public string FullName { get; set; }
}
The base control:
public class ListControl : Control
{
static ListControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ListControl), new FrameworkPropertyMetadata(typeof(ListControl)));
}
public ListControl()
{
SetValue(ElementListProperty, new List<ElementModel>());
}
public static readonly DependencyProperty ElementListProperty =
DependencyProperty.Register(
"ElementList",
typeof(List<ElementModel>),
typeof(ListControl),
new FrameworkPropertyMetadata(new List<ElementModel>())
);
public List<ElementModel> ElementList
{
get { return (List<ElementModel>)GetValue(ElementListProperty); }
set { SetValue(ElementListProperty, value); }
}
}
The Wrapper Control:
public class ListWrapper : Control
{
static ListWrapper()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ListWrapper), new FrameworkPropertyMetadata(typeof(ListWrapper)));
}
public ListWrapper()
{
SetValue(EMListProperty, new List<ElementModel>());
}
public static readonly DependencyProperty EMListProperty =
DependencyProperty.Register(
"EMList",
typeof(List<ElementModel>),
typeof(ListWrapper),
new FrameworkPropertyMetadata(new List<ElementModel>())
);
public List<ElementModel> EMList
{
get { return (List<ElementModel>)GetValue(EMListProperty); }
set { SetValue(EMListProperty, value); }
}
}
File 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:UIControl">
<Style TargetType="{x:Type local:ListControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ListControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ListBox ItemsSource="{TemplateBinding ElementList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="Name:"/>
<TextBlock Text="{Binding Path=Name}" />
<Label Content="Full name:"/>
<TextBlock Text="{Binding Path=FullName}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:ListWrapper}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ListWrapper}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<local:ListControl ElementList="{TemplateBinding EMList}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
If I put the controls in the window and binding properties, than the ListControl works fine and shows the elements, but the WrapperList does not.
<Window x:Class="MainApplication.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="clr-namespace:UIControl;assembly=UIControl"
Title="Window1" Height="304" Width="628">
<Grid>
<ui:ListControl x:Name="listCtr" ElementList="{Binding Path=EList}" HorizontalAlignment="Left" Width="300" />
<ui:ListWrapper x:Name="listWrp" EMList="{Binding Path=EList}" HorizontalAlignment="Right" Width="300" Background="Gray"/>
</Grid>
And the test data injection:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = this;
}
List<ElementModel> elist = null;
public List<ElementModel> EList
{
get
{
if (elist == null)
{
elist = new List<ElementModel>();
ElementModel em = new ElementModel() { Name = "Apple", FullName = "Red Apple" };
elist.Add(em);
em = new ElementModel() { Name = "Pineapple", FullName = "Yellow Pineapple" };
elist.Add(em);
}
return elist;
}
}
}
Project archive
I don’t know why, but if I should wrap this list of element (List) into new collection type (example ElementCollection : ObservalbeCollection) and use this collection type in the dependency property, it works fine...
public static readonly DependencyProperty EMListProperty =
DependencyProperty.Register(
"EMList",
typeof(ElementCollection),
typeof(ListWrapper),
new FrameworkPropertyMetadata(new ElementCollection())
);
public ElementCollection EMList
{
get { return (ElementCollection)GetValue(EMListProperty); }
set { SetValue(EMListProperty, value); }
}
I couldn't find anywhere where you are setting the binding context:
http://msdn.microsoft.com/en-us/library/ms752347.aspx
Related
I am trying to figure out how I can set the "IsEnabled" property to false on a Button but then have the children (in my case a Combobox) be enabled. It seems the children inherit the property value from the parent element (in this case the disabled button) and therefore also disables itself. How can I prevent this?
I have tried to lookup answers but they all use "Override Metadata" which WinUI3 and Windows App SDK do not contain.
<Button
x:Name="Button1"
IsEnabled="False">
<Button.Content>
<ComboBox x:Name="ComboBox1" />
</Button.Content>
</Button>
You can create a custom control like this:
Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Buttons">
<Style TargetType="local:CustomButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomButton">
<Grid
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Button
x:Name="ButtonControl"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsEnabled="{TemplateBinding IsButtonEnabled}" />
<ContentControl
x:Name="ContentControl"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"
IsEnabled="{TemplateBinding IsContentEnabled}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
CustomButton.cs
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Buttons;
[TemplatePart(Name = nameof(ButtonControl), Type = typeof(Button))]
[TemplatePart(Name = nameof(ContentControl), Type = typeof(ContentControl))]
public sealed class CustomButton : ContentControl
{
public static readonly DependencyProperty IsButtonEnabledProperty = DependencyProperty.Register(
nameof(IsButtonEnabled),
typeof(bool),
typeof(CustomButton),
new PropertyMetadata(true));
public static readonly DependencyProperty IsContentEnabledProperty = DependencyProperty.Register(
nameof(IsContentEnabled),
typeof(bool),
typeof(CustomButton),
new PropertyMetadata(true));
public CustomButton()
{
this.DefaultStyleKey = typeof(CustomButton);
}
public bool IsButtonEnabled
{
get => (bool)GetValue(IsButtonEnabledProperty);
set => SetValue(IsButtonEnabledProperty, value);
}
public bool IsContentEnabled
{
get => (bool)GetValue(IsContentEnabledProperty);
set => SetValue(IsContentEnabledProperty, value);
}
private Button? ButtonControl { get; set; }
private ContentControl? ContentControl { get; set; }
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
ButtonControl = GetTemplateChild(nameof(ButtonControl)) as Button;
if (GetTemplateChild(nameof(ContentControl)) is ContentControl contentControl)
{
if (ContentControl is not null)
{
ContentControl.SizeChanged -= ContentControl_SizeChanged;
}
ContentControl = contentControl;
ContentControl.SizeChanged += ContentControl_SizeChanged;
}
}
private void ContentControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (sender is ContentControl contentControl &&
ButtonControl is not null)
{
ButtonControl.Width = contentControl.ActualWidth + 20.0;
ButtonControl.Height = contentControl.ActualHeight + 20.0;
}
}
}
And use it like this:
MainWindow.xaml
<Grid>
<local:CustomButton
IsButtonEnabled="False"
IsContentEnabled="True">
<ComboBox
SelectedIndex="0"
SelectionChanged="ComboBox_SelectionChanged">
<x:String>Blue</x:String>
<x:String>Green</x:String>
<x:String>Red</x:String>
</ComboBox>
</local:CustomButton>
</Grid>
I have this object of class type HouseInfo that contains a list property:
public class HouseInfo
{
public string House
{
get;
set;
}
public List<String> Details
{
get;
set;
}
}
public List<HouseInfo> HouseInfos { get; set; }
I am successfully binding the House property to main items of combo box using ItemSource property in xaml but can't figure out the binding of Details to their respective submenus.
<ComboBox x:Name="Houses1"
Grid.Row="1"
Grid.Column="4"
ItemsSource="{Binding HouseInfos}"
Padding="0"
DisplayMemberPath="House"
VerticalContentAlignment="Center"
VerticalAlignment="Top"
HorizontalContentAlignment="Stretch"
Margin="0,0,0,2">
</ComboBox>
I tried customizing menuitems in xaml but I get the error "itemsCollection must be empty before using items Source."
How do I get the Details list in each menu item as submenu items?
Any help would be appreciated. Thanks in advance.
Update:
I have bound submenu items as well but they are not visible. I am sure they have bound successfully as it generates submenu items equal to the count of the list inside the details property list of the object. This is the updated xaml for the menu:
<Menu x:Name="menu"
VerticalAlignment="Top"
Grid.Row="1"
Grid.Column="4"
Height="19">
<MenuItem ItemsSource="{Binding HouseInfos}"
Padding="0"
Background="#0068FF11"
VerticalAlignment="Top"
RenderTransformOrigin="0.5,0.5"
Height="19"
Width="105">
<MenuItem.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform />
<RotateTransform />
<TranslateTransform X="0.5" />
</TransformGroup>
</MenuItem.RenderTransform>
<MenuItem.Header>
<Label x:Name="headerYears"
Margin="0"
Padding="0"
Content="Houses"
Background="#00FF0000"
MaxHeight="18"
UseLayoutRounding="False"
RenderTransformOrigin="0,0"
HorizontalContentAlignment="Center" />
</MenuItem.Header>
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header"
Value="{Binding House}" />
<Setter Property="ItemsSource"
Value="{Binding InfoPoints}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</Menu>
Here is the image of menu which is populated but not visible.
Bound but invisible submenu items
Try using the DataSource property of the combobox. You can assign HouseInfos.House1.
What I did was I dynamically assign them to the combobox
comboBox1.DataSource = HouseInfo.House1.Details;
comboBox1.DisplayMember = "HouseDetails";
comboBox1.ValueMember = "HouseDetailsID";
Or you can try something like the above.
Use this structure. I matched the names with your own names.
MainWindw.xaml
<Window x:Class="MyNameSpace.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:MyNameSpace"
mc:Ignorable="d"
Title="TestMenu" Height="450" Width="800">
<DockPanel>
<Menu DockPanel.Dock="Top" ItemsSource="{Binding MenuItems}">
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</Menu.ItemContainerStyle>
<Menu.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:HouseInfo}" ItemsSource="{Binding Path=Details}">
<TextBlock Text="{Binding House}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
<Grid>
</Grid>
</DockPanel>
</Window>
MainWindow.cs
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
namespace MyNameSpace
{
/// <summary>
/// Interaction logic for MainWindw.xaml
/// </summary>
public partial class MainWindw : Window
{
public List<HouseInfo> MenuItems { get; set; }
public MainWindw()
{
InitializeComponent();
MenuItems = new List<HouseInfo>();
HouseInfo houseInfo1 = new HouseInfo();
houseInfo1.House = "Header A";
houseInfo1.Details = new List<HouseInfo>() { new HouseInfo() { House = "Header A1" }, new HouseInfo() { House = "Header A2" } };
HouseInfo houseInfo2 = new HouseInfo();
houseInfo2.House = "Header B";
houseInfo2.Details = new List<HouseInfo>() { new HouseInfo() { House = "Header B1" }, new HouseInfo() { House = "Header B2" } };
MenuItems.Add(houseInfo1);
MenuItems.Add(houseInfo2);
DataContext = this;
}
}
public class HouseInfo
{
public string House
{
get;
set;
}
public List<HouseInfo> Details { get; set; }
private readonly ICommand _command;
public HouseInfo()
{
_command = new CommandViewModel(Execute);
}
public ICommand Command
{
get
{
return _command;
}
}
private void Execute()
{
// (NOTE: In a view model, you normally should not use MessageBox.Show()).
MessageBox.Show("Clicked at " + House);
}
}
public class CommandViewModel : ICommand
{
private readonly Action _action;
public CommandViewModel(Action action)
{
_action = action;
}
public void Execute(object o)
{
_action();
}
public bool CanExecute(object o)
{
return true;
}
public event EventHandler CanExecuteChanged
{
add { }
remove { }
}
}
}
you can gave style to every element with this code
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</Menu.ItemContainerStyle>
for example add this line to HouseInfo class
public Thickness Margin { get; set; }
and MainWindow.cs
MenuItems = new List<HouseInfo>();
HouseInfo houseInfo1 = new HouseInfo();
houseInfo1.House = "Header A";
houseInfo1.Margin = new Thickness(5);
houseInfo1.Details = new List<HouseInfo>() { new HouseInfo() { House = "Header A1" }, new HouseInfo() { House = "Header A2", Margin=new Thickness(10) } };
and set Style in xaml
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
<Setter Property="Margin" Value="{Binding Margin}" />
</Style>
</Menu.ItemContainerStyle>
test:
I want to bind a property (myHeight) in my Controltemplate to the parent. The following is my code so far.
Resource dict
<Style TargetType="local2:TestingControl" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local2:TestingControl">
<Border
Height="{TemplateBinding myHeight}"
Background="Green"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel>
<ContentPresenter Content="{TemplateBinding Content}"/>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
TestingControl.cs
[ContentProperty(Name = "Content")]
public sealed class TestingControl : Control
{
public TestingControl()
{
this.DefaultStyleKey = typeof(TestingControl);
}
public static readonly double myHeight = (double)100;
public object Content
{
get { return (string)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(string), typeof(TestingControl), new PropertyMetadata(string.Empty));
}
What i'm trying to bind is the myHeight. I'd like to have this in the .cs since I need to run some operations on it. This fails to load entirely!
I also tried the following approach
Resource dict
<x:Double x:Key="myHeight">100</x:Double>
<Style TargetType="local2:TestingControl" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local2:TestingControl">
<Border
Height="{ThemeResource myHeight}"
Background="Green"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter Content="{TemplateBinding Content}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
TestingControl.cs
[ContentProperty(Name = "Content")]
public sealed class TestingControl : Control
{
public TestingControl()
{
this.DefaultStyleKey = typeof(TestingControl);
var x = (double)Resources["myHeight"];
}
public object Content
{
get { return (string)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(string), typeof(TestingControl), new PropertyMetadata(string.Empty));
}
The problem with the second approach, is that when reading the property in the .cs code, var x = (double)Resources["myHeight"]; I get an exception.
Resolutions to either (preferably both, since I'm just trying to learn UWP) would be greatly appreciated.
The first thing is TemplateBinding should bind the dependency property and you write the static filed that can not bind to Height.
The second thing is ThemeResource will find the Theme but you define a static source.
<x:Double x:Key="myHeight">100</x:Double>
<Style TargetType="local2:TestingControl" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local2:TestingControl">
<Border
Height="{StaticResource myHeight}"
Background="Green"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter Content="{TemplateBinding Content}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The third thing is you get the resource at first but the resource inits after OnApplyTemplate.
You should move the code that gets the resource to OnApplyTemplate.
protected override void OnApplyTemplate()
{
try
{
// find something in TestingControl.Resources
var x = Resources["myHeight"];
}
catch (Exception e)
{
}
try
{
// find something in App.Resources
var x = App.Current.Resources["myHeight"];
}
catch (Exception e)
{
}
base.OnApplyTemplate();
}
If your resource is written in App.xaml that you should use App.Current.Resources to get the resource.
If you want to get the resource in your customs control that you should add the resource in your control.
<local:TestingControl>
<local:TestingControl.Resources>
<x:Double x:Key="myHeight">100</x:Double>
</local:TestingControl.Resources>
</local:TestingControl>
I have a canvas, and I want to give it grid lines as a background, but I want there to be a constant number of grid lines that divide the canvas into equally-sized sections, rather than just have equally-spaced grid-lines. I want this to be preserved when the canvas is resized by the user.
How should I do this?
Here is a solution which is based in two wpf ListView controls behind the canvas(one for rows and second for columns). The content of the columns related ListView control is a rectangle.
Updated version - Managed Grid Lines Control. Here you can manage the number of grid lines and their visibility.
Xaml code - grid lines control:
<UserControl x:Class="CAnvasWithGrid.GridLineControl"
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"
xmlns:canvasWithGrid="clr-namespace:CAnvasWithGrid"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" x:Name="This">
<Grid x:Name="LayoutRoot">
<Grid.Resources>
<Style TargetType="ListView">
<Setter Property="Background" Value="Transparent"/>
</Style>
<Style x:Key="ListViewItemStyle" TargetType="ListViewItem">
<Setter Property="Background" Value="Transparent"/>
</Style>
<DataTemplate x:Key="InnerListviewDataTemplate" DataType="{x:Type canvasWithGrid:CellModel}">
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Margin="0" StrokeDashArray="4" Stroke="Black" StrokeThickness="0.5" Fill="Transparent"/>
</DataTemplate>
<DataTemplate x:Key="ListviewDataTemplate" DataType="{x:Type canvasWithGrid:RowModel}">
<ListView ItemsSource="{Binding CellModels}" BorderBrush="#00FFFFFF" BorderThickness="0" Margin="0"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding CellModels, Converter={canvasWithGrid:CollectionLength2NumberConverter}}"></UniformGrid>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem" BasedOn="{StaticResource ListViewItemStyle}">
<Setter Property="Margin" Value="0"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ContentPresenter Content="{TemplateBinding Content}" Margin="0"
ContentTemplate="{StaticResource InnerListviewDataTemplate}" />
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ContentTemplate" Value="{StaticResource InnerListviewDataTemplate}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</DataTemplate>
</Grid.Resources>
<ListView ItemsSource="{Binding ElementName=This, Path=RowModels}" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding ElementName=This, Path=RowModels, Converter={canvasWithGrid:CollectionLength2NumberConverter}}"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem" BasedOn="{StaticResource ListViewItemStyle}">
<Setter Property="Margin" Value="0"></Setter>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ContentPresenter Content="{TemplateBinding Content}" Margin="-1"
ContentTemplate="{StaticResource ListviewDataTemplate}" />
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ContentTemplate" Value="{StaticResource ListviewDataTemplate}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
Grid lines control - code behind
/// <summary>
/// Interaction logic for GridLineControl.xaml
/// </summary>
public partial class GridLineControl : UserControl
{
public GridLineControl()
{
InitializeComponent();
}
public static readonly DependencyProperty NumberOfColumnsProperty = DependencyProperty.Register(
"NumberOfColumns", typeof (int), typeof (GridLineControl), new PropertyMetadata(default(int), NumberOfColumnsChangedCallback));
private static void NumberOfColumnsChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var numberOfRows = (int)dependencyObject.GetValue(NumberOfRowsProperty);
var numberOfColumns = (int)args.NewValue;
if (numberOfColumns == 0 || numberOfRows == 0) return;
var rowModelsCollection = GetRowModelsCollection(numberOfRows, numberOfColumns);
dependencyObject.SetValue(RowModelsProperty, rowModelsCollection);
}
public int NumberOfColumns
{
get { return (int) GetValue(NumberOfColumnsProperty); }
set { SetValue(NumberOfColumnsProperty, value); }
}
public static readonly DependencyProperty NumberOfRowsProperty = DependencyProperty.Register(
"NumberOfRows", typeof (int), typeof (GridLineControl), new PropertyMetadata(default(int), NumberOfRowsChangedCallback));
private static void NumberOfRowsChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var numberOfRows = (int)args.NewValue;
var numberOfColumns = (int)dependencyObject.GetValue(NumberOfColumnsProperty);
if(numberOfColumns == 0 || numberOfRows == 0) return;
var rowModelsCollection = GetRowModelsCollection(numberOfRows, numberOfColumns);
dependencyObject.SetValue(RowModelsProperty, rowModelsCollection);
}
private static ObservableCollection<RowModel> GetRowModelsCollection(int numberOfRows, int numberOfColumns)
{
var rowModelsCollection = new ObservableCollection<RowModel>();
for (var i = 0; i < numberOfRows; i++)
{
rowModelsCollection.Add(new RowModel(numberOfColumns) {Position = (i + 1).ToString()});
}
return rowModelsCollection;
}
public int NumberOfRows
{
get { return (int) GetValue(NumberOfRowsProperty); }
set { SetValue(NumberOfRowsProperty, value); }
}
public static readonly DependencyProperty RowModelsProperty = DependencyProperty.Register("RowModels",
typeof(ObservableCollection<RowModel>), typeof(GridLineControl),
new PropertyMetadata(default(ObservableCollection<RowModel>)));
public ObservableCollection<RowModel> RowModels
{
get { return (ObservableCollection<RowModel>)GetValue(RowModelsProperty); }
private set { SetValue(RowModelsProperty, value); }
}
}
Models:
public class RowModel:BaseGridMember
{
public RowModel(int numberOfCellsInRow)
{
CellModels = new ObservableCollection<CellModel>();
for (int i = 0; i < numberOfCellsInRow; i++)
{
CellModels.Add(new CellModel{Position = (i+1).ToString()});
}
}
public ObservableCollection<CellModel> CellModels { get; set; }
}
public class CellModel:BaseGridMember
{
}
public class BaseGridMember:BaseObservableObject
{
private string _position;
public string Position
{
get { return _position; }
set
{
_position = value;
OnPropertyChanged();
}
}
}
Main window xaml code - as you can see here is a ImageContol instead of Canvas but you can replace it:
<Window x:Class="CAnvasWithGrid.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:canvasWithGrid="clr-namespace:CAnvasWithGrid"
Title="MainWindow" Height="525" Width="525" x:Name="This">
<Grid Tag="{Binding ElementName=This}">
<Grid.Resources>
<BooleanToVisibilityConverter x:Key="Bool2VisConvKey" />
</Grid.Resources>
<Grid.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Show Grid Lines" Command="{Binding ShowGridLinesCommand}"/>
</ContextMenu>
</Grid.ContextMenu>
<Image Source="Resources/Koala.jpg" Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MouseDown="UIElement_OnMouseDown"/>
<canvasWithGrid:GridLineControl NumberOfRows="50" NumberOfColumns="50"
IsHitTestVisible="False" Visibility="{Binding ElementName=This, Path=AreGridLineVisible, Converter={StaticResource Bool2VisConvKey}, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
Main window code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ShowGridLinesCommand = new RelayCommand(ShowGridLineManageCommand);
AreGridLineVisible = true;
}
private void ShowGridLineManageCommand()
{
AreGridLineVisible = !AreGridLineVisible;
}
public static readonly DependencyProperty AreGridLineVisibleProperty = DependencyProperty.Register(
"AreGridLineVisible", typeof (bool), typeof (MainWindow), new PropertyMetadata(default(bool)));
public bool AreGridLineVisible
{
get { return (bool) GetValue(AreGridLineVisibleProperty); }
set { SetValue(AreGridLineVisibleProperty, value); }
}
public static readonly DependencyProperty ShowGridLinesCommandProperty = DependencyProperty.Register(
"ShowGridLinesCommand", typeof (ICommand), typeof (MainWindow), new PropertyMetadata(default(ICommand)));
public ICommand ShowGridLinesCommand
{
get { return (ICommand) GetValue(ShowGridLinesCommandProperty); }
set { SetValue(ShowGridLinesCommandProperty, value); }
}
private void UIElement_OnMouseDown(object sender, MouseButtonEventArgs e)
{
}
}
How it looks like:
Sounds like a candidate for a custom control with custom drawing. You don't really want to use multiple FrameworkElements like "Line" if you are expecting many grid-lines for performance reasons.
So you'd create a customControl GridLinesControl and overwrite the OnRender method. You can get the actual width and height of the component using the properties ActualWidth and ActualHeight, divide by the number of grid lines you want and draw lines using drawingContext.DrawLine.
The easiest way would be to add the GridLinesControl you've made underneath the canvas, taking up the same space (so it has the right ActualWidth and ActualHeight) like this:
<Grid>
<myControls:GridLinesControl/>
<Canvas ... />
</Grid>
So it's always underneath.
My requirement is Binding dynamic property to my Value (double) property of Custom control.But it not working as expected.
Below is the code :
Simple customcontrol part :
xaml
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBox x:Name="PART_TextBox" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
c#
public class CustomControl1 : Control
{
public double? Value
{
get { return (double?)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double?), typeof(CustomControl1), new PropertyMetadata(null, new PropertyChangedCallback(OnValueChanged), new CoerceValueCallback(CoerceValueChange)));
private static object CoerceValueChange(DependencyObject d, object baseValue)
{
return baseValue;
}
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((CustomControl1)d).OnValueChanged(e);
}
private void OnValueChanged(DependencyPropertyChangedEventArgs e)
{
if (tb != null && e.NewValue != null)
tb.Text = e.NewValue.ToString();
else
tb.Text = string.Empty;
}
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
TextBox tb = null;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
tb = base.GetTemplateChild("PART_TextBox") as TextBox;
}
}
here goes the usage :
xaml
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<Label Content="Double TextBox without Converter" />
<Label Content="Double TextBox with Converter" />
<Label Content="MS TextBox" />
<Button Content="Button" Click="Button_Click_1"/>
</StackPanel>
<StackPanel Grid.Column="1">
<cc:CustomControl1 Value="{Binding MyValue}" Height="40" Width="200" Background="Red"/>
<TextBox Height="30" Text="{Binding MyValue}" />
</StackPanel>
</Grid>
c#
public partial class MainWindow : Window, INotifyPropertyChanged
{
private dynamic myValue;
public dynamic MyValue
{
get { return myValue; }
set { myValue = value; RaisePropertyChanged("MyValue"); }
}
public MainWindow()
{
InitializeComponent();
this.myValue = 3;
this.DataContext = this;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
this.myValue = 5;
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
the coerce's base value is always null,but if i use converter in binding it works as expected.
but i want to use like normal property , Please help me on this.
binding works perfectly if i use TextBox,it is not the issue ,, i'm facing issue while i use DP's in CustomControl
Regards,
Kumar
Found answer from here..............
http://social.msdn.microsoft.com/Forums/vstudio/en-US/7d0f0a90-b0b8-45c9-9ec9-76517c8ea4af/dynamic-property-not-working-well-with-dependencyproperties?forum=wpf