ListView SelectItem Binding not working as expected - c#

I have a fairly straight forward listview control for my Windows 8 XAML/C# application. I am binding the listview to a PersonList and that works correctly. However, what I'd like to do and haven't been able to find the answer for is I want the to click an item in the listview and be able to display the PersonSingle object to the other textboxes on the screen.
I've read some posts that indicate that the listview may not be the right control for this operation. Am I missing something in my listview that would allow me to do this operation or should I use a different control?
<ListView
x:Name="itemListView"
Visibility="Visible"
Width="auto"
Height="auto"
ItemsSource="{Binding PersonList, Mode=TwoWay}"
SelectedItem = "{Binding PersonSingle, Mode=OneWay}"
ItemTemplate="{StaticResource person80Template}"
SelectionMode="Single"
Grid.Row="1"
Grid.Column="1"
VerticalAlignment="Stretch">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Margin="7,7,0,0">
<Button
AutomationProperties.Name="PersonValue"
Content="{Binding PersonName}"
Style="{StaticResource TextButtonStyle}"/>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
UPDATE:
So I figured out how to do it in a non-MVVM way (or at least not in a true MVVM way)
I added an ItemClick event to my ListView:
ItemClick="itemListView_ItemClick"
And then in the code behind I added the following:
private void itemListView_ItemClick(object sender, ItemClickEventArgs e)
{
VM.PersonSingle = ((Person)e.ClickedItem);
}
That works, but like I said, it doesn't feel very MVVM'ish. If you have any suggestions on how to make it work without having to manually set the PersonSingle object please answer below.

Your ItemClick solution is the right solution. You could create an attached behavior if you are opposed to handling events, but that's your choice.

Related

C# WPF DataBinding

Im new in wpf programming and i have an application with MVVM. I have a StackPanel and inside the StackPanel there is a ItemsControl named ListView and the Itemsource is my ObserveCollection NewProducts which conatins ProductID and Name.
So what is my code does, well it generates buttons with Names from my Collection NewProducts and i wanna give this buttons Command that triggers when i click on them.
I try this:
<StackPanel Grid.Row="1">
<ItemsControl x:Name="ListViewProducts" ItemsSource="{Binding NewProducts}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="10" Width="auto" Height="auto">
<StackPanel>
<Border Width="100" Height="40" CornerRadius="5">
<Button Content="{Binding Name}" Tag="{Binding ProductId}" FontWeight="Bold" Command="{BindingSaleViewModel.SendCommand}"/>
</Border>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
But when i run my program and when i click on my buttons nothing happens. Its because the Command that i have in my SaleViewModel was not found in Product which is my Model class. And i wanna know if i can somehow get the path to my Command in my ViewModel.
Thanks, and please forgive me mine English, I know its bad.
Binding methods in WPF is unfortunately not that simple.
The command is actually a class, that implements the ICommand interface.
You can find an example here: How to Bind a Command in WPF.
Otherwise you can bind the click event to a method in code-behind <Button Click="Button_Click"/>
In code-behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
// Add your code here...
}
Give your root element a name. eg:
<UserControl x:Class="blah"
other stuff
x:Name="root">
Then you can reference the root element in your binding, who's datacontext will (presumably?) be your SaleViewModel:
<Button Content="{Binding Name}" Tag="{Binding ProductId}" FontWeight="Bold"
Command="{Binding DataContext.SendCommand, ElementName=root}"
CommandParameter="{Binding}"/>
Also note the CommandParameter binding which will pass the button's data context (Product) through to your command, otherwise you won't know which product the command relates to.

Invoking Commands from ListView in MVVM

I'm using Prism for Windows Runtime to wire up events in my Views with DelegateCommands in my ViewModels. I am wondering what would be the best way to invoke commands (e.g. select item) from a ListView that contains Buttons (or custom controls derived freom the Button class). I'd like to keep the effects (e.g. background change, tilt effect) provided by the Button control. But the button unfortunately absorbs the click events, which, in consequence, I cannot use in the ListView to hook up my commands e.g with the following XAML (and Behaviors SDK):
<ListView ItemsSource="{Binding AvailableItemsList}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<customControls:NavMenuButton Style="{StaticResource SelectionListMenuButton}" Content="{Binding Nickname}" DescriptionText="{Binding Name}" />
</DataTemplate>
</ListView.ItemTemplate>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SelectionChanged">
<core:InvokeCommandAction Command="{Binding ItemSelectedCommand}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</ListView>
What would be the best way to achieve this? I have found similar questions, but the difference here is that the controls in the list items are apparently "stealing" the click event (while it works just fine with e.g. a simple TextBlock).
To close this question, here is the solution based on MatDev8's comment above (thank you!):
<ListView x:Name="myListView" ItemsSource="{Binding AvailableItemsList}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<customControls:CustomTextButton Style="{StaticResource SelectionListMenuButton}"
Content="{Binding Nickname}"
DescriptionText="{Binding Name}"
Command="{Binding DataContext.ItemSelectedCommand, ElementName=myListView}"
CommandParameter="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
To have the commands binding in ListView you can use buttons within the ListView.
now for the clicking related issue you can modify your button's Controltemplate to make it look like a simple textblock. this way your clicking will also work on the listview and it will not be displayed as button.
<ControlTemplate TargetType="Button">
<TextBlock Text="{TemplateBinding Content}" />
</ControlTemplate>
You can also customize the click event in other way like moving focus to the button should also raise your click event(indirectly fire your command). This will help in your case of Listview where just moving to next item should also fire the command.

