I have a single page with a sidebar (Menu Items), top bar and a big main area. I can get my code to work if I make my sidebar items buttons. In my design I have a Listbox of items that's just text I don't want to use buttons.
Goal: Is to select a menu item in the left side bar and change the view in the main area.
I'm very new to MVVM and there is lots of good content on this site and youtube but I haven't found an example that uses text, it's always with buttons which doesn't help me.
I wish I could do something like this.
<ListBoxItem x:Name="Customer"
Command={Binding UpdateViewCommand}
CommandParameter = "Customer"
</ListBoxItem>
I'll explain what I have.
LeftSideBar.xaml
<Grid>
<ListBox IsSelected="True" SelectionMode="Single">
<ListBoxItem x:Name="Customer">
<StackPanel>
<TextBlock>Customers</TextBlock>
</StackPanel>
</ListBoxItem>
<ListBoxItem x:Name="Security">
<StackPanel>
<TextBlock>Security</TextBlock>
</StackPanel>
</ListBoxItem>
</ListBox>
</Grid>
LeftSideBar.xaml.cs
public partial class LeftSideBar : UserControl
{
public LeftSideBar()
{
InitializeComponent();
}
}
MainWindow.xaml
<Window.Resources>
<DataTemplate x:Name="homeViewTemplate" DataType="{x:Type viewmodels:HomeViewModel}">
<view:HomeView DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="customerViewTemplate" DataType="{x:Type viewmodels:CustomerViewModel}">
<view:CustomerView DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="securityViewTemplate" DataType="{x:Type viewmodels:SecurityViewModel}">
<view:SecurityView DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<controls:TopBar></controls:TopBar>
<controls:LeftSideBar></controls:LeftSideBar>
<ContentControl Grid.Column="1" Content="{Binding SelectedViewModel}"/>
</Grid>
MainWindow.xml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
MainViewModel.cs
internal class MainViewModel : BaseViewModel
{
private BaseViewModel _selectedViewModel = new HomeViewModel();
public BaseViewModel SelectedViewModel
{
get { return _selectedViewModel; }
set { _selectedViewModel = value; }
}
}
UpdateViewCommand.cs
public class UpdateViewCommand : ICommand
{
private MainViewModel ViewModel;
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{throw new NotImplementedException();}
public void Execute(object parameter)
{throw new NotImplementedException();}
}
Here's a quick and dirty example on how you could do that, you will have to adjust it to your needs.
Just wrap your text inside a type with a command... or whatever else you see fit.
Code:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApp1
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
DataContext = new[]
{
new Item {Text = "abcd", Command = new RelayCommand(s => MessageBox.Show("abcd!"))},
new Item {Text = "efgh", Command = new RelayCommand(s => MessageBox.Show("efgh!"))},
new Item {Text = "ijkl", Command = new RelayCommand(s => MessageBox.Show("ijkl!"))}
};
}
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e?.AddedItems?[0] is Item item)
{
item.Command.Execute(null);
}
}
}
public class Item
{
public string Text { get; set; }
public ICommand Command { get; set; }
}
public class RelayCommand : ICommand
{
private readonly Func<object, bool> _canExecute;
private readonly Action<object> _execute;
public RelayCommand(Action<object> execute) : this(execute, _ => true)
{
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object? parameter)
{
return _canExecute(parameter);
}
public void Execute(object? parameter)
{
_execute(parameter);
}
public event EventHandler? CanExecuteChanged;
}
}
XAML:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ListBox SelectionChanged="Selector_OnSelectionChanged" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate DataType="local:Item">
<TextBlock Text="{Binding Text}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Related
I've spent some time trying to solve this problem but couldn't find a solution.
I am trying to bind commands and data inside an user control to my view model. The user control is located inside a window for navigation purposes.
For simplicity I don't want to work with Code-Behind (unless it is unavoidable) and pass all events of the buttons via the ViewModel directly to the controller. Therefore code-behind is unchanged everywhere.
The problem is that any binding I do in the UserControl is ignored.
So the corresponding controller method is never called for the command binding and the data is not displayed in the view for the data binding. And this although the DataContext is set in the controllers.
Interestingly, if I make the view a Window instead of a UserControl and call it initially, everything works.
Does anyone have an idea what the problem could be?
Window.xaml (shortened)
<Window x:Class="Client.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Client.Views"
mc:Ignorable="d">
<Window.Resources>
<local:SubmoduleSelector x:Key="TemplateSelector" />
</Window.Resources>
<Grid>
<StackPanel>
<Button Command="{Binding OpenUserControlCommand}"/>
</StackPanel>
<ContentControl Content="{Binding ActiveViewModel}" ContentTemplateSelector="{StaticResource TemplateSelector}">
<ContentControl.Resources>
<DataTemplate x:Key="userControlTemplate">
<local:UserControl />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Grid>
</Window>
MainWindowViewModel (shortened)
namespace Client.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
private ViewModelBase mActiveViewModel;
public ICommand OpenUserControlCommand { get; set; }
public ViewModelBase ActiveViewModel
{
get { return mActiveViewModel; }
set
{
if (mActiveViewModel == value)
return;
mActiveViewModel = value;
OnPropertyChanged("ActiveViewModel");
}
}
}
}
MainWindowController (shortened)
namespace Client.Controllers
{
public class MainWindowController
{
private readonly MainWindow mView;
private readonly MainWindowViewModel mViewModel;
public MainWindowController(MainWindowViewModel mViewModel, MainWindow mView)
{
this.mViewModel = mViewModel;
this.mView = mView;
this.mView.DataContext = mViewModel;
this.mViewModel.OpenUserControlCommand = new RelayCommand(ExecuteOpenUserControlCommand);
}
private void OpenUserControlCommand(object obj)
{
var userControlController = Container.Resolve<UserControlController>(); // Get Controller instance with dependency injection
mViewModel.ActiveViewModel = userControlController.Initialize();
}
}
}
UserControlSub.xaml (shortened)
<UserControl x:Class="Client.Views.UserControlSub"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Client.Views"
xmlns:viewModels="clr-namespace:Client.ViewModels"
mc:Ignorable="d">
<Grid>
<ListBox ItemsSource="{Binding Models}" SelectedItem="{Binding SelectedModel}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Attr}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel>
<Button Command="{Binding Add}">Kategorie hinzufügen</Button>
</StackPanel>
</Grid>
</UserControl>
UserControlViewModel (shortened)
namespace Client.ViewModels
{
public class UserControlViewModel : ViewModelBase
{
private Data _selectedModel;
public ObservableCollection<Data> Models { get; set; } = new ObservableCollection<Data>();
public Data SelectedModel
{
get => _selectedModel;
set
{
if (value == _selectedModel) return;
_selectedModel= value;
OnPropertyChanged("SelectedModel");
}
}
public ICommand Add { get; set; }
}
}
UserControlController (shortened)
namespace Client.Controllers
{
public class UserControlController
{
private readonly UserControlSub mView;
private readonly UserControlViewModel mViewModel;
public UserControlController(UserControlViewModel mViewModel, UserControlSub mView)
{
this.mViewModel = mViewModel;
this.mView = mView;
this.mView.DataContext = mViewModel;
this.mViewModel.Add = new RelayCommand(ExecuteAddCommand);
}
private void ExecuteAddCommand(object obj)
{
Console.WriteLine("This code gets never called!");
}
public override ViewModelBase Initialize()
{
foreach (var mod in server.GetAll())
{
mViewModel.Models.Add(mod);
}
return mViewModel;
}
}
}
So I have this project where I have two buttons and a ListView. The ListView is separated into it's own UserControl with it's own ViewModel which contains a ObservableCollection.
I'm using a ContentPresenter to display that control because I will be using different views.
Currently, when I'm clicking the Log button, it does in fact add the string to the collection, but the view doesn't update. And it keeps adding more and more everytime I click on it. (I've put a breakpoint inside private void AddItemOne() to inspect it to prove that it adds items.)
Question
Why doesn't my view update when I click the "Log" button even though it's adding items.
It does add the first item if I hardcode it like this.
public LogViewModel()
{
Logs = new ObservableCollection<string>();
Logs.Add("Test");
}
MainWindow.xaml
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Grid.Column="0" Height="25" Content="Log"
Command="{Binding AddItemOneCommand}"/>
<Button Grid.Row="0" Grid.Column="1" Height="25" Content="Other"
Command="{Binding AddItemTwoCommand}"/>
<UserControl Content="{Binding CurrentView}" Grid.Row="1" Grid.ColumnSpan="2"/>
</Grid>
MainViewModel.cs
class MainViewModel
{
public RelayCommand AddItemOneCommand { get; set; }
public RelayCommand AddItemTwoCommand { get; set; }
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
}
}
/* ViewModels */
public LogViewModel LogViewModel { get; set; }
public MainViewModel()
{
AddItemOneCommand = new RelayCommand(o => AddItemOne());
AddItemTwoCommand = new RelayCommand(o => AddItemTwo());
LogViewModel = new LogViewModel();
_currentView = LogViewModel;
}
private void AddItemOne()
{
LogViewModel.Logs.Add("Test");
}
private void AddItemTwo()
{
LogViewModel.Logs.Add("Test");
}
}
LogView.xaml
<UserControl.DataContext>
<local:LogViewModel/>
</UserControl.DataContext>
<Grid>
<ListView ItemsSource="{Binding Logs}" Background="Gray"/>
</Grid>
LogViewModel.cs
class LogViewModel
{
public ObservableCollection<string> Logs { get; set; }
public LogViewModel()
{
Logs = new ObservableCollection<string>();
}
}
Misc
App.xaml
<Application.Resources>
<DataTemplate DataType="{x:Type local:LogViewModel}">
<local:LogView/>
</DataTemplate>
</Application.Resources>
And the RelayCommand
public class RelayCommand : ICommand
{
private Action<object> execute;
private Func<object, bool> canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
}
Remove this from LogView.xaml:
<UserControl.DataContext>
<local:LogViewModel/>
</UserControl.DataContext>
It creates another instance of the LogViewModel instead of using the one that you create in the MainViewModel.
You should also replace the UserControl in MainWindow.xaml with a ContentControl that binds to the CurrentView property:
<ContentControl Content="{Binding CurrentView}" Grid.Row="1" Grid.ColumnSpan="2" />
My idea is to have a window with 2 content controls changing to different views. For example, my app may start with a welcome image in one content control and enter/exit button in other. On enter button click, I want to change the welcome view to some other view and the enter/exit buttons to 3 other buttons.
My current problem is, trying to change the welcome view by clicking the enter button does not seem to work if I put it in a user control and host it in content control. My guess is because I am actually creating a new instance of my MainViewModel instead of referencing the existing one, but I am not entirely sure.
How can I make it so, when I click on my button in BottomPanelTwoButtonsView my HomeView changes to TestView?
There are 4 views in total:
HomeView - the welcome screen
TestView - some other view
BottomPanelTwoButtonsView - enter/exit buttons
BottomPanelThreeButtonsView - some 3 other buttons
Here's the basic code I have:
App.xaml.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow app = new MainWindow();
MainViewModel context = new MainViewModel();
app.DataContext = context;
app.Show();
}
}
MainWindow.xaml
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:localViewModels="clr-namespace:MyApp.ViewModels"
xmlns:localViews="clr-namespace:MyApp.Views"
mc:Ignorable="d"
Name="RootWindow"
WindowStyle="None" ResizeMode="NoResize"
Topmost="True"
WindowStartupLocation="CenterScreen"
WindowState="Maximized"
Background="{StaticResource BackgroundSolidColorBrush}"
ToolBarTray.IsLocked="True"
NumberSubstitution.Substitution="European"
Title="MyApp" Height="1920" Width="1080">
<Window.Resources>
<DataTemplate DataType="{x:Type localViewModels:HomeViewModel}">
<localViews:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type localViewModels:TestViewModel}">
<localViews:TestView />
</DataTemplate>
<DataTemplate DataType="{x:Type localViewModels:BottomPanelTwoButtonsViewModel}">
<localViews:BottomPanelTwoButtons />
</DataTemplate>
<DataTemplate DataType="{x:Type localViewModels:BottomPanelThreeButtonsViewModel}">
<localViews:BottomPanelThreeButtons />
</DataTemplate>
</Window.Resources>
<DockPanel Background="White">
<ContentControl Content="{Binding CurrentBottomPanelViewModel}" DockPanel.Dock="Bottom" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Height="96" Width="1080">
</ContentControl>
<ContentControl Content="{Binding CurrentPageViewModel}" HorizontalAlignment="Stretch" VerticalAlignment="Top" HorizontalContentAlignment="Stretch" Height="1824" Width="1080">
</ContentControl>
</DockPanel>
</Window>
MainViewModel.cs
public class MainViewModel : ObservableObject
{
#region Fields
private ICommand _changePageCommand;
private IPageViewModel _currentPageViewModel;
private IBottomPanelViewModel _currentBottomPanelViewModel;
private ObservableCollection<IPageViewModel> _pageViewModels;
#endregion
public MainViewModel()
{
CurrentPageViewModel = new HomeViewModel();
CurrentBottomPanelViewModel = new BottomPanelTwoButtonsViewModel();
//CurrentBottomPanelViewModel = new BottomPanelThreeButtonsViewModel();
PageViewModels.Add(new ScreeningsScheduleViewModel());
PageViewModels.Add(new TestViewModel());
PageViewModels.Add(CurrentPageViewModel);
}
#region Properties / Commands
public ICommand ChangePageCommand
{
get
{
if (_changePageCommand == null)
{
_changePageCommand = new RelayCommand(
p => ChangeViewModel((IPageViewModel)p),
p => p is IPageViewModel);
}
return _changePageCommand;
}
}
public ObservableCollection<IPageViewModel> PageViewModels
{
get
{
if (_pageViewModels == null)
{
_pageViewModels = new ObservableCollection<IPageViewModel>();
}
return _pageViewModels;
}
set
{
_pageViewModels = value;
OnPropertyChanged("PageViewModels");
}
}
public IPageViewModel CurrentPageViewModel
{
get => _currentPageViewModel;
set
{
if (_currentPageViewModel != value)
{
_currentPageViewModel = value;
OnPropertyChanged("CurrentPageViewModel");
}
}
}
public IBottomPanelViewModel CurrentBottomPanelViewModel
{
get => _currentBottomPanelViewModel;
set
{
if (_currentBottomPanelViewModel != value)
{
_currentBottomPanelViewModel = value;
OnPropertyChanged("CurrentBottomPanelViewModel");
}
}
}
#endregion
#region Methods
private void ChangeViewModel(IPageViewModel viewModel)
{
if (!PageViewModels.Contains(viewModel))
{
PageViewModels.Add(viewModel);
}
CurrentPageViewModel = PageViewModels
.FirstOrDefault(vm => vm == viewModel);
}
#endregion
}
BottomPanelTwoButtonsView.xaml
<UserControl x:Class="MyApp.Views.BottomPanelTwoButtons"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mvm="clr-namespace:MyApp"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="1080">
<UserControl.DataContext>
<mvm:MainViewModel/>
</UserControl.DataContext>
<Grid Height="96" Background="White" Width="1080">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Grid.Column="0"
Content="1"
Command="{Binding DataContext.ChangePageCommand, Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type mvm:MainViewModel}}, Mode=TwoWay}"
CommandParameter="{Binding PageViewModels[2]}"
IsEnabled="True"
Style="{StaticResource RoundCornerButtonCyanFilledBig}"
/>
<Button Grid.Column="1" Style="{StaticResource RoundCornerButtonMagentaFilledBig}"/>
</Grid>
</UserControl>
IPageViewModel.cs
public interface IPageViewModel
{
string Name { get; }
}
ObservableObject.cs
public abstract class ObservableObject : INotifyPropertyChanged
{
[Conditional("DEBUG")]
[DebuggerStepThrough]
public virtual void VerifyPropertyName(string propertyName)
{
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (!ThrowOnInvalidPropertyName)
{
Debug.Fail(msg);
}
else
{
throw new Exception(msg);
}
}
}
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
public virtual void RaisePropertyChanged(string propertyName)
{
VerifyPropertyName(propertyName);
OnPropertyChanged(propertyName);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
RelayCommand.cs
public class RelayCommand : ICommand
{
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute ?? throw new ArgumentNullException("execute");
_canExecute = canExecute;
}
[DebuggerStepThrough]
public bool CanExecute(object parameters)
{
return _canExecute == null ? true : _canExecute(parameters);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameters)
{
_execute(parameters);
}
}
I guess that your problem is within the BottomPanelTwoButtonsView.xaml
There you have the following lines:
<UserControl.DataContext>
<mvm:MainViewModel/>
</UserControl.DataContext>
So you're overwriting your datacontext which is defined in the Window.Resources of the MainWindow
I have a BindingList binded to a DataGrid, but when I add item to that list, UI is not updating.
Here is a minimal version of code that can reproduce this problem:
MainWindow.xaml
<Window x:Class="WpfTestApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfTestApp1"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource x:Key="SessionSource" Source="{Binding Sessions}" />
</Window.Resources>
<Grid>
<DockPanel LastChildFill="True">
<StackPanel DockPanel.Dock="Right">
<Button x:Name="BtnTest" Click="BtnTest_OnClick" Content="Test"></Button>
</StackPanel>
<DataGrid x:Name="DG" DockPanel.Dock="Right" ItemsSource="{Binding Source={StaticResource SessionSource}}">
<DataGrid.Columns>
<DataGridTextColumn x:Name="UserName" Width="Auto" Header="Title" Binding="{Binding Title}"/>
<DataGridTextColumn x:Name="UserAction" Width="Auto" Header="Host" Binding="{Binding Host}"/>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Grid>
</Window>
MainWindow.xaml.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace WpfTestApp1
{
public class Session : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _title;
public string Title { get => _title; set { _title = value; OnPropertyChanged(); } }
private string _host;
public string Host { get => _host; set { _host = value; OnPropertyChanged(); } }
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public partial class MainWindow : Window
{
public BindingList<Session> Sessions { get; set; }
public MainWindow()
{
InitializeComponent();
Sessions = new BindingList<Session>();
}
private void BtnTest_OnClick(object sender, RoutedEventArgs e)
{
Sessions.Add(new Session(){Title = "test1", Host="test2"});
}
}
}
Tried to implement INotifyPropertyChanged interface in MainWindow class, but seems not working too.
Initialize the Sessions property before calling InitializeComponent:
public partial class MainWindow : Window
{
public BindingList<Session> Sessions { get; } = new BindingList<Session>();
public MainWindow()
{
InitializeComponent();
}
private void BtnTest_OnClick(object sender, RoutedEventArgs e)
{
Sessions.Add(new Session { Title = "test1", Host = "test2" });
}
}
In WPF it isn't necessary to use BindingList, especially not when the element type implements INotifyPropertyChanged. ObservableCollection is more common:
public ObservableCollection<Session> Sessions { get; }
= new ObservableCollection<Session>();
I have some WPF code that looks like this
C#
namespace ItemEventTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = this;
MyItems = new ObservableCollection<MyItem>();
MyItems.Add(new MyItem { Name = "Otto" });
MyItems.Add(new MyItem { Name = "Dag" });
MyItems.Add(new MyItem { Name = "Karin" });
InitializeComponent();
}
public ObservableCollection<MyItem> MyItems { get; set; }
}
public class MyItem :INotifyPropertyChanged
{
private string m_name;
public string Name
{
get { return m_name; }
set
{
m_name = value;
OnPropertyChanged();
}
}
public void WhenMouseMove()
{
//Do stuff
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Xaml
<Window x:Class="ItemEventTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ItemEventTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox ItemsSource="{Binding MyItems}">
<ListBox.ItemTemplate>
<DataTemplate DataType="local:MyItem">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
What I now want to do is when the mouse moves over an item is to call WhenMouseMove on the object that is the source of that item.
I would like to have the function called directly on the object and not by first going through MainWindow or some view model connected to MainWindow. It feels like it should be possible because the data is bound that way but I haven't managed to find a description of how to do it.
If you are seeking solution in MVVM pattern
Edit: Add a reference to System.Windows.Interactivity dll (which is system defined if Blend or VS2015 installed)
then add following namespace xmlns:i="schemas.microsoft.com/expression/2010/interactivity"
// xaml file
<Grid>
<ListBox ItemsSource="{Binding MyItems}">
<ListBox.ItemTemplate>
<DataTemplate DataType="local:MyItem">
<TextBlock Text="{Binding Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding MouseHoveredItemChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
// viewmodel (MyItem class)
public void WhenMouseMove()
{
//Do stuff
Console.WriteLine(Name);
}
private RelayCommand _MouseHoveredItemChangedCommand;
public RelayCommand MouseHoveredItemChangedCommand
{
get
{
if (_MouseHoveredItemChangedCommand == null)
{
_MouseHoveredItemChangedCommand = new RelayCommand(WhenMouseMove);
}
return _MouseHoveredItemChangedCommand;
}
}
// RelayCommand class
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private Action methodToExecute;
private Func<bool> canExecuteEvaluator;
public RelayCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
{
this.methodToExecute = methodToExecute;
this.canExecuteEvaluator = canExecuteEvaluator;
}
public RelayCommand(Action methodToExecute)
: this(methodToExecute, null)
{
}
public bool CanExecute(object parameter)
{
if (this.canExecuteEvaluator == null)
{
return true;
}
else
{
bool result = this.canExecuteEvaluator.Invoke();
return result;
}
}
public void Execute(object parameter)
{
this.methodToExecute.Invoke();
}
}