Bind dependency Property to Child Element in Code behind - wpf

I basically got a ContentControl that has Border as content witch also has TextBlock as content. What i want is that e.g. the Foreground Brush of the TextBlock is bound to a dependency property of the parent ContentControl... I am stuck right here i don't know how to solve this.
public class NumberRollItem : ContentControl
{
public int Index { get; set; }
public int AnimationIndex { get; set; }
public Brush ItemForeground
{
get { return (Brush)GetValue(ItemForegroundProperty); }
set { SetValue(ItemForegroundProperty, value); }
}
public static readonly DependencyProperty ItemForegroundProperty =
DependencyProperty.Register("ItemForeground", typeof(Brush), typeof(NumberRollItem), new PropertyMetadata(new SolidColorBrush(Colors.White)));
public Brush ItemBackground
{
get { return (Brush)GetValue(ItemBackgroundProperty); }
set { SetValue(ItemBackgroundProperty, value); }
}
public static readonly DependencyProperty ItemBackgroundProperty =
DependencyProperty.Register("ItemBackground", typeof(Brush), typeof(NumberRollItem), new PropertyMetadata(new SolidColorBrush(Colors.Black)));
public double ItemFontSize
{
get { return (double)GetValue(ItemFontSizeProperty); }
set { SetValue(ItemFontSizeProperty, value); }
}
public static readonly DependencyProperty ItemFontSizeProperty =
DependencyProperty.Register("ItemFontSize", typeof(double), typeof(NumberRollItem), new PropertyMetadata(45d));
public NumberRollItem(char c, int index)
{
this.Index = index;
string text = ""; text += c;
HorizontalContentAlignment = System.Windows.HorizontalAlignment.Stretch;
Content = new Border()
{
Background = ItemBackground, // Background bound to ItemBackground but how??
HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
VerticalAlignment = System.Windows.VerticalAlignment.Stretch,
Child = new TextBlock()
{
FontSize = 45,
Text = text,
Foreground = ItemForeground,
},
};
}
}

Updated for WP
Well if you're doing everything via code, you can create the Binding in code too,
Something like:
public NumberRollItem(char c, int index)
{
this.Index = index;
string text = ""; text += c;
// Giving This control a Name to later use for Binding
string thisControlName = "NumberItem";
Name = thisControlName;
HorizontalContentAlignment = System.Windows.HorizontalAlignment.Stretch;
var tBlock = new TextBlock()
{
FontSize = 45,
Text = text
};
var binding = new Binding
{
ElementName = thisControlName,
Path = new PropertyPath("ItemForeground")
};
tBlock.SetBinding(TextBlock.ForegroundProperty, binding);
Content = new Border()
{
Background = ItemBackground,
HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
VerticalAlignment = System.Windows.VerticalAlignment.Stretch,
Child = tBlock,
};
}
What you're doing with this code is:
In the constructor of NumberRollItem you create a Binding with ElementName pointing to NumberRollItem class and assign the Binding to the TextBlock.ForegroundProperty. This will thus have them bound to allow for any future changes to ItemForeground to affect the TextBlock.Foreground as well.
Not sure why you've taken this approach and not the xaml Style route but whatever works for ya :)

Related

MVVM - How change backcolor for a single char in a datagrid

