WPF Getting the content of a ListViewItem - wpf

I have a school assignment: when a ListViewItem gets double-clicked, it's content has to be displayed in the MessageBox. I've done quite a lot of research on the internet and couldn't find a fitting solution to my problem. What is the easiest way to get the content of a ListViewItem?
The ListView looks like this:
<ListView Name="LV">
<ListViewItem>a listview</ListViewItem>
<ListViewItem >with several</ListViewItem>
<ListViewItem>items</ListViewItem>
</ListView>

When you have explictly assigned ListViewItem instances to the ListView (as in the example in the question), you can get each ListViewItem's content by means of its Content property.
You may access it in a MouseDoubleClick event handler.
<ListView x:Name="LV" MouseDoubleClick="LV_MouseDoubleClick">
<ListViewItem>a listview</ListViewItem>
<ListViewItem>with several</ListViewItem>
<ListViewItem>items</ListViewItem>
</ListView>
The event handler in code behind:
private void LV_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var item = (ListViewItem)LV.SelectedItem;
var content = (string)item.Content;
MessageBox.Show(content);
}
There is however a better way to get the Content of the SelectedItem.
Set the SelectedValuePath property
<ListView x:Name="LV" MouseDoubleClick="LV_MouseDoubleClick"
SelectedValuePath="Content">
<ListViewItem>a listview</ListViewItem>
<ListViewItem>with several</ListViewItem>
<ListViewItem>items</ListViewItem>
</ListView>
and get SelectedValue in code behind:
private void LV_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var content = (string)LV.SelectedValue;
MessageBox.Show(content);
}
As a note, your ListView should be a ListBox, the base class of ListView. You would only use a ListView when you also set its View property.

Related

TextBlock binding to selected DataGrid Element

Does anyone know how to dynamically set the ElementName for textblock.text binding?
I have two Datagrids that have the same information but the second DataGrid is just a filter of the same datasource, but what I want is to bind text of a textblock to the selected item depending if the item was clicked in the main datagrid or the secondary datagrid.
I have the below code to bind the textblock to one datagrid but I would also like the same to happen if the user clicked an item in the secondDataGrid.
Is this possible?
<TextBlock Margin="29,0" Text="{Binding SelectedItem.Name, ElementName=MainDataGrid}"
It is possible, though I don't think this is the proper solution.
You can handle one of the DataGrids' events in the code-behind, where in the handler you can write the following code:
BindingOperations.SetBinding(textBlock, TextBlock.TextProperty,
new Binding("SelectedItem.Name")
{
ElementName = "DataGrid1"
});
Basically you reset the Binding on the TextBlock's Text property with this code, where:
textBlock is the name of your TextBlock;
with TextBlock.TextProperty you define that you want to work with the Text property on the TextBlock;
The third paramter is the new Binding itself. The constructor takes the Path of the Binding and then in the "body" I set the ElementName.
If DataGrid1 fires the event you set the ElementName to that DataGrid's name, if DataGrid2 fires the event then you set the ElementName to the second DataGrid's name.
SelectionChanged can be a good event to handle on both DataGrid, but if you want the TextBlock to update when you selected and element in the first then select another one in the second and then click back to the first element to update then you need to handle the GotFocus event as well.
Play a little bit with it and you will see what I mean.
My working example:
private void SetBindingOnTextBlock(string elementName)
{
BindingOperations.SetBinding(textBlock, TextBlock.TextProperty, new Binding("SelectedItem.Name")
{
ElementName = elementName
});
}
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SetBindingOnTextBlock("DataGrid1");
}
private void DataGrid_SelectionChanged_1(object sender, SelectionChangedEventArgs e)
{
SetBindingOnTextBlock("DataGrid2");
}
private void DataGrid1_GotFocus(object sender, RoutedEventArgs e)
{
SetBindingOnTextBlock("DataGrid1");
}
private void DataGrid2_GotFocus(object sender, RoutedEventArgs e)
{
SetBindingOnTextBlock("DataGrid2");
}
UPDATE 1:
Set
IsSynchronizedWithCurrentItem="True"
on the DataGrids and it may solve your problem if ItemsSources are the same. (Not sure if this is what #dkozl meant) Originally I assumed that they are different.

Binding Button.IsEnabled to position of current in CollectionView

I am trying to bind the IsEnabled property of a button to properties of the window's CollectionViewSource. I am doing this to implement First/Previous/Next/Last buttons and want the First and Previous to be disabled when the view is on the first item etc.
I have the collection view source set up, UI controls binding to it correctly, with access to its view in code so the click event handlers work fine in navigating through the view.
<CollectionViewSource x:Key="cvMain" />
The DockPanel is the root element of the window
<DockPanel DataContext="{StaticResource cvMain}">
FoJobs is an observable collection, cvJobs is a CollectionView that I use in the button's click handler
private void Window_Loaded(object sender, RoutedEventArgs e) {
((CollectionViewSource)Resources["cvMain"]).Source = FoJobs;
cvJobs = (CollectionView)((CollectionViewSource)Resources["cvMain"]).View;
}
I have tried this but get a binding error "BindingExpression path error: '' property not found on 'object' ''ListCollectionView'"
<Button Name="cbFirst" Click="cbMove_Click" IsEnabled="{Binding Source={StaticResource cvMain}, Converter={StaticResource CurrPos2BoolConverter}}" />
I am trying to do with a converter first but figure a style with triggers would be more efficient, but cant get access to the collection view. Even though the underlying datacontext is set to a collection view source, the binding is passed to the converter as the view's source (if I dont explicity set the binding's Source, as above), which has no currency properties (CurrentPosition, Count etc).
Any help would be greatly appreciated.
Why don't you use a RoutedCommand for this(even if you don't use MVVM that is)?
say something like:
<Button x:Name="nextButton"
Command="{x:Static local:MainWindow.nextButtonCommand}"
Content="Next Button" />
and in your code-behind:
public static RoutedCommand nextButtonCommand = new RoutedCommand();
public MainWindow() {
InitializeComponent();
CommandBinding customCommandBinding = new CommandBinding(
nextButtonCommand, ExecuteNextButton, CanExecuteNextButton);
nextButton.CommandBindings.Add(customCommandBinding); // You can attach it to a top level element if you wish say the window itself
}
private void CanExecuteNextButton(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = /* Set to true or false based on if you want button enabled or not */
}
private void ExecuteNextButton(object sender, ExecutedRoutedEventArgs e) {
/* Move code from your next button click handler in here */
}
You can also apply one of the suggestions from Explicitly raise CanExecuteChanged() to manually re-evaluate Button.isEnabled state.
This way your encapsulating logic relating to the button in one area.

