Get clicked object from ItemsControl and populate Popup with its properties - wpf

I've got an ItemsControl which displays objects from a list from my viewmodel. I also have code to display a Popup when the user click on an item in the ItemsControl. However I don't know how to get the actual object from the clicked item to read its properties and display them in the Popup.
I've got a Click event handler for the Button (which is used to display my items in the ItemsControl) and I tried to see in the debugger if the button contains the desired object but apparently it doesn't.
How else can I get the object and populate the popup with its properties?
<ItemsControl ItemsSource="{Binding RecipientsNames}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button x:Name="btnConvoContact" Click="BtnConvoContact_Click"
Background="White" Foreground="Black" Cursor="Hand"
Width="Auto" Height="14" Padding="0" BorderThickness="0" Margin="0 0 6 0" HorizontalAlignment="Left" VerticalAlignment="Top">
<TextBlock Text="{Binding Path=Name}" FontSize="12" Margin="0 -2 0 -2"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Cast the DataContext of the sender argument in the event handler to your data type:
private void BtnConvoContact_Click(object sender, RoutedEventArgs e)
{
Button btn = sender as Button;
var dataObject = btn.DataContext as YourDataClass;
}

Related

Getting current Object in Control template

<ControlTemplate TargetType="{x:Type ListBoxItem}">
<StackPanel>
<StackPanel Margin="0,0,28,0" Orientation="Horizontal" Visibility="{Binding IsEditable,Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock Foreground="Gray" Text="{Binding DateCreated,Converter={StaticResource DateTimeConverter}}" FontFamily="/Assets/Fonts/Berthold Akzidenz Grotesk BE Regular.ttf" FontSize="16"/>
<TextBlock Text=":" Foreground="Gray"/>
<TextBlock Width="20"/>
<TextBox ScrollViewer.HorizontalScrollBarVisibility="Disabled" BorderThickness="0" Name="TrainerNoteText" Text="{Binding TrainerNote}" FontFamily="/Assets/Fonts/Berthold Akzidenz Grotesk BE Regular.ttf" Foreground="Black" FontSize="16" TextWrapping="Wrap" KeyUp="EditTrainerNote" Width="400"/>
</StackPanel>
</StackPanel>
</ControlTemplate>
The above control template is in a listview. The textbox inside is editable. So when user presses the enter key, I need to get the current object associated with that. How to do this?
You can listen to KeyDown RoutedEvent at ListView level.
http://msdn.microsoft.com/en-us/library/system.windows.input.keyboard.keydown.aspx
Its an attached event and its handler can be placed anywhere in VisualTree.
Here is an example:
<StackPanel TextBox.KeyDown="OnKeyDownHandler">
<TextBox Width="300" Height="20"/>
</StackPanel>
And this is the handler:
public void OnKeyDownHandler(object sender, KeyEventArgs e)
{
if (e.Key == Key.Return)
{
TextBox tbx = (TextBox)sender;
tbx.....
}
}
You know, you really should define what your items look like in a DataTemplate defined in the ListBox.ItemTemplate property and not the ListBoxItem.Template property. Based on the example from the linked page:
<ListBox Width="400" Margin="10" ItemsSource="{Binding YourCollectionProperty}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=TaskName}" />
<TextBlock Text="{Binding Path=Description}"/>
<TextBlock Text="{Binding Path=Priority}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When Binding a collection property to the ListBox.Items property, all of the UI elements inside the DataTemplate will have access to the properties of the type that is in the collection. In this example, the type that populates the YourCollectionProperty collection has TaskName, Description and Priority properties in it. You can replace these properties with those from the type that is in your collection property.
If you set up your properties to implement the INotifyPropertyChanged interface (or use DependencyProperties then any updates in the UI elements will automatically be updated in the data objects in the view model/code behind. Therefore, there is no need to add KeyDown or KeyUp handlers. For more information, please read the Data Binding Overview page on MSDN.

WPF - Filtering a DataCollection with an autocompletebox

I have a view and ViewModel that are working perfectly. I have recently added an AutocompleteBox (found in the WPF Toolkit) which allows users to quickly look up an item.
My view is as such:
An ItemsControl containing my CollectionViewSource named People. Generating perfectly
An AutocompleteBox where the dropdown shows only the items containing the values the user is typing in the AutocompleteBox. Works well. If I type John, all of the people in my CollectionViewSource named People with the word John in the name appear in the dropdown.
My issue is: how do I filter my ItemsControl when the user selects the item he wishes to see from the Dropdown?
My code so far in XAML to bind the data:
<toolkit:AutoCompleteBox Height="25" Width="400"
Foreground="AliceBlue"
ItemsSource="{Binding People.View}"
ValueMemberPath="Name"
Text="{Binding Name}"
IsTextCompletionEnabled="True"
FilterMode="Contains"
Background="#303030">
<toolkit:AutoCompleteBox.ItemTemplate>
<DataTemplate>
<Grid Width="360" HorizontalAlignment="Left">
<StackPanel Orientation="Vertical" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="300">
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" Foreground="#25A0DA"
FontSize="14" Width="300"/>
<TextBlock Text="{Binding Status}" FontWeight="Normal" Foreground="White"
FontSize="10" Width="300"/>
</StackPanel>
</Grid>
</DataTemplate>
</toolkit:AutoCompleteBox.ItemTemplate>
</toolkit:AutoCompleteBox>
<ItemsControl x:Name="tStack" Grid.Column="0" Grid.Row="1"
ItemsSource="{Binding People.View}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
BorderThickness="0.5">
</ItemsControl>
The itemsControl is styled and the format of the items inside it are also templated/styled but far too long and unconstructive to post here.
From the Name property setter in your viewmodel to which toolkit:AutoCompleteBox.Text is bound, you will have to filter the ObservableCollection backing your Collectionview i,e ItemsSource of your ItemsControl.
If you have your Collectionsource with you then you can have filter applied to it like below:
ICollectionView _peopleView = CollectionViewSource.GetDefaultView(peoples);
_peopleView .Filter = PeopleFilter
private bool PeopleFilter(object item)
{
People people= item as People;
return people.Name.Contains( _filterString ); //Here filter string will be your Name prperty value
}
From Setter of name property you will hav to call _peopleView .Refresh(); to apply the filter
Thanks

ListBox with "load more" option

I would like to know how to construct the ListBox in WP7 that only load 20 items at a single time, and have a footer that show "load more" if there is any.
When user press the "load more", it will load another 20 in the list without loading the previous loaded data?
I am using LINQ at the behind source.
my code for XMAL as follow:
<Grid>
<ListBox name="newsIndexListBoxEN">
<ListBoxItem>
<DataTemplate>
<StackPanel Width="410" Orientation="Horizontal" VerticalAlignment="Top" Margin="0,5,0,5">
<StackPanel Background="DarkBlue" Margin="10,0,0,0" Height="100" Width="100" VerticalAlignment="Top">
<TextBlock Name="columnsTypeTB" Text="{Binding pType}" Margin="0,0,0,0" Foreground="White" FontSize="23" HorizontalAlignment="Center" />
<Image Width="100" Height="100" VerticalAlignment="Top" HorizontalAlignment="Center" Source="Background.png" />
</StackPanel>
<StackPanel Width="300" Height="100" Margin="0,0,0,0">
<Path Margin="0,0,0,0" Data="M39,8 L389,8" Fill="DarkBlue" Height="1" Stretch="Fill" Stroke="DarkBlue" UseLayoutRounding="False" Width="400"/>
<TextBlock Margin="8,0,0,0" Text="{Binding pTitle}" Tag="{Binding pID}" Style="{StaticResource PhoneTextNormalStyle}" TextWrapping="Wrap" Width="292" Height="66" />
<TextBlock Margin="8,5,0,0" Text="{Binding pDate}" Tag="{Binding pID}" MouseEnter="NewsViewContent_mouseEnter" Style="{StaticResource PhoneTextSmallStyle}" VerticalAlignment="Bottom" TextWrapping="Wrap" Width="292" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBoxItem>
</ListBox>
</Grid>
C# Code as follow:
using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream fs = storage.OpenFile(fileName, FileMode.Open))
{
XDocument menuIndex = XDocument.Load(fs);
var menuIndexList = from query in menuIndex.Descendants("news")
orderby (int)query.Element("newsID") descending
select new mkmenu
{
pID = query.Element("newsID").Value,
pTitle = query.Element("newsTitle").Value,
pDate = query.Element("newspDate").Value,
pType = newsType
};
newsIndexListBoxEN = menuIndexList.Count();
}
}
any ideas? sample code?
You can edit your listbox template to show a "Load more" button at the end of the list. In Blend, right click on your listbox, choose Edit Template, Edit a copy. By default, your listbox has a template like this:
ScrollViewer
ItemPresenter
Wrap your ItemPresenter into a StackPanel, then add a button at the end:
ScrollViewer
StackPanel
ItemPresenter
Button
That button will always be displayed at the end of the listbox. Handle the Clicked event of that button to add items to your ObservableCollection.
You can bind your listbox to ObservableCollection and add first 20 items on your page(app) load. Than after pressing "load more" get next 20 items and add to collection. Items will automatically be added to listbox.