I would the rows containing the search value (Search Name), show this value (in datagrid) with a different color.
See the pic below.
Some ideas about this ?
You can do this by creating a new control that extends a standard TextBlock, which uses a series of Run items to display the text, using the appropriate formatting.
public class HighlightTextBlock: TextBlock
{
public string BaseText
{
get { return (string)GetValue(BaseTextProperty); }
set { SetValue(BaseTextProperty, value); }
}
public static readonly DependencyProperty BaseTextProperty =
DependencyProperty.Register("BaseText", typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(null, UpdateDisplay));
public string HighlightText
{
get { return (string)GetValue(HighlightTextProperty); }
set { SetValue(HighlightTextProperty, value); }
}
public static readonly DependencyProperty HighlightTextProperty =
DependencyProperty.Register("HighlightText", typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(null, UpdateDisplay));
public Brush HighlightBrush
{
get { return (Brush)GetValue(HighlightBrushProperty); }
set { SetValue(HighlightBrushProperty, value); }
}
public static readonly DependencyProperty HighlightBrushProperty =
DependencyProperty.Register("HighlightBrush", typeof(Brush), typeof(HighlightTextBlock), new PropertyMetadata(Brushes.Orange, UpdateDisplay));
public bool HighlightCaseSensitive
{
get { return (bool)GetValue(HighlightCaseSensitiveProperty); }
set { SetValue(HighlightCaseSensitiveProperty, value); }
}
public static readonly DependencyProperty HighlightCaseSensitiveProperty =
DependencyProperty.Register("HighlightCaseSensitive", typeof(bool), typeof(HighlightTextBlock), new PropertyMetadata(false, UpdateDisplay));
private static void UpdateDisplay(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var hightlightTextBlock = sender as HighlightTextBlock;
if (hightlightTextBlock == null)
return;
hightlightTextBlock.Inlines.Clear();
if (string.IsNullOrEmpty(hightlightTextBlock.BaseText))
return;
if (string.IsNullOrEmpty(hightlightTextBlock.HighlightText))
{
hightlightTextBlock.Inlines.Add(new Run(hightlightTextBlock.BaseText));
return;
}
var textItems = Regex.Split(hightlightTextBlock.BaseText,
"(" + hightlightTextBlock.HighlightText + ")",
hightlightTextBlock.HighlightCaseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase);
foreach (var item in textItems)
{
var run = new Run(item);
var highlight = hightlightTextBlock.HighlightCaseSensitive
? string.Compare(item, hightlightTextBlock.HighlightText, StringComparison.InvariantCulture) == 0
: string.Compare(item, hightlightTextBlock.HighlightText, StringComparison.InvariantCultureIgnoreCase) == 0;
if (highlight)
run.Background = hightlightTextBlock.HighlightBrush;
hightlightTextBlock.Inlines.Add(run);
}
}
}
The brackets around the HighlightText value tells Regex.Split to include the matched text in the returned list of items.
This control can then be used as part of an item template in your datagrid column definition. See here for an example of how to do that.

How to draw a line between two WPF controls using dependency properties?

I need to draw a line connecting two WPF controls. I have defined a dependency property in my Node objects so if the Node is moved the line still connect the objects.
I have the following example but I'm unable to get it working.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Node node1 = new Node(myCanvas) { Width = 50, Height = 50 };
Node node2 = new Node(myCanvas) { Width = 50, Height = 50 };
Canvas.SetLeft(node1, 0);
Canvas.SetLeft(node2, 200);
Canvas.SetTop(node1, 0);
Canvas.SetTop(node2, 0);
myCanvas.Children.Add(node1);
myCanvas.Children.Add(node2);
Connector conn = new Connector();
conn.Source = node1.AnchorPoint;
conn.Destination = node2.AnchorPoint;
myCanvas.Children.Add(conn);
}
}
class Node : Control
{
public static readonly DependencyProperty AnchorPointProperty =
DependencyProperty.Register(
"AnchorPoint", typeof(Point), typeof(Node),
new FrameworkPropertyMetadata(new Point(0, 0),
FrameworkPropertyMetadataOptions.AffectsMeasure));
public Point AnchorPoint
{
get { return (Point)GetValue(AnchorPointProperty); }
set { SetValue(AnchorPointProperty, value); }
}
private Canvas mCanvas;
public Node(Canvas canvas)
{
mCanvas = canvas;
this.LayoutUpdated += Node_LayoutUpdated;
}
void Node_LayoutUpdated(object sender, EventArgs e)
{
Size size = RenderSize;
Point ofs = new Point(size.Width / 2, size.Height / 2);
AnchorPoint = TransformToVisual(this.mCanvas).Transform(ofs);
}
protected override void OnRender(DrawingContext drawingContext)
{
drawingContext.DrawEllipse(
Brushes.Red,
null,
new Point(Width / 2, Height / 2), Width / 2, Height / 2);
}
}
public sealed class Connector : UserControl
{
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register(
"Source", typeof(Point), typeof(Connector),
new FrameworkPropertyMetadata(default(Point)));
public Point Source {
get { return (Point)this.GetValue(SourceProperty); }
set { this.SetValue(SourceProperty, value); }
}
public static readonly DependencyProperty DestinationProperty =
DependencyProperty.Register(
"Destination", typeof(Point), typeof(Connector),
new FrameworkPropertyMetadata(default(Point)));
public Point Destination {
get { return (Point)this.GetValue(DestinationProperty); }
set { this.SetValue(DestinationProperty, value); }
}
public Connector()
{
LineSegment segment = new LineSegment(default(Point), true);
PathFigure figure = new PathFigure(default(Point), new[] { segment }, false);
PathGeometry geometry = new PathGeometry(new[] { figure });
BindingBase sourceBinding =
new Binding { Source = this, Path = new PropertyPath(SourceProperty) };
BindingBase destinationBinding =
new Binding { Source = this, Path = new PropertyPath(DestinationProperty) };
BindingOperations.SetBinding(
figure, PathFigure.StartPointProperty, sourceBinding);
BindingOperations.SetBinding(
segment, LineSegment.PointProperty, destinationBinding);
Content = new Path
{
Data = geometry,
StrokeThickness = 5,
Stroke = Brushes.White,
MinWidth = 1,
MinHeight = 1
};
}
}
All you have to do to make your example work is to bind conn.Source and .Destination to the nodes' AnchorPoints, or else the Connector just get the AnchorPoints' initial values (0,0), and doesn't listen for further changes:
...
Connector conn = new Connector();
//conn.Source = node1.AnchorPoint;
conn.SetBinding(Connector.SourceProperty,
new Binding()
{
Source = node1,
Path = new PropertyPath(Node.AnchorPointProperty)
});
//conn.Destination = node2.AnchorPoint;
conn.SetBinding(Connector.DestinationProperty,
new Binding()
{
Source = node2,
Path = new PropertyPath(Node.AnchorPointProperty)
});
myCanvas.Children.Add(conn);