WPF: Get object visualized by (hierarchical) data template

I've got a TreeView with a couple of items in it. The items are visualized by a simple hierarchical data template, like this:
<HierarchicalDataTemplate x:Key="instanceTemplate">
<CheckBox Checked="InstanceCheckChanged" Unchecked="InstanceCheckChanged">
<Label>Hello World!</Label>
</CheckBox>
</HierarchicalDataTemplate>
As you can see I've added an event handler, here's the code behind:
private void InstanceCheckChanged(object sender, RoutedEventArgs e)
{
CheckBox checkBox = (CheckBox)sender;
}
In this event handler, the sender of the event is obviously the check box itself, however the checkbox is actually visualizing my normal object. My question is, how do I get the object that the checkbox visualized? Preferably I would like to have a method with a signature like this:
public MyObject GetMyObject(UIElement sender);
Is this possible in WPF, or is there a clean way to store some metadata so that I know which MyObject was checked?
Your CheckBox's DataContext will be the object that it's representing:
var myObject = ((CheckBox)sender).DataContext as MyObject;

Scroll the scrollviewer to top through viewmodel

I am using the ScrollViewer with the MVVM pattern, and a list of items is wrapped by the ScrollViewer, such as
<ScrollViewer>
<ListView>
<ListView.View>
<GridView>
<GridViewColumn
Header = "Name"
DisplayMemberBinding="{Binding Path=Name}"
/>
</GridView>
</ListView.View>
</ListView>
</ScrollViewer>
The items of the listview are bound to a collection of objects in the viewmodel. I want the scrollviewer to scroll to the top whenever a item is added or removed from the collection.
I need the viewmodel to trigger the event, rather than using the ScrollToTop() method in the code-behind of the view.
IMHO, the clearest way to do this is using a "Behavior" via an AttachedProperty. An AttachedProperty is a mechanism to extend existing controls functionality.
First, create a class to hold the AtachedProperty, for instance:
public class ScrollViewerBehavior
{
public static bool GetAutoScrollToTop(DependencyObject obj)
{
return (bool)obj.GetValue(AutoScrollToTopProperty);
}
public static void SetAutoScrollToTop(DependencyObject obj, bool value)
{
obj.SetValue(AutoScrollToTopProperty, value);
}
public static readonly DependencyProperty AutoScrollToTopProperty =
DependencyProperty.RegisterAttached("AutoScrollToTop", typeof(bool), typeof(ScrollViewerBehavior), new PropertyMetadata(false, (o, e) =>
{
var scrollViewer = o as ScrollViewer;
if (scrollViewer == null)
{
return;
}
if ((bool)e.NewValue)
{
scrollViewer.ScrollToTop();
SetAutoScrollToTop(o, false);
}
}));
}
This attached property allows a ScrollViewer having "magically" a new property of type Boolean, acting like a DependencyProperty in your XAML. If you bind this property to a standard property in your ViewModel, for instance:
private bool _reset;
public bool Reset
{
get { return _reset; }
set
{
_reset = value;
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Reset"));
}
}
(again, the name is up to you) and then you set this Reset property to true, your ScrollViewer will scroll to top.
I have named the AtachedPropertyas AutoScrollToTop, but the name is not important for this purpose.
The XAML will be something like:
<ScrollViewer my:ScrollViewerBehavior.AutoScrollToTop="{Binding Reset, Mode=TwoWay}">
<ListView>
<ListView.View>
<GridView>
<GridViewColumn
Header = "Name"
DisplayMemberBinding="{Binding Path=Name}"
/>
</GridView>
</ListView.View>
</ListView>
</ScrollViewer>
Note: my is the namespace where your ScrollViewerBehavior class lives. For example: xmlns:my="clr-namespace:MyApp.Behaviors"
Finally, the only thing you have to do in your ViewModel is to set Reset = true when you like, in your case, when you add or remove an element from the collection.
Create a new ListView control which extend Listview and use this new one instead
public class ScrollListView : ListView
{
protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.OldItems.Count > 0)
this.ScrollIntoView(e.OldItems[e.OldStartingIndex]);
base.OnItemsChanged(e);
}
}
I have also faced a similar scenario where I needed to assign ScrollViewer's HorizontalOffset and VerticalOffset programmatically. I am afraid there is no direct binding mechanism for this. What I did was a way around (believe me, I still do not like the approach I followed, but I did not find any other option). Here is what I suggest:
Hook the ScrollViewer's Loaded event, cast the sender object to ScrollViewer and assign it to a property in DataContext (Means you need to keep a ScrollViewer propery in DataContext which will hold the reference of ScrollViewer in the UI). Hook up ObservableCollection's CollectionChanged events in ViewModel and using the ScrollViewer property, you can call methods like ScrollToTop() etc.
This is just a way around. I am still looking for better solution.
The simplest correct way to do this in MVVM is by creating an event in your viewmodel and subscribing to it from your view. And then, in the event handler, call ScrollToTop.
You fire the event from your viewmodel every time your collection is modified, for instance, and then it's up to the view to react to that event and scroll the list to the top.
Even if this involves some code-behind and demands that the view knows part of its viewmodel, it doesn't violate the MVVM pattern, unlike other workarounds.
public interface IMyViewModel
{
event EventHandler MyCollectionChanged;
}
public class MyViewModel : IMyViewModel
{
public event EventHandler MyCollectionChanged;
// More viewmodel related stuff
protected virtual void OnMyCollectionChanged(EventArgs e)
{
if (MyCollectionChanged != null)
MyCollectionChanged(this, e);
}
}
public class MyWindow : Window
{
public MyWindow(IMyViewModel viewModel)
{
this.DataContext = viewModel;
InitializeComponent();
(this.DataContext as IViewModel).MyCollectionChanged+= MyCollectionChangedEventHandler;
}
private void MyCollectionChangedEventHandler(object sender, EventArgs e)
{
// Do view related stuff
scrollViewer.ScrollToTop();
}
}
EDIT: But it can be refined a lot more, of course. If you want to avoid using code-behind, look for DataEventTriggers. If you don't mind about code-behind but are concerned about memory leaks, look for weak events.
And finally, since the logic you want is 100% view-related (have the ListView scroll every time an item is added or removed to it), you could also implement it as a Behavior / attached property, or extending the ListView. That could get a tad more convoluted, but I encourage you to give those options some thought.

