How to get x:Name value runtime - wpf

I got:
<ListView.View GridViewColumnHeader.Click="ColumnHeaderClick">
<GridView>
<GridViewColumn x:Name="Col" Header="Item">
private void ColumnHeaderClick(object sender, RoutedEventArgs e)
{
GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;
}
Now, how to get the x:Name value of GridViewColumn in the method? I can't put 'Name' property for the column in xaml, and it comes like empty string in runtime. Although I am able to get the header, still need to get x:Name value.

When you use the x:Name syntax on an element that isn't a FrameworkElement or FrameworkContentElement, it registers the element with a NameScope. Unfortunately, you can't retrieve the name for an element, the lookup only works the other way around.
If you need to pass additional information about the DataGridColumn, a custom attached property would be an easy way of doing it.
More information about x:Name can be found on MSDN. Also, the NameScope documentation describes its behavior.

You can leverage reflection to grab the value with:
GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;
String xName = (String)headerClicked.getType().GetProperty("Name").GetValue(headerClicked, null);

Related

Binding formatted text to templated TextBlock

I have an external control which displays a layout formed from a label and an input control. My label requires special formatting (subscript) but it currently only supports direct text.
So my approach is to create a custom TextBlock implementation, which exposes a new InlineContent dependency property that, once set, converts the content and adds it to it's actual Inlines collection.
For the layout control I add a custom DataTemplate which binds the label content to the InlineContent property of my custom text block.
ExtendedTextBlock.cs:
private static void InlinesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is ExtendedTextBlock b)) return;
b.Inlines.Clear();
if (e.NewValue is InlineCollection collection)
b.Inlines.AddRange(collection);
if (e.NewValue is Span span)
b.Inlines.AddRange(span.Inlines);
if (e.NewValue is Run run)
b.Inlines.Add(run);
if (e.NewValue is string str)
b.Inlines.Add(new Run(str));
}
DataTemplate:
<DataTemplate>
<controls:ExtendedTextBlock InlineContent="{Binding}" />
</DataTemplate>
Label:
<dxlc:LayoutItem.Label>
<Span>
<Run>Right (R</Run>
<Run Typography.Variants="Subscript">R</Run>
<Run>)</Run>
</Span>
</dxlc:LayoutItem.Label>
This works fine for regular text (strings) but when I set a Span as my label's content, then I get the following exception:
System.Windows.Markup.XamlParseException: 'Collection was modified; enumeration operation may not execute.'
Inner Exception:
InvalidOperationException: Collection was modified; enumeration operation may not execute.
This occurs in line b.Inlines.AddRange(span.Inlines). Why so? I don't understand which collection changes.
Binding directly to Text does not work. Then I only see 'System.Documents.Text.Span` but not the span actually being rendered.
No idea why that happens, but copying Span.Inlines to a new collection solves the problem:
using System.Linq;
...
b.Inlines.AddRange(span.Inlines.ToList());

How to find clicked UIElement child

I would like to get the clicked UIElement's child element that is button. Maybe there is simple and short solution for this? I have searched for this answer awhile, but could't find solution that would be easy to understand and use. I will appreciate any kind of help related to this question.
Code that i have right now:
private new void MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender == (sender as UIElement))
{
//TODO: getting controls name that is placed inside clicked UIElement
}
}
Edit:
Wanted to mention that UIElement is ContentControl that is using ResourceDictionary template.
My xaml code looks something like this
<ContentControl Style="{StaticResource DesignerItemStyle}">
<Button x:Name="btnAdd" Content="add function" IsHitTestVisible="True"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
</ContentControl>
There are two properties in MouseButtonEventArgs which you can leverage for this purpose
Source
This property contains reference to the object that raised the event. example Button etc
OriginalSource
the original reporting source as determined by pure hit testing, before any possible Source adjustment by a parent class, which may have been done to flatten composited element trees. example a Rectangle, Border or any template element inside the Button.
you can retrieve the Name for the element by casting OriginalSource to FrameworkElement or more appropriate
if the OriginalSource is not the desired element then you can retrieve the Logical parent of OriginalSource and that is more likely to be the desired element and retrieving Name remain same as above.
retrieve logical parent example
LogicalTreeHelper.GetParent(e.OriginalSource as DependencyObject);

Object reference not set to an instance of an object when creating two ComboboxItems functions in wpf