ItemsControl children return NAN when asking for Canvas.GetLeft

I have a very simple WPF application that renders simple shapes in a canvas:
The blue squares are ItemsControl and the red circles are Controls
The following step in my application is adding connection lines between the shapes. The shaphes will be moved and I want the connections to be automatically moved. I readed about how to do it adding connection bindings.
All worked fine with canvas direct children (container), but if I want to connect the nodes, it does not work. It seems that if I don't call Canvas.SetLeft() and Canvas.SetTop() explicitily, then Canvas.GetLeft() and Canvas.GetTop() return NAN.
How should I proceed?
Should I implement a mechanism to get all objects placed in my canvas, so I always can calculate Canvas.GetLeft() over all of them?
Should I proceed in another way?
Source code and screenshot
This is the source code of the example. You can find here the complete example:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Container container1 = new Container() { Width = 100, Height = 100 };
Node node1 = new Node() { Width = 50, Height = 50 };
container1.Items.Add(node1);
Container container2 = new Container() { Width = 100, Height = 100 };
Node node2 = new Node() { Width = 50, Height = 50 };
container2.Items.Add(node2);
Canvas.SetLeft(container2, 200);
myCanvas.Children.Add(container1);
myCanvas.Children.Add(container2);
}
}
class Container : ItemsControl
{
protected override void OnRender(DrawingContext drawingContext)
{
drawingContext.DrawRectangle(
Brushes.Blue, null, new Rect(0, 0, this.Width, this.Height));
}
}
class Node : Control
{
protected override void OnRender(DrawingContext drawingContext)
{
drawingContext.DrawEllipse(
Brushes.Red, null,
new Point(Width / 2, Height / 2), Width / 2, Height / 2);
}
}
This is how I implemented the connections between the shapes:
public Shape AddConnection(UIElement source, UIElement target)
{
Connector conn = new Connector();
conn.SetBinding(Connector.StartPointProperty,
CreateConnectorBinding(source));
conn.SetBinding(Connector.EndPointProperty,
CreateConnectorBinding(target));
return conn;
}
private MultiBinding CreateConnectorBinding(UIElement connectable)
{
// Create a multibinding collection and assign an appropriate converter to it
MultiBinding multiBinding = new MultiBinding();
multiBinding.Converter = new ConnectorBindingConverter();
// Create binging #1 to IConnectable to handle Left
Binding binding = new Binding();
binding.Source = connectable;
binding.Path = new PropertyPath(Canvas.LeftProperty);
multiBinding.Bindings.Add(binding);
// Create binging #2 to IConnectable to handle Top
binding = new Binding();
binding.Source = connectable;
binding.Path = new PropertyPath(Canvas.TopProperty);
multiBinding.Bindings.Add(binding);
// Create binging #3 to IConnectable to handle ActualWidth
binding = new Binding();
binding.Source = connectable;
binding.Path = new PropertyPath(FrameworkElement.ActualWidthProperty);
multiBinding.Bindings.Add(binding);
// Create binging #4 to IConnectable to handle ActualHeight
binding = new Binding();
binding.Source = connectable;
binding.Path = new PropertyPath(FrameworkElement.ActualHeightProperty);
multiBinding.Bindings.Add(binding);
return multiBinding;
}
The Connector object is very simple. It has a LineGeometry and exposes two DependencyProperties to calculate the start point and the end point.
public static readonly DependencyProperty StartPointProperty =
DependencyProperty.Register(
"StartPoint",
typeof(Point),
typeof(Connector),
new FrameworkPropertyMetadata(
new Point(0, 0),
FrameworkPropertyMetadataOptions.AffectsMeasure));
public static readonly DependencyProperty EndPointProperty =
DependencyProperty.Register(
"EndPoint",
typeof(Point),
typeof(Connector),
new FrameworkPropertyMetadata(
new Point(0, 0),
FrameworkPropertyMetadataOptions.AffectsMeasure));
Everything is so wrong I can't really answer the question without fixing things.
Your nodes and containers shouldn't be controls that use OnRender. There's a lot of expectations in WPF, and one expectation is that you use their controls. If you dig into Microsoft code, they have a lot of things hard-coded for their classes.
You should have data objects for Node and Container that have Connections. Container should have a list of children Nodes.
You'll use a DataTemplate or Style to actually implement the UI. That's where you do your bindings, but don't use a multibinding. Just bind to individual values themselves. If you need to evaluate, then you create ViewModel objects that perform these calculations for you. You don't do your construction code in converters.
Because you're using bindings to connect things and your "connectable" doesn't describe whether it's a node or container or both, I'm going to assume it can be both.
For example:
public interface IConnection
{
IConnectable A { get; set; }
IConnectable B { get; set; }
}
public class Connection : IConnection, Line
{
DependencyProperty AProperty = ...;
DependencyProperty BProperty = ...;
}
public class Node : IConnectable
{
DependencyProperty ConnectionProperty = ...;
}
public class Container : IConnectable
{
DependencyProperty ConnectionProperty = ...;
ObservableCollection<IConnectable> Children = ...;
}
public class ContainerView : IConnectable
{
DependencyProperty ConnectionPointProperty = ...;
DependencyProperty ConnectionProperty = ...;
void OnSizeChanged(...)
{
RecalcConnectionPoint();
}
void OnConnectionPointOtherChanged()
{
RecalcConnectionPoint();
}
void RecalcConnectionPoint()
{
if (Connection.A == this)
{
if (Connection.B.ConnectionPoint.Left < this.Left)
{
ConnectionPoint = new Point(Left, Top + Height/2);
}
else
{
ConnectionPoint = new Point(Right, Top + Height/2);
}
}
}
}
Then you would bind the properties that match up from your Model classes to your ViewModel classes. Then manipulating the data in your Model classes would update your View.
Your Styles for your Container and Nodes would decide how to draw them, so say one day you decide a Node should look like a Rectangle instead... You change a style and don't have to dig through OnRender code.
This is how you design WPF programs.
Other benefits.
If you were to put a "Connection UI Object" somewhere on the Container, you'd bind to it's point instead. You could use a Grid to align the ConnectionPointView, and then the ConnectionPoint would be updated automatically.