Use the DataTemplate to display the content in the exact same way as not using a DataTemplate

My ListBox currently binds as expected using
<ListBox ItemsSource="{Binding ChildDuplicate, UpdateSourceTrigger=PropertyChanged}" DataContext="{Binding}" Height="Auto" >
Where ChildDuplicate is an ObservableCollection<MyUserControl>
The problem I am facing is I need to add a button for every item within the ListBox (so it shows both my MyUserControl and the Button control).
This button cannot live within the MyUserControl for several reasons.
I was hoping to use a DataTemplate but this doesn't seem to work (XmlParser error is thrown), I've tried
<ListBox ItemsSource="{Binding ChildDuplicate, UpdateSourceTrigger=PropertyChanged}" DataContext="{Binding}" Height="Auto" >
<ListBoxItem>
<DataTemplate>
<ContentControl Content="{Binding}"></ContentControl>
</DataTemplate>
</ListBoxItem>
</ListBox>
I also tried using the ItemsControl control in the same way, the same issue.
Any idea how I use the DataTemplate to display the content in the exactly same way as not using a DataTemplate?
Try this instead:
<ListBoxItem>
<ListBoxItem.Template>
<ControlTemplate>
<ContentControl Content="{Binding}"></ContentControl>
</ControlTemplate>
</ListBoxItem.Template>
</ListBoxItem>

How do I get access to the item in a ListBox that is being rendered rather than its source data?

In a continuation from my question here
I tried using the solution provided in the linked answer, however when I give it the image which the ListBox is binding to, it gives me the wrong position, because the Item Template is loading in a source URL rather than the actual image, and it seems the data is null.
My Item Template looks like this:
<ListBox ItemsSource="{Binding Items}"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
Height="64" Name="listBoxSource"
VerticalAlignment="Bottom"
SelectionChanged="listBoxSource_SelectionChanged" Canvas.Left="32" Canvas.Top="365" Width="596"
>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image x:Name="ListImage" Source="{Binding thumbnailURL}" Height="64" Width="64"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
How do I get access to "ListImage" for the currently selected Item?
Every ItemsControl has an ItemsContainerGenerator, which (surprisingly enough) is responsible for generating a control for each item in the list. It provides a few useful methods for finding the container of a given item, and vice versa. You can use it like this:
private void listBoxSource_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listBox = (ListBox) sender;
var containerGenerator = listBox.ItemContainerGenerator;
var container = (UIElement)containerGenerator.ContainerFromItem(listBox.SelectedItem);
}
You can then use the variable container with the solution from your other post to find the coordinates,
Point position = container.GetRelativePosition(Application.Current.RootVisual);
And on a side note, in your DataTemplate you don't need the StackPanel, since the ListBox is providing that with its ItemsPanelTemplate.
<DataTemplate>
<Image x:Name="ListImage" Source="{Binding thumbnailURL}" Height="64" Width="64"/>
</DataTemplate>
on tap event you can do this:
private void image_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
Image selectedImage = e.OriginalSource as Image;
}
is this what you need?
I would suggest a different solution because the way you are trying to solve it (finding the control) is prone to error.
Make the DataTemplate a resource and refer to it in the ListBox by using ItemTemplate="{StaticResource myTemplate}"
Next, when an item is selected, add a ContentControl to the Canvas, set its ContentTemplate to the same DataTemplate and the DataContext to the SelectedItem.
This way you only have to define the template once (one place to maintain it) and do not have to walk the VisualTree.

Add adorner to bound children?

<ItemsControl Name="CanvasTableMap" ItemsSource="{Binding}" ItemsPanel="{DynamicResource ItemsPanelTemplate1}" ItemTemplate="{DynamicResource DataTemplate1}">
<ItemsControl.Resources>
<ItemsPanelTemplate x:Key="ItemsPanelTemplate1">
<WrapPanel Background="{DynamicResource ContentBackground}" />
</ItemsPanelTemplate>
<DataTemplate x:Key="DataTemplate1">
<Button Canvas.Left="100" Content="{Binding Name}" Template="{DynamicResource ButtonTableTemplate}"></Button>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
Here is my code.No problem with that. I have created an adorner and i would like to add an adorner for each button when i want. It is a little difficult as i dont know how to get the Buttons. CanvasTableMap.Items returns the Model so i dont know how to get access to the controls efficiently.
An easy way to do that is to define a handler for the Loaded event of the button, and add the adorner in that handler:
XAML
<Button Canvas.Left="100" Content="{Binding Name}" ... Loaded="Button_Loaded" />
Code-behind
private void Button_Loaded(object sender, RoutedEventArgs e)
{
var button = (Button)sender;
var layer = AdornerLayer.GetAdornerLayer(button);
// Add the adorner
...
}
If you don't want to use code-behind, you can create an attached behavior (either with System.Windows.Interactivity or by creating an attached property)
You can use the ItemContainerGenerator to get the control created from the data (ContainerFromItem). Usually doing things that way is not such a good idea though.

Categories