DependencyPropertyDescriptor help required - c#

I have a simple WPF app, where I have a combobox and a label, using dependency property I want to show the selected Item in label, when user select any item in the combobox, label will be changed accordingly.
Here is my code.
public event EventHandler _itemChanged;
public MainWindow()
{
List<String> items = new List<string>();
items.Add("C");
items.Add("C++");
items.Add("C#");
items.Add("Java");
items.Add("Js");
InitializeComponent();
combx.ItemsSource = items;
_itemChanged += MainWindow__itemChanged;
DependencyPropertyDescriptor dpcombx;
dpcombx =
DependencyPropertyDescriptor.FromProperty((DependencyProperty)
ComboBox.SelectedValueProperty, typeof(ComboBox));
dpcombx.AddValueChanged(dpcombx, _itemChanged);
}
void MainWindow__itemChanged(object sender, EventArgs e)
{
ComboBox cb = (ComboBox) sender;
lbl_Combx.Content = (string)cb.SelectedItem;
}
The problem is, EventHandler is not getting called. Please help me.
here is the XAML
<Window x:Class="DP.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ComboBox Name="combx"
HorizontalAlignment="Left"
Margin="57,121,0,0"
VerticalAlignment="Top"
Width="120" />
<Label Content=""
x:Name="lbl_Combx"
HorizontalAlignment="Left"
Margin="368,182,0,0"
VerticalAlignment="Top" />
</Grid>
</Window>

You need to pass the dependency object (comboBox) in the AddValueChanged instead of its property descriptor.
dpcombx.AddValueChanged(combx, _itemChanged);

Related

Listbox, not getting selected item

I have an WPF form with a listbox and a button on it.
I populate a listbox (tagbox) with the List and then bind this with datasource as below.
private void WeekFood_Load(object sender, EventArgs e)
{
List<string> tags = new List<string>() { "A", "B", "C"};
tagbox.DataSource = tags;
InitializeComponent();
}
Then when I click the button, I run the following code
private void GenSunday_Click(object sender, EventArgs e)
{
MessageBox.Show(tagbox.SelectedItems.Count.ToString());
}
No matter how many items are selected in the listbox it always returns 0 with tagbox.SelectedItems always being null.
I have tried populating the box by iterating the list with valuemember and displaymember however this also does not fix things.
How is this fixable.
Many thanks in advance
In WPF, please use ItemSource property for ListBox and bind the object to it.
Also call the InitializeComponent(); before assigning any data to the controls.
//MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
List<string> tags = new List<string>() { "A", "B", "C" };
tagBox.ItemsSource = tags;
}
private void button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(tagBox.SelectedItems.Count.ToString());
}
In MainWindow.xaml
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ListBox x:Name="tagBox" HorizontalAlignment="Left" Height="100" Margin="99,249,0,0" VerticalAlignment="Top" Width="561"/>
<Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="99,57,0,0" VerticalAlignment="Top" Width="148" Height="34" Click="button_Click"/>
</Grid>
</Window>

Set Content of an Element without a Name-Property

I'm using a ListBox in combination with a ObservableCollection. The content is set via a TemplateSelector (TextBlock or Label). The text has to be formatted (f.e. with Run-Tags in Code-behind), but i can't access the Items. Is there a solution to get the elements?
I've tried the usage of OfType<>, but this works only on Panels. I searched for an children-attribute but, there isn't one for ListBoxes. Setting the Name-Property via binding is not possible for UId and Name.
An IEnumerator for the LogicalChildren doesn't work and iterate over the whole content everytime a new element is added, is not so optimal. Here a minimal example.
<Window.Resources>
<DataTemplate x:Key="TextBlockTemplate">
<StackPanel>
<TextBlock />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="LabelTemplate">
<StackPanel>
<Label/>
</StackPanel>
</DataTemplate>
<local:myTemplateSelector x:Key="myTemplateSelector" x:Name="myTemplateSelector" TextBlockTemplate="{StaticResource TextBlockTemplate}" LabelTemplate="{StaticResource LabelTemplate}"/>
</Window.Resources>
<Grid Margin="0">
<ListBox Name="mylist" Grid.Row="3"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding _listEntries}"
ItemTemplateSelector="{StaticResource myTemplateSelector}"
>
</ListBox>
</Grid>
Greetings and thanks :)
The TextBlock has an Inlines property that returns the Inline elements that comprise the contents of the TextBlock.
The Label has a Content property that you, depending on how you are using it, may cast to a Panel.
There are no inline elements for a TextBox.
Now, I found a solution. I made the TextBlock and Label as User Control and set the Name-property. In the code-behind, I have access to the DataContext and the element can set itself.
<UserControl x:Class="Test.TextBlockControl"
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:local="clr-namespace:TextBlockControl"
Loaded="UserControl_Loaded">
<Grid>
<StackPanel HorizontalAlignment="Stretch" Margin="0,0,0,0">
<TextBlock Name="textBlock"/>
</StackPanel>
</Grid>
In the Code behind i can now access the values and set:
public partial class TextBlockControl : UserControl
{
public List<string> name => DataContext as List<string>;
public TextBlockControl()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
foreach (var t in name)
{
var run = new Run(t.Text);
if (t.IsHighlighted)
{
run.Foreground = Brushes.Green;
}
else
{
run.Foreground = Brushes.Red;
}
textBlock.Inlines.Add(run);
}
}
}
}
In the MainWindow, the dataTemplate then references the UserControl (root is the namespace):
<root:PickControl />