Silverlight: Default value in Combobox

I would like to display a default text in the combobox. For example, "Choose a person" message. could you please help me out.
Please note that I am using databinding from domaincontext
Thank you !!
To achieve this, I used a derived ExtendedComboBox class which extends the built-in ComboBox class. You can find the source code of this class in my blog post or below.
After you add this class to your project, you can use this XAML code to display a default value:
<local:ExtendedComboBox ItemsSource="{Binding ...Whatever...}" NotSelectedText="Select item..." />
Also, here is the test page with this control. I think that the second combobox is that what you need.
Full code of this class:
[TemplateVisualState(Name = ExtendedComboBox.StateNormal, GroupName = ExtendedComboBox.GroupItemsSource)]
[TemplateVisualState(Name = ExtendedComboBox.StateNotSelected, GroupName = ExtendedComboBox.GroupItemsSource)]
[TemplateVisualState(Name = ExtendedComboBox.StateEmpty, GroupName = ExtendedComboBox.GroupItemsSource)]
public class ExtendedComboBox : ComboBox
{
public const string GroupItemsSource = "ItemsSourceStates";
public const string StateNormal = "Normal";
public const string StateNotSelected = "NotSelected";
public const string StateEmpty = "Empty";
private ContentPresenter selectedContent;
public ExtendedComboBox()
{
this.DefaultStyleKey = typeof(ComboBox);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.selectedContent = this.GetTemplateChild("ContentPresenter") as ContentPresenter;
// This event can change the NotSelected state
this.SelectionChanged += (s, e) => this.SetTextIfEmpty();
// Set a state at start
this.SetTextIfEmpty();
}
// This method can change the Empty state
protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
this.SetTextIfEmpty();
}
/// <summary>
/// Text if the SelectedItem property is null.
/// </summary>
public string NotSelectedText
{
get { return (string)GetValue(NotSelectedTextProperty); }
set { SetValue(NotSelectedTextProperty, value); }
}
public static readonly DependencyProperty NotSelectedTextProperty =
DependencyProperty.Register("NotSelectedText", typeof(string), typeof(ExtendedComboBox), new PropertyMetadata(" "));
/// <summary>
/// Text if there are no items in the ComboBox at all.
/// </summary>
public string EmptyText
{
get { return (string)GetValue(EmptyTextProperty); }
set { SetValue(EmptyTextProperty, value); }
}
public static readonly DependencyProperty EmptyTextProperty =
DependencyProperty.Register("EmptyText", typeof(string), typeof(ExtendedComboBox), new PropertyMetadata(null));
/// <summary>
/// Changes the state of this control and updates the displayed text.
/// </summary>
protected void SetTextIfEmpty()
{
if (this.selectedContent == null || !(this.selectedContent.Content is TextBlock))
return;
var text = this.selectedContent.Content as TextBlock;
if (this.SelectedItem == null && this.Items != null && this.Items.Count > 0)
{
text.Text = this.NotSelectedText;
VisualStateManager.GoToState(this, ExtendedComboBox.StateNotSelected, true);
}
else if (this.SelectedItem == null)
{
text.Text = this.EmptyText ?? this.NotSelectedText;
VisualStateManager.GoToState(this, ExtendedComboBox.StateEmpty, true);
}
else VisualStateManager.GoToState(this, ExtendedComboBox.StateNormal, true);
}
}
Just do this:
theComboBox.SelectedItem = yourDataItem;
alternatively, set the selected index:
theComboBox.SelectedIndex = 0;
Edit
If ItemSource is bound, you want to override the combo's DataContextChanged and then use one of the above lines to set the index/selected item.
However, if you don't want the default text to be selectable, you will have to do something along the lines of this.
You can use set value method to insert a value at a particular position.
It may be not best option but it works for me.
var array= APPTasks.ListPhysician.OrderBy(e => e.phyFName).ThenBy(e => e.phyLName).ToArray();
array.SetValue((new Physician { phyFName = "Select Attening Md", phyLName = "" }), 0);
just bring your data in a variable and use SetValue method.

