How to access a specific item in a Listbox with DataTemplate?
Thank you for your help guys!! Finally i got it. Solved the problem with the VisualTreeHelper. What a great function ^^
private void ContactListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ContactListBox.SelectedIndex == -1)
return;
currentSelectedListBoxItem = this.ContactListBox.ItemContainerGenerator.ContainerFromIndex(ContactListBox.SelectedIndex) as ListBoxItem;
if (currentSelectedListBoxItem == null)
return;
// Iterate whole listbox tree and search for this items
TextBox nameBox = helperClass.FindDescendant<TextBox>(currentSelectedListBoxItem);
TextBlock nameBlock = helperClass.FindDescendant<TextBlock>(currentSelectedListBoxItem);
helperFunction
public T FindDescendant<T>(DependencyObject obj) where T : DependencyObject
{
// Check if this object is the specified type
if (obj is T)
return obj as T;
// Check for children
int childrenCount = VisualTreeHelper.GetChildrenCount(obj);
if (childrenCount < 1)
return null;
// First check all the children
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child is T)
return child as T;
}
// Then check the childrens children
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = FindDescendant<T>(VisualTreeHelper.GetChild(obj, i));
if (child != null && child is T)
return child as T;
}
return null;
}
Binding selected item in listbox with datatemplate
Note that, when you use an ItemTemplate
, each item gets wrapped in a ListBoxItem
-- so, you shouldn't use a ListBoxItem in the template, as that will produce two nested ones. Try removing it, like so:
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Tapped="userTapped" Tag="{Binding}">
<!-- content here -->
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
For the handler, it might be easier to simply reference the ListBox by name instead of trying to use Tag
(thanks @MetroSmurf):
private void userTapped(object sender, TappedRoutedEventArgs e)
{
var selectedItem = lbMessagesUsersList.SelectedItem;
var subject = MyDatasMessagesUserList.FirstOrDefault(sub => sub == selectedItem);
}
For the second part of your question: to stretch the items, you need to stretch the ListBoxItem
wrapper. Do this by using ItemContainerStyle
:
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Binding selected item from Listbox with datatemplate
Sorry I couldn't add a comment. I hope your requirement is to show the selected item's value in the TextBox and update the same value into your collection(in your case DataTable) as you change value from the TextBox. If that is your requirement change your TextBox bindings like this
<TextBox x:Name="ValueText" Text="{Binding SelectedItem.ColumnName, ElementName=listBox, UpdateSourceTrigger=PropertyChanged}" />
I don't know why you are binding SelectedItem to string type SelectedItemString property. It will create binding error as per your source.
Get DataTemplate from data object in ListBox
If you have scope to add a new dependency property to the EditableTextBlock
user control you could consider adding one that has the name StartupInEditMode
, defaulting to false
to keep the existing behavior.
The Loaded
handler for the UserControl
could then determine the status of StartupInEditMode
to decide how to initially set the value of IsInEditMode
.
//..... Added to EditableTextBlock user control
public bool StartupInEdit
{
get { return (bool)GetValue(StartupInEditProperty); }
set { SetValue(StartupInEditProperty, value); }
}
public static readonly DependencyProperty StartupInEditProperty =
DependencyProperty.Register("StartupInEdit", typeof(bool), typeof(EditableTextBlock ), new PropertyMetadata(false));
private void EditableTextBlock_OnLoaded(object sender, RoutedEventArgs e)
{
IsInEditMode = StartupInEditMode;
}
For controls already in the visual tree the changing value of StartupInEdit
does not matter as it is only evaluated once on creation. This means you can populate the collection of the ListBox
where each EditableTextBlock
is not in edit mode, then swap the StartupInEditmMode
mode to True
when you start adding new items. Then each new EditableTextBlock
control starts in the edit mode.
You could accomplish this switch in behavior by specifying a DataTemplate
where the Binding
of this new property points to a variable of the view and not the collection items.
<DataTemplate DataType="local:Column">
<utils:EditableTextBlock x:Name="editableTextBlock"
Text="{Binding Name, Mode=TwoWay}"
StartupInEditMode="{Binding ANewViewProperty, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
</DataTemplate>
You need to add a property to the parent Window
(or Page
or whatever is used as the containter for the view) called ANewViewProperty
in this example. This value could be part of your view model if you alter the binding to {Binding DataContext.ANewViewProperty, RelativeSource={RelativeSource AncestorType={x:Type Window}}}
.
This new property (ANewViewProperty
) does not even need to implement INotifyPropertyChanged
as the binding will get the initial value as it is creating the new EditableTextBlock
control and if the value changes later it has no impact anyway.
You would set the value of ANewViewProperty
to False
as you load up the ListBox
ItemSource
initially. When you press the button to add a new item to the list set the value of ANewViewProperty
to True
meaning the control that will now be created starting up in edit mode.
Update: The C#-only, View-only alternative
The code-only, view-only alternative (similar to user2946329's answer)is to hook to the ListBox.ItemContainerGenerator.ItemsChanged
handler that will trigger when a new item is added. Once triggered and you are now acting on new items (via Boolean DetectingNewItems
) which finds the first descendant EditableTextBlock
control for the appropriate ListBoxItem
visual container for the item newly added. Once you have a reference for the control, alter the IsInEditMode
property.
//.... View/Window Class
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
MyListBox.ItemContainerGenerator.ItemsChanged += ItemContainerGenerator_ItemsChanged;
}
private void ItemContainerGenerator_ItemsChanged(object sender, System.Windows.Controls.Primitives.ItemsChangedEventArgs e)
{
if ((e.Action == NotifyCollectionChangedAction.Add) && DetectingNewItems)
{
var listboxitem = LB.ItemContainerGenerator.ContainerFromIndex(e.Position.Index + 1) as ListBoxItem;
var editControl = FindFirstDescendantChildOf<EditableTextBlock>(listboxitem);
if (editcontrol != null) editcontrol.IsInEditMode = true;
}
}
public static T FindFirstDescendantChildOf<T>(DependencyObject dpObj) where T : DependencyObject
{
if (dpObj == null) return null;
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(dpObj); i++)
{
var child = VisualTreeHelper.GetChild(dpObj, i);
if (child is T) return (T)child;
var obj = FindFirstChildOf<T>(child);
if (obj != null) return obj;
}
return null;
}
Update #2 (based on comments)
Add a property to the view that refers back to the the ViewModel assuming you keep a reference to the View Model in the DataContext
:-
..... // Add this to the Window/Page
public bool DetectingNewItems
{
get
{
var vm = DataContext as MyViewModel;
if (vm != null)
return vm.MyPropertyOnVM;
return false;
}
}
.....
how to access TextBox inside ListBox itemsSource which is Datatemplate?
Here is an example of you could find the controls in the DataTemplate
inside the event handler:
private void wbprofileDesc_GotFocus(object sender, RoutedEventArgs e)
{
TextBox wbprofileDesc = sender as TextBox;
Grid parentGrid = wbprofileDesc.Parent as Grid;
ComboBox wbselect = parentGrid.Children.OfType<ComboBox>().FirstOrDefault(x => x.Name == "wbselect");
Label wbwidthvalue = parentGrid.Children.OfType<Label>().FirstOrDefault(x => x.Name == "wbwidthvalue");
}
How to select an item in a ListBox and pick the item's name from a DataTemplate in WPF
Although I would still encourage you to use MVVM
way of dealing with UI events, here's how you can achieve what you want, using Cancel
button's click event handler.
First in your xaml, bind
file name to Cancel
button's Tag
property.
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="FileName" text={Binding FileName}" />
<ProgressBar ... />
<Button Content="Cancel" Tag="{Binding FileName}"
Click="ButtonCancel_Click" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox>
Then in your click event handler
private void ButtonCancel_Click(object sender, RoutedEventArgs e)
{
Button myButton = (Button)sender;
string fileName = myButton.Tag.ToString();
// use fileName
}
Edit
Just to add a complete example, that was tested locally, and ensured that works.
XAML
<Window x:Class="WpfTestApp.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>
<ListBox Name="listBox1">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock x:Name="FileName" Text="{Binding Path=FileName}" />
<Button Content="Cancel" Tag="{Binding Path=FileName}"
Click="ButtonCancel_Click" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var fileNames = new List<DownloadModel>
{
new DownloadModel
{
FileName = "File1"
},
new DownloadModel
{
FileName = "File2"
},
new DownloadModel
{
FileName = "File3"
}
};
listBox1.ItemsSource = fileNames;
}
private void ButtonCancel_Click(object sender, RoutedEventArgs e)
{
var myButton = sender as Button;
if (myButton.Tag == null)
{
MessageBox.Show("Tag value was null.");
}
else
{
MessageBox.Show(string.Format("File name is {0}", myButton.Tag));
}
}
}
public class DownloadModel
{
public string FileName { get; set; }
}
Listbox DataTemplate to be applied to different models
I think it is better to use ItemsControl.ItemTemplateSelector if you like two datatemplates.
First you need one class inherit class "DataTemplateSelector" and override its method to select which datatemplate to use.
public class ModelItemTemplateSelector: DataTemplateSelector
{
public DataTemplate Model1Template { get; set; }
public DataTemplate Model2Template { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if(item is Model1)
{
return Model1Template;
}
else if(item is Model2)
{
return Model2Template;
}
return base.SelectTemplate(item, container);
}
}
Then code in xaml is below
<ListBox ItemsSource="{Binding Source}">
<ListBox.ItemTemplateSelector>
<local:ModelItemTemplateSelector Model1Template="{StaticResource Model1Template}" Model2Template="{StaticResource Model2Template}" />
</ListBox.ItemTemplateSelector>
</ListBox>
And the other code:
Two datatemplates
<DataTemplate x:Key="Model1Template" DataType="{x:Type local:Model1}">
<TextBlock Text="{Binding Age}" />
</DataTemplate>
<DataTemplate x:Key="Model2Template" DataType="{x:Type local:Model2}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
Two types
public class BaseModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Model1 : BaseModel
{
private int age;
public int Age
{
get { return age; }
set
{
age = value;
this.RaisePropertyChanged(nameof(Age));
}
}
}
public class Model2 : BaseModel
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
this.RaisePropertyChanged(nameof(Name));
}
}
}
Source in vm
private ObservableCollection<BaseModel> source;
public ObservableCollection<BaseModel> Source
{
get { return source; }
set
{
source = value;
this.RaisePropertyChanged(nameof(Source));
}
}
Find Control in DataTemplate with a multi selection listbox
You should set the Checkbox IsChecked binding like this:
IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}"
This means whenever you select a ListBoxItem your checkbox will also become checked.
Hope this helped :)
Related Topics
How to Use Httpclient to Send Content in Body of Get Request
String Concatenation VS String Builder. Performance
C# - Fill a Combo Box with a Datatable
How Should Anonymous Types Be Used in C#
What Is the Static Variable Initialization Order Across Classes in C#
Binding Listbox to List<Object> in Winforms
How to Prevent a SQL Injection Escaping Strings
.Net 4.0 and the Dreaded Onuserpreferencechanged Hang
How to Bind Crystal Report to Manually Created Dataset
Get SQL Query from Linq to SQL
Set Default Global JSON Serializer Settings
Cancelling a Task Is Throwing an Exception
Create Xml Nodes Based on Xpath
Webapi Streamcontent VS Pushstreamcontent
How to Make a Call to My Wcf Service Asynchronous