Why this simple WPF command does not work?

I'm trying to add a WPF custom command to a control. What I've done:
XAML:
<Window x:Class="H.I.S.windows.CommandTest"
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:H.I.S.windows"
mc:Ignorable="d"
Title="CommandTest" Height="450" Width="800">
<Window.CommandBindings>
<CommandBinding Command="local:CustomCommand.Save" CanExecute ="SaveCommand_CanExecute" Executed="SaveCommand_Executed" />
</Window.CommandBindings>
<Grid>
<Button Command="local:CustomCommand.Save" Height="50" Width="100">Click me!</Button>
</Grid>
</Window>
C#:
namespace H.I.S.windows
{
public partial class CommandTest : Window
{
public CommandTest()
{
InitializeComponent();
}
private void SaveCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void SaveCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("Saved");
}
}
public static class CustomCommand
{
public static readonly RoutedUICommand Save = new RoutedUICommand(
"Save",
"Save",
typeof(CustomCommand),
new InputGestureCollection()
{
new KeyGesture(Key.F2)
}
);
}
}
The button is disabled (even in design mode) and doesn't let the user click on it.
I've just implemented codes described HERE.
Where I'm wrong?
The code what you have posted throws me an error because of this below statement,
<Window.CommandBindings>
<CommandBinding Command="local:CustomCommand.Save" CanExecute ="CommandBinding_CanExecute" Executed="CommandBinding_Executed" />
</Window.CommandBindings>
It started working for me after changing it to below,
<Window.CommandBindings>
<CommandBinding Command="local:CustomCommand.Save" CanExecute ="SaveCommand_CanExecute" Executed="SaveCommand_Executed" />
</Window.CommandBindings>
The event handlers in your code-behind are different from what you have in your xaml for CommandBinding.
"SaveCommand_CanExecute" and "SaveCommand_Executed"
After changing as above, it works for me and I can see the messagebox with "Saved" message when I hit click on it.
Hope you are not missing this. If something else stopping you then try to see if it shows any errors on further and let us know.
As #SirRufo suggested in question comments, Problem was that I declared "CommandBindings" for whole WPF window and button inside another control.
There are 2 solutions for this case:
1: Declaring CommandBindings for Button's direct parent.
2: Set name for control which is bound with command and add some code like below to control:
<Window ... x:Name = "windowName" ...>
<Window.CommandBindings>
<CommandBinding Command="custumCommand:CustomCommands.Save" CanExecute ="CommandBinding_CanExecute" Executed="CommandBinding_Executed" />
</Window.CommandBindings>
<Grid>
<StackPanel>
<GroupBox>
<Button Command="custumCommand:CustomCommands.Save" CommandTarget = "{Binding ElementName=windowName}" Content="Save" />
</GroupBox>
</StackPanel>
</Grid>
</Window>
Look at "CommandTarget" attribute of Button.

SourceUpdated with NotifyOnSourceUpdated not fired in ListBox