Databinding and bound buttons

I have an ItemsControl bound to a list of objects.
It basically displays a list of bound properties on screen with a textbox and button next to each one to allow you to add additional information to each database field.
In the onclick of the button I need to take the string in the textbox and store it in the object for that item. The XAML looks a bit like this
<ItemsControl x:Name="ListDatabaseFields" ItemsSource="{Binding Path=SelectedImport.ColumnMappings}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<StackPanel>
<TextBlock Text="{Binding DatabaseColumn.Name}" TextWrapping="Wrap" Grid.Column="0"/>
<TextBox Name="txtNewFileField" Width="100"/>
<Button Name="Add" Content="Add File Field" Style="{StaticResource LinkButton}" Width="50" Click="Add_Click"/>
</StackPanel>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Whats the best way in the XAML to access the ColumnMapping the itemcontrol is bound to for that item and change its file field property.
I'm not sure if i understood your question correctly, please clarify if this is not what you are looking for.
I have adapted this to my test application, my DataTemplate looks like this:
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<TextBox MinWidth="100" Name="tbNewName"/>
<Button MinWidth="100"
Tag="{x:Reference tbNewName}" Click="ButtonNewName_Click"
Content="Do Stuff"/>
</StackPanel>
</DataTemplate>
Notice the Button.Tag which references the TextBox, i can use it in the handler like this:
private void ButtonNewName_Click(object sender, RoutedEventArgs e)
{
Employee emp = (sender as FrameworkElement).DataContext as Employee;
TextBox tbNewName = (sender as FrameworkElement).Tag as TextBox;
emp.Name = tbNewName.Text;
}
The DataContext is inherited, so the sender (the Button) contains the data-item, in your case a ColumnMapping, the Tag gives me the TextBox and that is all i need for changing a property.
If you need to reference more controls you could create an array in the Tag.

