WPF, how do I trigger ListBox.ScrollIntoView using MVVM pattern? - c#

I have a WPF app where the content of a ListBox is updated when the user presses a button. My initial problem was refocusing the ListBox to a specific SelectedIndex value which is binded to an ActiveItem property in my ViewModel. I was able to solve this issue with the following code:
XAML:
<ListBox ItemsSource="{Binding ListOfItems}" SelectedIndex="{Binding ActiveItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsSynchronizedWithCurrentItem="True" SelectionChanged="ListBox_SelectionChanged" x:Name="ListBoxSelector">
Code-behind:
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBoxSelector.ScrollIntoView(ListBoxSelector.SelectedItem);
}
However, I currently have the above code-behind in the MainWindow.xaml.cs file instead of my ViewModel. My question is how do I move this code to the ViewModel so that I can stick to the MVVM pattern? I can't quite figure out how to properly address the ScrollIntoView property of the ListBox from the ViewModel.

You can force the selected item to scroll into view using a Behavior class.
public class perListBoxHelper : Behavior<ListBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
}
protected override void OnDetaching()
{
AssociatedObject.SelectionChanged -= AssociatedObject_SelectionChanged;
base.OnDetaching();
}
private static void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listBox = sender as ListBox;
if (listBox?.SelectedItem == null)
return;
Action action = () =>
{
listBox.UpdateLayout();
if (listBox.SelectedItem != null)
listBox.ScrollIntoView(listBox.SelectedItem);
};
listBox.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
}
}
More details on my blog post.
Also, personally I'd bind to SelectedItem of the ListBox rather than SelectedIndex, and handle any processing on item selection in that property setter, rather than using an event handler.

The MVVM pattern doesn't preclude the use of code behind. In fact, it can't because there are cases where code behind is the right thing to do. A view model is a model, it is not a view replacement. Another way to think about it is that the view model contains the what and the view contains the how. At least, this is my interpretation of the MVVM pattern.
In your case, the what are the list of items and the current item. The how is the ListBox itself and its default behavior. It seems to me that scrolling the selected item into view is an additional behavior, and therefore should be kept in the view. You're not violating the MVVM pattern because you're keeping the what in the view model and the how in the view.

I'm not sure if my solution is MVVM pattern.But to such problem,it can resolve the problem.
Here is what I will do: If Button is pressed ,it will trigger a command to call method in ViewModel.When ViewModel finish it's job, viewModel throw an custom event(where include item index where listbox should scroll into). And before this happen, when View is Loaded,View's Code-behind should listen to it's ViewModel through View's DataContext, and do scrollIntoView.
As I said,I'm not sure if it's MVVM way,But I thought it's acceptable.

There isn't the universal solution for every request regarding this and as others have mentioned MVVM doesn't mean that there is no code behind but no unnecessary code behind.
However in your particular request there is the solution if you want no code behind - make a class that inherits from ListView and handles request as you would like it to be handled and then use it in your XAML.

Related

In WPF and by using Prism, how to use the MouseButtonEventArgs as a parameter of the command for a window?

I want to move a borderless windows, and before I adopt the Prism framework, I'd do it as follows:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MouseDown += Window_MouseDown;
}
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
DragMove();
}
}
}
but I don't know how to implement this while using Prism in MainWindowViewModel.cs (the view model), it seems the InvokeCommandAction can pass the event argument for an element like button or so, but it doesn't work for a window in my case.
Can anyone help me on this? Thanks in advance.
I don't know how to implement this while using Prism
I don't know what this is supposed to be exactly, but I'll assume it's how to call the view model when some event on the view happens:
The cleanest option is an attached behavior. Alternatively, you can use an InvokeCommandAction variant like DevExpress' EventToCommand that supports forwarding the parameter.
Well, finally I got the event fired, but it seems this approach contradict the concept of MVVM pattern, which requires that the view model should not know anything about nor has any dependency upon any view elements.
In my case, I can add the Interaction.Triggers to the Window and pass the MouseButton to the view model by usng Prism’s InvokeCommandAction, as follows:
<Window
xmlns:prism="http://prismlibrary.com/"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
/>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<prism:InvokeCommandAction Command="{Binding WindowMouseCommand}" TriggerParameterPath="ChangedButton" />
</i:EventTrigger>
</i:Interaction.Triggers>
and in the view model:
public DelegateCommand<object> WindowMouseCommand { get; private set; }
...
WindowMouseCommand = new DelegateCommand<object>(WindowMouse);
...
private void WindowMouse(object mouseButton)
{
if (mouseButton is MouseButton m)
{
if (m == MouseButton.Left)
{
// DragMove();
}
}
}
if I want to call the .DragMove(), I need a reference of the Window... it's not the correct implementation of MVVM pattern.
So what is the best approach/practice for that?
I was suddenly enlightened when I saw this answer:
https://stackoverflow.com/a/3426183/10958770
yes, moving a window is a pure UI logic, therefore it's not necessary to move it to a ViewModel... so let me just leave it in the view.