Using XAML and Silverlight 4, is there a way to build up a dynamic GRID rows and columns from databinding?

I'm trying to have a regular grid rows and columns defined dynamically using databinding in XAML only.
I know I can use code behind for this but I'm looking for a way to do it purely in XAML.
Any thoughts?
Ok, after much reading around the net I coded the following solution:
public class DynamicGrid : Grid
{
public static readonly DependencyProperty NumColumnsProperty =
DependencyProperty.Register ("NumColumns", typeof (int), typeof (DynamicGrid),
new PropertyMetadata ((o, args) => ((DynamicGrid)o).RecreateGridCells()));
public int NumColumns
{
get { return (int)GetValue(NumColumnsProperty); }
set { SetValue (NumColumnsProperty, value); }
}
public static readonly DependencyProperty NumRowsProperty =
DependencyProperty.Register("NumRows", typeof(int), typeof(DynamicGrid),
new PropertyMetadata((o, args) => ((DynamicGrid)o).RecreateGridCells()));
public int NumRows
{
get { return (int)GetValue(NumRowsProperty); }
set { SetValue (NumRowsProperty, value); }
}
private void RecreateGridCells()
{
int numRows = NumRows;
int currentNumRows = RowDefinitions.Count;
while (numRows > currentNumRows)
{
RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
currentNumRows++;
}
while (numRows < currentNumRows)
{
currentNumRows--;
RowDefinitions.RemoveAt(currentNumRows);
}
int numCols = NumColumns;
int currentNumCols = ColumnDefinitions.Count;
while (numCols > currentNumCols)
{
ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
currentNumCols++;
}
while (numCols < currentNumCols)
{
currentNumCols--;
ColumnDefinitions.RemoveAt(currentNumCols);
}
UpdateLayout();
}
}
It works but I'm not sure it is the optimal solution. Any comments on this one?

Resources