I´m all out of ideas here
The thing is that Im using two comboboxes and I want to get values from both comboboxes to show content in DataGrid in wpf.
I have this function that gets values from both comboboxes. This works well.
private void cboxYearChange(object sender, SelectionChangedEventArgs e)
{
ComboBoxItem typeItemYear = (ComboBoxItem)comboBox2.SelectedItem;
string valueYear = typeItemYear.Content.ToString();
ComboBoxItem typeItemMonth = (ComboBoxItem)comboBox1.SelectedItem;
string valueMonth = typeItemMonth.Content.ToString();
}
But then I want to create another function to check for changes on the other combobox:
private void cboxMonthChange(object sender, SelectionChangedEventArgs e)
{
ComboBoxItem typeItemYear = (ComboBoxItem)comboBox2.SelectedItem;
string valueYear = typeItemYear.Content.ToString();
ComboBoxItem typeItemMonth = (ComboBoxItem)comboBox1.SelectedItem;
string valueMonth = typeItemMonth.Content.ToString();
}
I can build, but when I run this I get the Object reference not set to an instance of an object error on the ComboBoxItem typeItemYear = (ComboBoxItem)comboBox2.SelectedItem; line in the cboxMonthChange function
What am I missing here ?
SelectedItem is null until something is selected. Unless they both change at the same time (which is not possible as these events are fired in sequence), either the type cast on comboBox1.SelectedItem or comboBox2.SelectedItem will throw an exception.
Check if SelectedItem is set the methods.
Or use another cast, like:
ComboBoxItem item1 = comboBox1.SelectedItem as ComboBoxItem;
if (item1 != null)
{
// do something
}
Hope this helps :-)
1) you should not refer to control's name within the code whenever possible.
So you can know, for instance, which ComboBox was changed within a SelectionChanged
handler by casting the Sender to a ComboBox.
2) but in such a simple case, just use public properties and bind them to
your ComboBox : all will get done with no code.
<ComboBox x:Name="YearSelectCB" SelectedItem="{Binding SelectedYear}">
<ComboBox x:Name="MonthSelectCB" SelectedItem="{Binding SelectedMonth}">
(you can set the DataContext of the window in several ways, for instance in the
window loaded event handler (DataContext=this) )

Silverlight DataTrigger not firing on load

I'm attempting to convert some of my WPF skills to Silverlight, and have run into a slightly odd problem in the test mini-app I've been working on. In WPF, I got used to using DataTriggers within a style to set up control properties based on properties of the bound data. I discovered that some assemblies related to Blend allow you to do something like this in Silverlight, and I came up with something like this, in which I've got the following namespaces declared:
xmlns:ia="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
xmlns:iv="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<DataTemplate x:Key="testItemTemplate">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Name, Mode=TwoWay}" x:Name="thing"/>
<iv:Interaction.Triggers>
<ia:DataTrigger Binding="{Binding Name}" Value="ReddenMe" Comparison="Equal">
<ia:ChangePropertyAction TargetName="thing" PropertyName="Foreground" Value="Red">
</ia:ChangePropertyAction>
</ia:DataTrigger>
</iv:Interaction.Triggers>
</StackPanel>
</DataTemplate>
In this example, I've got a data object implementing INotifyPropertyChanged and raising the PropertyChanged event as usual for the Name property. I get the expected behaviour if I change the value of the textbox and lose focus, but if the initial value of the textbox is set to ReddenMe (which for this contrived example I'm using as the trigger for the text to be red), the text doesn't go red. Does anybody know what's going on here? For DataTriggers in WPF, the trigger would be fired immediately for any data.
I realise that I could use a Converter here, but I can think of situations where I'd want to use triggers, and I wonder if there's anything I could do to make this work.
Here's a solution I found on Tom Peplow's blog: inherit from DataTrigger, and make the trigger evaluate the condition when its associated element is loaded.
Here's how you can code it:
public class DataTriggerEvaluateOnLoad : Microsoft.Expression.Interactivity.Core.DataTrigger
{
protected override void OnAttached()
{
base.OnAttached();
var element = AssociatedObject as FrameworkElement;
if (element != null)
{
element.Loaded += OnElementLoaded;
}
}
protected override void OnDetaching()
{
base.OnDetaching();
var element = AssociatedObject as FrameworkElement;
if (element != null)
{
element.Loaded -= OnElementLoaded;
}
}
private void OnElementLoaded(object sender, RoutedEventArgs e)
{
EvaluateBindingChange(null);
}
}

DataGridColumn Binding in code