I'm trying to use a SourceUpdated event in ListBox, but it doesn't fire.
I have binded ObservableCollection to ItemSource of ListBox, set NotifyOnSourceUpdated = true, and binding is working correctly - after adding new item to the collection, the ListBox displays new item, but without fireing the event.
MainWindow.xaml.cs:
public partial class MainWindow:Window
{
public ObservableCollection<string> Collection { get; set; }
public MainWindow()
{
InitializeComponent();
Collection = new ObservableCollection<string>();
var custBinding = new Binding()
{
Source = Collection,
NotifyOnSourceUpdated = true
};
intList.SourceUpdated += intList_SourceUpdated;
intList.SetBinding(ItemsControl.ItemsSourceProperty, custBinding);
}
private void intList_SourceUpdated(object sender, DataTransferEventArgs e)
{
MessageBox.Show("Updated!");
}
private void btnAddInt_Click(object sender, RoutedEventArgs e)
{
var randInt = new Random().Next();
Collection.Add(randInt.ToString());
}
}
MainWindow.xaml:
<Window x:Class="Test.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:Test"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Button x:Name="btnAddInt" Content="Button" HorizontalAlignment="Left" Margin="41,31,0,0" VerticalAlignment="Top" Width="75" Click="btnAddInt_Click"/>
<ListBox x:Name="intList" HorizontalAlignment="Left" Height="313" Margin="160,31,0,0" VerticalAlignment="Top" Width="600"/>
</Grid>
What am I missing, that it's not working ?
Thank you for an advice.
SourceUpdated is only fired on elements that can accept input and change the databound source value directly.
https://msdn.microsoft.com/en-us/library/system.windows.data.binding.sourceupdated(v=vs.110).aspx
In this case, the listbox itself is not updating the collection, rather the collection is being updated via a button click. This has nothing to do with the listbox firing the SourceUpdated Event.
Only input elements that can accept input like a textboxes, checkboxes, radio buttons and custom controls that use those controls will be able to two-way bind and transfer their values back to the source it is bound to.
You may be looking for CollectionChanged which will fire when items are added or removed from the Collection.
https://msdn.microsoft.com/en-us/library/ms653375(v=vs.110).aspx
Collection = new ObservableCollection<string>();
Collection.CollectionChanged += (s, e) =>
{
// collection changed!
};
Hope this helps! Cheers!

Single-line wpf textbox horizontal scroll to end

I have a templated listbox which template among other things contains a wpf textbox too. The data is provided to the listbox through ItemsSource.
The textboxes display filepaths and these are usally quite long. I want when the textboxes are loaded to show the end of the filepaths.
I tried a combination of DataContextChanged event and setting HorizontalScrollBarVisibility (using double.max or getting the real char length) but to no success. The DataContextChanged seems to be the correct event to use as it fires on each setting of the ItemsSource.
Edit:
Here is sample code to show when the suggestion by Lester works and when it doesnt. I am trying to have it work when the text is set through binding.
<Window x:Class="WpfAppTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
Loaded="LoadedHandler">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBox Name="tbb" Width="50" Height="20" Text="{Binding Path=Str}"
IsReadOnly="True" Grid.Column="0" Grid.Row="0"
DataContextChanged="ContextChangedHandler"/>
<ListBox SelectionMode="Single" x:Name="listBox" Grid.Column="0" Grid.Row="1"
VerticalAlignment="Top">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Width="50" Height="20" Text="{Binding Path=Str}"
IsReadOnly="True"
DataContextChanged="ContextChangedHandler"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var obj = new SomeClass
{
Str = "qwetyuiuropqo[psdal;dkas;ldamzxn m,cnz128391"
};
listBox.ItemsSource = new List<SomeClass> { obj };
tbb.DataContext = obj;
}
public class SomeClass
{
public string Str { get; set; }
}
private void LoadedHandler(object sender, RoutedEventArgs e)
{
var obj = new SomeClass
{
Str = "qwetyuiuropqo[psdal;dkas;ldamzxn m,cnz128391"
};
listBox.ItemsSource = new List<SomeClass> { obj };
tbb.DataContext = obj;
}
private void ContextChangedHandler(object sender, DependencyPropertyChangedEventArgs e)
{
var textBox = sender as TextBox;
if (textBox == null) return;
textBox.CaretIndex = textBox.Text.Length;
var rect = textBox.GetRectFromCharacterIndex(textBox.CaretIndex);
textBox.ScrollToHorizontalOffset(rect.Right);
}
}
This code worked for me for scrolling to the end of the TextBox (taken from this question):
textBox.CaretIndex = textBox.Text.Length;
var rect = textBox.GetRectFromCharacterIndex(textBox.CaretIndex);
textBox.ScrollToHorizontalOffset(rect.Right);
Solution is to change DataContextChanged event with Loaded so that proper notifications are received for the textbox.

Categories