C# referencing a Grid in WPF to change properties

Hello im new to making apps with WPF and XAML in Visual Studio. So I have a grid I want to change its properties in the code.
My Grid's properties:
<Grid HorizontalAlignment="Left"
Height="603"
Margin="0,51,0,0"
x:Name="usersPan"
VerticalAlignment="Top"
Width="1286">
How I have been trying to change its properties
this.usersPan.SetValue(Grid.WidthProperty, PAN_SIZE);
usersPan.SetValue(Grid.WidthProperty, PAN_SIZE);
usersPan.Width = 0;
usersPan.Visibility = Visibility.Collapsed;
When I try to do that^ it says null reference for userPan
Thanks
Noooooooo, Don't ever do that. Make a ViewModel that is bound to the Grid's Width property, and then just change the value.
My suspicion is that you do not need this at all. Have a look into containers, and how to position them.
In all of this years, there have been rare occasions I needed to do that and I suspect you do not need to. Tell me what you are doing.
EDIT:
You have a VM which needs to implement the NotifyPropertyChanged interface (I won't do that here, there are plenty of examples on hoew to do that)
public class MainVM
{
public ObservableCollection<TabVM> TabsVms {get;set;}
public int SelectedIndex {get;set}
}
bound to the control
<TabControl DataContext={TabsVMs} SelectedIndex="{Binding SelectedIndex}">
...
</TabControl>
And in runtime you create a couple of Tabs
var TabsVMs = new ObservableCollection<TabVM>();
TabsVMs.add(new TabVM());
TabsVMs.add(new TabVM());
TabsVMs.add(new TabVM());
Then in runtime you change the value of the index.
MainVm.SelectedIndex = 1
and the the coresponding tab will become selected.
EDIT:
I can also recommend you to use Fody for the MVVM notification.
Also, when it comes to bindings, I can recommend you to use WPF inspector. a handy little tool
The best way to write WPF programs is to use the MVVM (Model-View-View Model) design pattern. There are two (2) ideas behind MVVM:
Write as little code as possible in the view's code-behind and put all of the logic in the View Model object, using WPF's data binding feature to connect the properties of the View Model object to the view's controls.
Separate the logic from the display so you can replace the view with some other construct without having to change the logic.
MVVM is a huge topic on its own. There are lots of articles about it, and frameworks that you can use to build your program. Check out MVVM Light, for example.
Don't know exactly why Grid is invisible in code-behind, but You can access it's properties using events (but don't think it is perfect solution).
For example add to your grid event Loaded
<Grid HorizontalAlignment="Left"
Height="603"
Margin="0,51,0,0"
x:Name="usersPan"
VerticalAlignment="Top"
Width="1286"
Loaded="FrameworkElement_OnLoaded">
and then from code-behind you can access grid in next way:
private void FrameworkElement_OnLoaded(object sender, RoutedEventArgs e)
{
var grid = sender as Grid;
if (grid != null)
{
grid.Width = 0;
}
}
Better solution :
Add some boolean property to your ViewModel like public bool IsGridVisible{get;set;}
And bind it to your Grid
<Grid HorizontalAlignment="Left"
Height="603"
Margin="0,51,0,0"
x:Name="usersPan"
VerticalAlignment="Top"
Width="1286"
Visibility="{Binding Path=IsGridVisible, Converter={StaticResource BoolToVis}">
where BoolToVis is converter which converts true to Visible and false to Hidden. You can define it in App.xaml like :
<BooleanToVisibilityConverter x:Key="BoolToVis" />
I was able to do something like this so I can change properties outside of an event.
private Grid userGrid;
private void onUserGridLoaded(object sender, RoutedEventArgs e)
{
userGrid = sender as Grid;
}

Notify ViewModel when View is rendered/instantiated

I have a custom usercontrol (ChartControl) that I use within my WPF app (MainApp) and which I render as follows:
<ContentControl Grid.Row="1" Content="{Binding ChartControl, Mode=OneWay}" />
Upon starting MainApp the following are executed in the given order:
MainApp View
MainApp ViewModel
ChartControl ViewModel
ChartControl View
I instantiate the ChartControl ViewModel from within the constructor of my MainApp ViewModel. The problem is that after instantiating the ChartControl ViewModel I also need to call a method of ChartControl from within MainApp.
The problem I am having is that I need the ChartControl view to be rendered (have its InitializeComponent executed) before I call the method as part of its viewmodel.
I thought one solution could be to notify the view model from the view when it is fully instantiated and set up. Is that a viable solution and if yes how would I do that?
In summary, I need the view to be fully set up before invoking a method of the matching viewmodel. The problem I am having is that in this case the view model is instantiated first and only then is the view rendered.
Any ideas?
Thanks
You can make use of Interactivity triggers to fire Command on your VM on any UI event
You can listen to Loaded event of UserControl like below and bind it to Command on your VM:
<UserControl x:Class="Test.TestView.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
x:Name="myControl" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding ElementName=myControl, Path=OnLoadedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
And sure you will have Command in your VM as
public ICommand OnLoadedCommand { get; private set; }
public MyUserControl()
{
OnLoadedCommand = new DelegateCommand(OnLoaded);
}
public void OnLoaded()
{
}
Another way to hook up the Loaded event, basically rendering the same result as nit's answer, is simply referencing your viewmodel in the constructor of the view and adding an event handler which in turn calls whatever method you need to call, like this:
public MyControl()
{
InitializeComponent();
this.Loaded += (s, e) => { ((MyViewModel)DataContext).MyInitializer(); };
}
If you find the syntax confusing you might want to read up on Anonymous methods and Subscribing to event handlers (using anonymous methods).
I'm using similar solution like Hogler only with reflection (lazy coupled solution). I dont want referencing specific type of my ViewModel (because of generality, interchangeability, etc.).
public MyControl()
{
InitializeComponent();
Loaded += MyControl_Loaded;
}
private void MyControl_Loaded(object sender, RoutedEventArgs e)
{
(DataContext.GetType().GetProperty("LoadedCommand")?.
GetValue(DataContext) as ICommand)?.
Execute(null);
}
ViewModel can (dont have to) contain desired command like property (LoadedCommand in this case). Nothing more.
In an MVVM world I found that when creating a visual item and putting it onto the view (in this case adding to a list) the item wouldn't be in the visual tree until the loaded event fired.
My view model contained the items list in an observable collection which the XAML view would display.
ObservableCollection<MyControl> Items;
I'd add an item to the list, but when I perform an operation that requires it to be in the visual tree and performs visual tree recursion, this couldn't happen immediately after. Instead I had to code something like this:
var newItem = new MyControl();
newItem.Loaded += NewItemLoaded;
Items.Add(new MyControl());
The event handler would then unhook and perform the operation - and at this point is was in the visual tree as required:
private void NewItemLoaded(object sender, RoutedEventArgs e)
{
var item = sender as MyControl;
item.Loaded -= NewItemLoaded;
// now this item is in the visual tree, go ahead and do stuff ...
}

Get item from template in ItemsControl

I have an ItemsControl that is populated with an observable collection of some ViewModel classes, like so:
<ItemsControl ItemsSource="{Binding MyCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate Type="{x:Type local:MyViewModel}">
<Button Content="{Binding ActionName}" Click="ClickHandler"/>
</DataTemplate>
<ItemsControl.ItemTemplate>
</ItemsControl>
Works great, looks great, but I can't seem to figure out how to get the "ClickHandler" to be aware of the class 'MyViewModel' that is represented by the data template. Behold!
private void ClickHandler(object sender, RoutedEventArgs e)
{
// The 'sender' is the button that raised the event. Great!
// Now how do I figure out the class (MyViewModel) instance that goes with this button?
}
OK duh, I almost immediately realized that it is the 'DataContext' of the 'sender'. I am going to leave this up unless the community thinks that this question is just too obvious.
private void ClickHandler(object sender, RoutedEventArgs e)
{
// This is the item that you want. Many assumptions about the types are made, but that is OK.
MyViewModel model = ((sender as FrameworkElement).DataContext as MyViewModel);
}
Your own answer will do the trick in this specific case. Here's another technique which, while much more complicated, will also work on any scenario regardless of complexity:
Starting from sender (which is a Button), use VisualTreeHelper.GetParent until you find a ContentPresenter. This is the type of UIElement that the ItemTemplate you specified is hosted into for each of your items. Let's put that ContentPresenter into the variable cp. (Important: if your ItemsControl were a ListBox, then instead of ContentPresenter we 'd look for a ListBoxItem, etc).
Then, call ItemsControl.ItemContainerGenerator.ItemFromContainer(cp). To do that, you will need to have some reference to the specific ItemsControl but this shouldn't be hard -- you can, for example, give it a Name and use FrameworkElement.FindName from your View itself. The ItemFromContainer method will return your ViewModel.
All of this I learned from the stupidly useful and eye-opening posts of Dr. WPF.

In Silverlight, how to bind ListBox item selection to a Navigate event?

I am writing a windows-phone 7 application. I've got a page with a list of TextBlock(s) contained in a ListBox. The behavior I want is that upon clicking one of those TextBlock(s) the page is redirected to a different one, passing the Text of that TextBlock as an argument.
This is the xaml code: (here I am binding to a collection of strings, and the event MouseLeftButtonDown is attached to each TextBlock).
<ListBox x:Name="List1" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock MouseLeftButtonDown="List1_MouseLeftButtonDown" Text="{Binding}"
FontSize="20"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
But this has been unsuccessful for me. I have tried attaching MouseLeftButtonDown event to either the individual TextBox(es) or to the ListBox. And I have had exceptions raised as soon as I use NavigationService.Navigate(uri). Which event should be attached? Should the event be attached to the individual items or to the list as a whole?
I have found a way to work around this problem by populating ListBox with HyperlinkButton(s). However, I would like to understand why the TextBox approach did not work.
This is my first attempt with Silverlight, so I might be missing something basic here.
There are a few ways to do this but I'll walk you through one of the the simplest (but not the purest from an architectural perspective).
Basically you want to find out when the selection of the ListBox changes. The ListBox raises a SelectionChanged event which can be listened to in the code behind.
<ListBox x:Name="List1" ItemsSource="{Binding}" SelectionChanged="SelectionChangedHandler" SelectionMode="Single" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontSize="20"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Then have a handler something like:
private void SelectionChangedHandler(object sender, SelectionChangedEventArgs e)
{
IList selectedItems = e.AddedItems;
string val = selectedItems.OfType<string>().FirstOrDefault();
NavigationService.Navigate(new Uri(val));
}
One thing you'll need to be aware of is that ListBoxes support multiple selection. For this reason, the event arguments give you back a list of the selected items. For simplicity, all I've done is taken the first value from this list and used that as the navigation value. Notice how I've also set the SlectionMode property of the ListBox to Single which will ensure the user can only select one item.
If I were doing this for real I'd look into creating an TriggerAction tat can be hooked up to an event trigger through xaml which will remove the for code behinds. Take a look at this link if you're interesetd.
In addition to Chris' and James' replies, I'd add that you will also need to clear the listbox selection in the event handler, otherwise the user won't be able to tap the same item twice on the listbox (because the item will already be selected).
Using James' approach, I would change the SelectionChangedHandler() implementation as follows:
private void SelectionChangedHandler(object sender, SelectionChangedEventArgs e)
{
// Avoid entering an infinite loop
if (e.AddedItems.Count == 0)
{
return;
}
IList selectedItems = e.AddedItems;
string val = selectedItems.OfType<string>().FirstOrDefault();
NavigationService.Navigate(new Uri(val));
// Clear the listbox selection
((ListBox)sender).SelectedItem = null;
}
What I would recommend is binding the SelectedItem property of the ListBox to a property in your ViewModel. Then, on the ListBox's SelectedItemChanged event, navigate to to the appropriate URL passing the data key on the QueryString, or upgrade to something like MVVM Light and put the actual SelectedItem object on the message bus for the child window to pick up. I have a sample of this second method on my Skydrive that you can check out.
HTH!
Chris

Categories

Resources