Set the focus on a textbox in xaml wpf

Despite some posts on this forum and others i cannot find something that tells me how to set the focus on a TextBox.
I have a userControl with many labels and textBoxes. When the form is loaded I want the a particular textBox to have the focus.
I have set the tabIndex but that didn't seem to work.
Any suggestions?
You can use the FocusManager.FocusedElement attached property for this purpose. Here's a piece of code that set the focus to TxtB by default.
<StackPanel Orientation="Vertical" FocusManager.FocusedElement="{Binding ElementName=TxtB}">
<TextBox x:Name="TxtA" Text="A" />
<TextBox x:Name="TxtB" Text="B" />
</StackPanel>
You can also use TxtB.Focus() in your code-behind if you don't want to do this in XAML.
You can apply this property directly on the TextBox :
<TextBox Text="{Binding MyText}" FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"/>
I am new to using WPF and reading through the above examples I had a similar experience trying set the focus to a textbox using the xaml code examples given, i.e. all the examples above didn't work.
What I found was I had to place the FocusManager.FocusElement in the page element. I assume this would probably work as well if you used a Window as the parent element. Anyway, here is the code that worked for me.
<Page x:Class="NameOfYourClass"
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"
mc:Ignorable="d"
Title="Title"
Height="720"
Width="915"
Background="white"
Loaded="pgLoaded"
FocusManager.FocusedElement="{Binding ElementName=NameOfYourTextBox}">
<!-- Create child elements here. -->
</Page>
I have a TextBox inside a Grid inside a DataTemplate which I want to have keyboard focus when it becomes visible. I also found that
<DataTemplate x:Key="DistanceView" DataType="{x:Type vm:ROI}">
<Grid FocusManager.FocusedElement="{Binding ElementName=tbDistance}">
<TextBox x:Name="tbDistance" Grid.Column="1" Grid.Row="1" VerticalAlignment="Bottom"/>
</Grid>
</DataTemplate>
did not work for me.
However when I call Focus() in the parent ContentControl
private void ContentControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((sender as ContentControl).IsVisible)
{
(sender as ContentControl).Focus();
}
}
it starts to work and the caret is visible in the TextBox. I think the FocusScope has to be given focus for the FocusManager.FocusedElement property to have any effect.
Jerry
From experimenting around, the xaml solution
FocusManager.FocusedElement="{Binding ElementName=yourElement}"
seems to work best when you place it in the highest element in the window hierarchy (usually Window, or the Grid you place everything else in)
Usage:
local:FocusManager.FocusOnLoad="True"
public class FocusManager
{
public static readonly DependencyProperty FocusOnLoad = DependencyProperty.RegisterAttached(
"FocusOnLoad",
typeof(bool),
typeof(FocusManager),
new UIPropertyMetadata(false, new PropertyChangedCallback(OnValueChanged))
);
private static void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (!(sender is Control control))
return;
if ((bool) e.NewValue == false)
return;
control.Loaded += (s, e) => control.Focus();
}
public static bool GetFocusOnLoad(DependencyObject d) => (bool) d.GetValue(FocusOnLoad);
public static void SetFocusOnLoad(DependencyObject d, bool value) => d.SetValue(FocusOnLoad, value);
}
FocusManager was not in intellisense and this confused me a bit. I just typed the entire attribute and it worked.
FocusManager.FocusedElement="{Binding ElementName=MyTextBox}"
Microsoft Visual Studio Enterprise 2015 version 14.0.23107.0/C#/WPF
For completeness, there is also a way to handle this from code behind (e.g. in the case of controls that, for whatever reason, are created dynamically and don't exist in XAML). Attach a handler to the window's Loaded event and then use the ".Focus()" method of the control you want. Bare-bones example below.
public class MyWindow
{
private VisualCollection controls;
private TextBox textBox;
// constructor
public MyWindow()
{
controls = new VisualCollection(this);
textBox = new TextBox();
controls.Add(textBox);
Loaded += window_Loaded;
}
private void window_Loaded(object sender, RoutedEventArgs e)
{
textBox.Focus();
}
}
bind the element you want to point the focus in as
FocusManager.FocusedElement= "{Binding ElementName= Comobox1}"
in grid or groupbox etc
Further to my comment on Feb 04 '22, I solved it this way:
In the UserControl definitionin the XAML add a Loaded event handler. (pressing tab after Loaded= will automatically add an event handler to the code behind)
Then edit the event handler in the code behind:
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
expressionTextBox.Focus();
}
I'm hoping that WPF is clever enough to handle th unhooking of the evnt at some point, allowing the class to be garbage collected and not give rise to memory leaks, but I don't know. I'd be interested in any comments on that.

Resources