Does anyone know how I can do the equivalent XAML binding in code?
<DataGrid ... >
<DataGrid.Columns>
<DataGridTextColumn
Binding="{Binding Description}" <=== set in code **
/>
</DataGrid.Columns>
</DataGrid>
Cheers,
Berryl
=== UPDATE ====
It looks like the method I have been looking for is DataGridColumn.GenerateElement
If so, then the focus of this question is now how to set the Binding correctly. The reason I want to do this code is that my grid has 7 columns that are identical visually and whose data can be known by an index.
So I want to be able to simplify the xaml by using a subclass DataGridTextColumn which has an index property, and just have:
<DataGrid ... >
<DataGrid.Columns >
<local:DayOfWeekColumn Index="0" />
<local:DayOfWeekColumn Index="1" />
....
<local:DayOfWeekColumn Index="7" />
</DataGrid.Columns >
</DataGrid >
=== REVISED QUESTION ===
Assuming the Binding itself is logically and syntactically correct, what should the parameters to BindingOperations.SetBinding be??
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem) {
var activity = (ActivityViewModel)dataItem;
var cellData = activity.Allocations[Index];
var b = new Binding
{
Source = cellData,
UpdateSourceTrigger = UpdateSourceTrigger.LostFocus,
Converter = new AllocationAmountConverter()
};
BindingOperations.SetBinding(??, ??, b);
return ??;
}
=== EDITS for ARAN =====
I am not overriding GenerateElement right now, but rather trying to get a static helper to set my binding for me. The helper is needed in any event to compensate for not being able to bind Header content in the current implementation of MSFT's DataGrid.
Basically the idea is to catch the DC from the grid and use it as necessary on each of the columns, which in this case would be the Header content, cell style, and Binding. Here is the code:
public class TimesheetDataGridColumnContextHelper
{
static TimesheetDataGridColumnContextHelper() {
FrameworkElement.DataContextProperty.AddOwner(typeof (DataGridTextColumn));
FrameworkElement.DataContextProperty.OverrideMetadata(
typeof (DataGrid),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, OnDataContextChanged));
}
public static void OnDataContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as DataGrid;
if (grid == null || !grid.Name.Equals("adminActivityGrid")) return;
foreach (var col in grid.Columns) {
var dowCol = col as DayOfTheWeekColumn;
if (dowCol == null) continue;
var context = (IActivityCollectionViewModelBase) e.NewValue;
var index = Convert.ToInt32(dowCol.DowIndex);
_setHeader(dowCol, context, index);
var editStyle = (Style) grid.FindResource("GridCellDataEntryStyle");
dowCol.CellStyle = editStyle;
_setBinding(dowCol, index, context);
}
}
private static void _setBinding(DayOfTheWeekColumn dowCol, int index, IActivityCollectionViewModelBase context) {
dowCol.Binding = new Binding
{
Path = new PropertyPath(string.Format("Allocations[{0}]", index)),
UpdateSourceTrigger = UpdateSourceTrigger.LostFocus,
Converter = new AllocationAmountConverter()
};
}
private static void _setHeader(DataGridColumn col, IActivityCollectionViewModelBase context, int index)
{
var date = context.HeaderDates[index];
var tb = new TextBlock
{
Text = date.ToString(Strings.ShortDayOfWeekFormat),
ToolTip = date.ToLongDateString()
};
col.Header = tb;
}
}
}
Everything works except for the Binding. I can't tell if it's because my binding is wrong somehow (although I get no obvious errors) or this is not a good place to set it. The grid columns are just empty when I run it.
Any idea??
Cheers,
Berryl
=== FIXED! ===
The logic in the last update was actually correct, but getting lost in the internals of the DataGrid I missed that my Binding.Path was missing the property to be bound to! Credit to Aran for understanding the issue, realizing that GenerateElement overrides were not necessary, and catching that the Binding Source should not have been set.
You're always doing the fiddly grid bits eh Beryl?
Do a couple of things. Use reflector to look at the implementation of GenerateElement in the DataGridTextColumn. (.NET programmers live in reflector)
Now for the answer:
In the datagrid each column is not part of the visual tree. The column has two methods GenerateElement and GenerateEditingElement. These methods return the viewer and the editor for the cell respectively. In your method above you are not creating the viewer, which will probably be a TextBlock.
from reflector, the implementation of GenerateElement is as below, notice the first thing they do is create the viewer for the cell.
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
TextBlock e = new TextBlock();
this.SyncProperties(e);
base.ApplyStyle(false, false, e);
base.ApplyBinding(e, TextBlock.TextProperty);
return e;
}
Once you have a textblock you can use the line below to set the binding on it.
BindingOperations.SetBinding(textBlock, TextBlock.TextProperty, binding);
I am not however convinced that you actually need to override the GenerateElement and GenerateEditingElement to get your desired effect. I think you could overide the Binding property of the base class and just modify the binding there with your extra field whenever it is set. This will mean everything else will just work and you wont end up removing functionality from your column. Once again a crawl through reflector looking at the class DataGridBoundColumn (the abstract base class) would be beneficial.
I do something similiar in one of our columns whenever a binding is set I modify the clipboard binding by adding an extra property so I can copy and paste effectively.
EDIT: Update...this should probably be another question but..
You are explicitly setting the source of the binding in your setBinding method. In the grid the source of the binding is the data contained in the row. You are setting it, which means it would be the same for each row. You can apply these funky bindings without the source property before the data context is set, the source becomes the item in each row, and your binding should reflect an index into the property held in each row.
Based on MSDN, it sounds like the first parameter of SetBinding() should be the control that you want to display the binding in (this in this case, assuming that GenerateElement() is a member of the DayOfWeekColumn class), and the second property is the property to bind the data to. I haven't used the WPF DataGrid very much, but I didn't see anything like a text property to set.
I do see that the DataGridTextColumn does have a Binding property, though. Maybe it would work to set it to the binding you created manually above?

Resources