Binding to CurrentItem in a ItemsControl

The XAML below is basically trying to make a list of Buttons (rendered from the Name property of objects in the Views collection in the current DataContext.
When I click on a button the CurrentItem property of CollectionViewSource should change and the associated View should be displayed in a content presenter.
OK. If I click in the ListBox in the XAML below it works exactly as desired.
But, If I click a button in the UniformGrid (created by the items control) the CurrentItem property is not updated.
How do I get the CurrentItem to be updated when an item is selected in the ItemsControl?
Thanks
<UserControl x:Class="Pos.Features.Reservation.ReservationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:product="clr-namespace:Pos.Features.ProductBrowser"
xmlns:activity="clr-namespace:Pos.Features.ActivityBrowser"
xmlns:addbysku="clr-namespace:Pos.Features.AddBySku"
xmlns:client="clr-namespace:Pos.Features.ClientBrowser"
xmlns:notes="clr-namespace:Pos.Features.Notes"
xmlns:controls="clr-namespace:Pos.Views"
xmlns:res="clr-namespace:Pos.Core;assembly=Pos.Core"
Height="300" Width="300">
<UserControl.Resources>
<DataTemplate DataType="{x:Type product:ProductBrowserViewModel}">
<product:ProductBrowserView/>
</DataTemplate>
<DataTemplate DataType="{x:Type activity:ActivityBrowserViewModel}">
<activity:ActivityBrowserView/>
</DataTemplate>
<CollectionViewSource x:Name="x" x:Key="ViewsCollection" Source="{Binding Views}" />
</UserControl.Resources>
<StackPanel>
<ListBox Name="ListBoxMenu" Grid.Column="0" Margin="5" ItemsSource="{Binding Source={StaticResource ViewsCollection}}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Padding="10"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Column="1" Content="{Binding ElementName=ListBoxMenu, Path=SelectedItem}"/>
<ItemsControl Grid.Column="2" Name="ViewList" ItemsSource="{Binding Source={StaticResource ViewsCollection}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button>
<TextBlock Text="{Binding Path=Name}" Name="txtButtonLabel" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Black"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1" Columns="{Binding Views.Count}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ContentControl Grid.Column="3" Content="{Binding Source={StaticResource ViewsCollection}, Path=CurrentItem}"/>
<Button Grid.Column="4" Click="Button_Click">dsadsd</Button>
</StackPanel>
</UserControl>
Your button does nothing. Usually your ViewModel would have an ICommand called Select (or something similar) that the Button would be bound against
Command="{Binding Select, ElementName="root"}"
and you'd pass the instance to the ICommand that you'd like to select
CommandParameter="{Binding}"
It would look something like this (c#/XAML like pseudocode):
public class MyModel { public string Name {get;set;} }
public class MyViewModel
{
public ObservableCollection<MyModel> Models {get;set;}
public ICommand Select {get;set;}
/* configure Models and Select etc */
}
<UserControl DataContext="{StaticResource MyViewModelInstance}" x:Name="root">
<ItemsControl ItemsSource="{Binding Models}">
<ItemsControl.ItemTemplate>
<ItemsPanelTemplate>
<Button Text="{Binding Name}"
Command="{Binding Select, ElementName="root"}"
CommandParameter="{Binding}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
The ItemsControl binds to Models, so each MyModel in Models gets a button. The button text is bound to the property Name. The button command is bound to the Select property in the ViewModel. When the button is pressed, it calls the ICommand, sending in the instance of MyModel that the button is bound against.
Please do note that using ViewModels within a UserControl is a code smell. UserControls should appear to users as all other controls--they should have bindable public properties which are bound to the user's ViewModel, not yours. You then bind to the values of these properties within the UserControl. For this example, you would have an ItemsSource property defined on your UserControl, and the ItemsControl would bind to this property rather than a ViewModel directly.
I think you have to do it manually in code-behind.
XAML
<Button Click="ViewListButton_Click">
<TextBlock Text="{Binding Path=Name}" Name="txtButtonLabel" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Black"/>
</Button>
Code-behind
private void ViewListButton_Click(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
ICollectionView view = CollectionViewSource.GetDefaultView(ViewList.ItemsSource);
view.MoveCurrentTo(button.DataContext);
}
If you're using the MVVM pattern and/or you don't want to use code-behind, you could bind the button's Command to a command in your ViewModel, as suggested by Will

Resources