I created a User Control which contains a ListView. I want to add a RelayCommand when the user change the text of a nested TextBox (using MVVM Light) :
<UserControl xmlns:my="clr-namespace:UI.View" x:Class="UI.View.MontureView"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4"
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" >
<ListView ItemsSource="{Binding Path=Monture}" Margin="0,39,0,95" Height="600" HorizontalAlignment="Center">
<ListView.View>
<GridView>
<GridViewColumn Header="Qte" Width="50" >
<GridViewColumn.CellTemplate >
<DataTemplate>
<TextBox Text="{Binding Path=Qte}" Width="40" TextAlignment="Right" Name="a">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged" >
<cmd:EventToCommand Command="{Binding MontureViewModel.MyProperty}" CommandParameter="{Binding ElementName=a}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</UserControl>
In my VM I have : (I removed some parts of the code)
namespace UI.ViewModel
{
public class MontureViewModel : ViewModelBase
{
public MontureViewModel()
{
MyProperty = new RelayCommand<TextBox>(e =>
{
MessageBox.Show("test");
});
}
public RelayCommand<TextBox> MyProperty { get; set; }
}
}
I tryied to add an event on a TextBox which isn't nested into a DataTemplate (outside of the ListView) and it works.
I think that I have to modify the code when I'm into the DataTemplate.
Any idea ?
You need to change the CommandParameter="{Binding ElementName=a}" to
CommandParameter="{Binding SelectedItem, ElementName=a}". This binds the CommandParameter to the selected Item of the GridView and the ElementName sets the control it is bound to within the binding.
Related
My MainView contains a TabControl with an ItemTemplate and a ContentTemplate.
The TabControl's ItemsSource is bound to the Property ObservableCollection<TabViewModel> TabCollection in my MainViewModel.
TabViewModel:
namespace LuxUs.ViewModels
{
public class TabViewModel
{
public string Name { get; set; }
public object VM {get; set;}
public TabViewModel(string name)
{
Name = name;
}
public TabViewModel(string name, object vm)
{
Name = name;
VM = vm;
}
}
}
I want to create the tabs with their tab's header AND content dynamically from the MainViewModel like this...:
MainViewModel:
using System.Collections.ObjectModel;
namespace LuxUs.ViewModels
{
public class MainViewModel : ObservableObject, IPageViewModel
{
public ObservableCollection<TabViewModel> TabCollection { get; set; }
public MainViewModel()
{
TabCollection = new ObservableCollection<TabViewModel>();
TabCollection.Add(new TabViewModel("Dachdefinition", new DachdefinitionViewModel()));
TabCollection.Add(new TabViewModel("Baukörperdefinition"));
TabCollection.Add(new TabViewModel("Fassade"));
TabCollection.Add(new TabViewModel("Raumdefinition"));
TabCollection.Add(new TabViewModel("Treppenloch | Galerieöffnung"));
}
}
}
View:
<UserControl x:Class="LuxUs.Views.MainView"
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"
xmlns:local="clr-namespace:LuxUs.Views"
xmlns:models="clr-namespace:LuxUs.Models"
xmlns:vm="clr-namespace:LuxUs.ViewModels"
mc:Ignorable="d">
<Grid>
<Grid>
<TabControl Style="{DynamicResource TabControlStyle}" ItemsSource="{Binding TabCollection}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<UserControl>
<ContentControl Content="{Binding VM}" />
</UserControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Grid>
</UserControl>
... but the content of each tab won't show. Instead, I get this text. The ViewModel ist the correct one but it should load the view instead of showing this text, of course:
The first tab's ViewModel DachdefinitionViewModel has only an empty constructor:
using System.Collections.ObjectModel;
namespace LuxUs.ViewModels
{
public sealed class DachdefinitionViewModel : ObservableObject
{
public DachdefinitionViewModel()
{
}
}
}
And here is its view Dachdefinition.xaml:
<UserControl x:Class="LuxUs.Views.Dachdefinition"
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"
xmlns:local="clr-namespace:LuxUs.Views"
xmlns:vm="clr-namespace:LuxUs.ViewModels"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:DachdefinitionViewModel></vm:DachdefinitionViewModel>
</UserControl.DataContext>
<Grid Margin="50">
...
...
...
</Grid>
</UserControl>
Is the binding correct here or do I need to bind differently? Why is the view not showing up inside the first tab?
you need to connect view model with correct view via DataTemplate.
DataTemplate provides visual representation for data type which doesn't have it (your view model). If you don't specify DataTemplate, you will get default one: TextBlock with string representation of object (result of ToString() method, default to type name).
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type vm:DachdefinitionViewModel}">
<views:Dachdefinition />
</DataTemplate>
</Grid.Resources>
<TabControl Style="{DynamicResource TabControlStyle}"
ItemsSource="{Binding TabCollection}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<UserControl>
<ContentControl Content="{Binding VM}" />
</UserControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
Your TabControl declaration should look like this:
<TabControl ItemsSource="{Binding TabCollection}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type vm:DachdefinitionViewModel}">
<local:Dachdefinition/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="Content" Value="{Binding VM}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
And the Dachdefinition UserControl must not set its own DataContext property, because the DataContext value is supposed to be inherit from the control's parent element, i.e. the TabItem.
<UserControl x:Class="LuxUs.Views.Dachdefinition" ...>
<!--
do not set UserControl.DataContext here
-->
<Grid Margin="50">
...
</Grid>
</UserControl>
Yes there is a problem in databinding.
at
<ContentControl Content="{Binding VM}" />
This line would just display ToString() value of the object bound to
it. (VM in this case).
Instead you can try using ContentTemplateSelection where you can choose the type of ContentTemplate in run time based on the type of object bound to it.
class TabContentTemplateSelector:DataTemplateSelector
{
public DataTemplate DefaultTemplate { get; set; }
public DataTemplate DachdeTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is TabViewModel tabViewModel)
{
if (tabViewModel.VM != null && tabViewModel.VM is DachdefinitionViewModel)
{
return DachdeTemplate;
}
else
{
return DefaultTemplate;
}
}
return base.SelectTemplate(item, container);
}
}
<DataTemplate x:Key="DachdeTemplate">
</DataTemplate>
<DataTemplate x:Key="SomeOtherTemplate">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<local:TabContentTemplateSelector x:Key="myTabContentTemplateSelector"
DachdeTemplate="{StaticResource DachdeTemplate}"
DefaultTemplate="{StaticResource
SomeOtherTemplate}"
/>
</UserControl.Resources>
<Grid>
<TabControl ItemsSource="{Binding TabCollection}"
ContentTemplateSelector="{StaticResource
myTabContentTemplateSelector}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
https://www.c-sharpcorner.com/UploadFile/41e70f/dynamically-selecting-datatemplate-for-wpf-listview-way-1/
Check this for how to dynamically change template.. In the template you can use any kind of user control.
I have a list of string in my viewmodel which gets populated in the run time with filenames like Events.csv etc. I am binding this list as an itemssource to the DataGrid but it shows the length of the string rather than the string. Please help.
Don't worry about the INotifyPropertyChanged and DATACONTEXT. The DataContext is set correctly to the
DetailedRunInformationViewModel and other pieces of data in this view are populating correctly.
Please see the attached screenshot when I debug.
ViewModel:
public class DetailedRunInformationViewModel : RunRelatedErrors
{
public List<string> AllFilesGeneratedList { get; set; }
public DetailedRunInformationViewModel(int sessionID, RunData runData)
{
DisplayAllFiles();
}
public void DisplayAllFiles()
{
if (_runData != null)
{
if (_runData.CSVDataInDataTableFormatForEachFile != null && _runData.CSVDataInDataTableFormatForEachFile.Count > 0)
{
if (AllFilesGeneratedList == null)
{
AllFilesGeneratedList = new List<string>();
}
foreach(var key in _runData.CSVDataInDataTableFormatForEachFile.Keys)
{
AllFilesGeneratedList.Add(key);
}
}
}
}
}
In the xaml
<DataGrid
x:Name="AllRunFiles"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="{StaticResource AllControlsMargin}"
ItemsSource="{Binding AllFilesGeneratedList}"
AutoGenerateColumns="True">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="SelectionChanged">
<i:InvokeCommandAction
Command="{Binding OpenSelectedFile}"
CommandParameter="{Binding ElementName=AllRunFiles,Path=SelectedValue}"></i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
<DataGrid.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding}"
Style="{StaticResource TextBlockHyperlinkStyle}"></TextBlock>
</DataTemplate>
</DataGrid.ItemTemplate>
</DataGrid>
Your DataGrid needs to have a Columns definitions, and make AutoGenerateColumns="False" so you have more control over how you display.
<DataGrid>
<i:Interaction.Triggers>
...
</i:Interaction.Triggers>
<DataGrid.Columns>
<DataGridTemplateColumn Header="File Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Also I didn't check this, but I think you might need to change your TextBlock Text="{Binding}" to TextBlock Text="{Binding Path=.}".
I have a problem with my List View.
It shows all the elements that I add to the ObservableCollection binded to it, just how it's supposed to work, but when I right-click any of it's elements, the bindings won't work and it won't display the data as I intend it to do.
I created another WPF project to show you the problem more clearly.
Here's my wpf code:
<Window x:Class="WpfApp2.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"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListView x:Name="listViewWithContextMenu" ItemsSource="{Binding Path=CollectionOfThings}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ListView.View>
<GridView>
<GridViewColumn Width="120" Header="Name" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Width="120" Header="Quantity" DisplayMemberBinding="{Binding Quantity}"/>
</GridView>
</ListView.View>
<ListView.ContextMenu>
<ContextMenu>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical" Margin="3">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name: "></TextBlock>
<TextBlock Text="{Binding Name}"></TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Quantity: "></TextBlock>
<TextBlock Text="{Binding Quantity}"></TextBlock>
</StackPanel>
</StackPanel>
</StackPanel>
</ContextMenu>
</ListView.ContextMenu>
</ListView>
</Grid>
and the c# code behind it:
using System.Windows;
using System.Collections.ObjectModel;
namespace WpfApp2
{
public partial class MainWindow : Window
{
public ObservableCollection<DataOfThing> CollectionOfThings = new ObservableCollection<DataOfThing>();
public MainWindow()
{
InitializeComponent();
CollectionOfThings.Add(new DataOfThing() { Name = "Some Name", Quantity = 2 });
CollectionOfThings.Add(new DataOfThing() { Name = "Some Other Name", Quantity = 3 });
CollectionOfThings.Add(new DataOfThing() { Name = "Strange Name", Quantity = 1 });
listViewWithContextMenu.ItemsSource = CollectionOfThings;
}
}
public class DataOfThing
{
public string Name { get; set; }
public int Quantity { get; set; }
}
}
And here's what I get:
What happens is that ContextMenu is not in the same visual tree of your ListView (or any other control). It is completely separated from your Window element tree and that's why it gets lost on binding.
I got a solution that might not be the most beautiful but works :)
Set a ContextMenuOpening event to your ListView:
<ListView x:Name="listViewWithContextMenu" ItemsSource="{Binding Path=CollectionOfThings}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ContextMenuOpening="listViewWithContextMenu_ContextMenuOpening">
And in your codebehind, do:
private void listViewWithContextMenu_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
var list = sender as ListView;
list.ContextMenu.DataContext = list.SelectedItem;
}
This is what I have:
<window>
<Window.Resources>
<DataTemplate DataType="{x:Type viewModel:LogsViewModel}" >
<DataGrid Name="MainLogDataGrid" ItemsSource="{Binding MainLogDataGrid}">
<DataGrid.ContextMenu>
<ContextMenu Name="MainGridContextMenu" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp" >
<i:InvokeCommandAction Command="{Binding OnMainDataGridContextMenuChange}"
CommandParameter="{Binding ElementName=MainGridContextMenu, Path=PlacementTarget}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<MenuItem Header="Insert Row" Name="InsertRowMenuItem" TabIndex="0" />
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
<DataTemplate DataType="{x:Type viewModel:ReportsViewModel}">
<Label FontSize="50">THIS IS WHERE THE REPORTS GO</Label>
</DataTemplate>
</Window.Resources>
</Window>
<ContentControl Content="{Binding CurrentPageViewModel}" />
class LogsViewModel : ObservableObject, IViewModel
{
public RelayCommand<object> OnMainDataGridContextMenuChange { get; private set; }
public LogsViewModel()
{
OnMainDataGridContextMenuChange = new RelayCommand<object>(MainGridContextMenuItemChange);
}
private void MainGridContextMenuItemChange(object menuItem)
{
var item = menuItem as MenuItem;
}
}
The problem is that MainGridContextMenuItemChange method is never reached. Could this have something to do with the context menu not seeing LogsViewModel DataContext? How can I hook tihs up? Thanks.
Try this
<ContextMenu Name="MainGridContextMenu" DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Mode=Self}}">
ContextMenu is not a part of LogicalTree so it cannot inherit the DataContext from Parent .However ContextMenu do have Property PlacementTarget and we can use it to get the DataContext of DataGrid using binding as shown above.
Second option to set the DataContext of ContextMenu is
<ContextMenu Name="MainGridContextMenu" DataContext="{Binding DataContext, Source={x:Reference MainLogDataGrid}}">
Hi I am new to the WPF and I am trying to learn it. So now I want to know how to create an onclick effect on a textblock that is in ListBox. I want to click on any of the items in the listBox and open a new window. I must be doing something wrong but I cant figure out what is it. So far I have the following.
<Grid>
<ItemsControl ItemsSource="{Binding Source={StaticResource cvsRoutes}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Name}" MinHeight="50">
<ListBox>
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="ListBox_MouseLeftButtonDown" />
<TextBlock Text="Something" >
<TextBlock.InputBindings>
<MouseBinding Command="" MouseAction="LeftClick" />
</TextBlock.InputBindings>
</TextBlock>
<TextBlock Text="Something" />
<TextBlock Text="Something" />
<TextBlock Text="Something" />
<TextBlock Text="Something" />
</ListBox>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
The code above is in my XAML file. Do I need something else if so. Where should it be?
This is the MVVM police! ;)
Xaml: Use bindings to ICommand instead and System.Windows.Interactivity & forinstance galasoft mvvm light. I haven't tested the code below, I just wrote it in notepad++.. Ouch I see one thing here now, you are doing this inside a datatemplate & listboxitem... Your TextBlock will look for the command on the LI and not VM, so you need a funky binding here. Check if it works, but you want your click event to execute on the datacontext of the vm, and not the listbox item, so binding must be changed slightly (vacation... =) )
Items in a listbox is wrapped in ListBoxItems and the datacontext is set what the LI is supposed to present, an item in a list.
You might want to change the KeyUp binding below frpm
<command:EventToCommand Command="{Binding KeyUpCommand}" PassEventArgsToCommand="True"/>
To:
<command:EventToCommand Command="{Binding Path=DataContext.KeyUpCommandCommand, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type UserControl}}}" PassEventArgsToCommand="True"/>
To be sure replace UserControl with the name of your control/page/cust ctrl/window.
...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:command="http://www.galasoft.ch/mvvmlight"
xmlns:local="clr-namespace:YOURNAMSPACE"
...
<UserControl.DataContext>
<local:ViewModelListStuff/>
</UserControl.DataContext>
<Grid>
<ItemsControl ItemsSource="{Binding Source={StaticResource cvsRoutes}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Name}" MinHeight="50">
<ListBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseLeftButtonDown">
<command:EventToCommand Command="{Binding PreviewMouseLeftButtonDownCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBlock Text="Something" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<command:EventToCommand Command="{Binding KeyUpCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
<TextBlock Text="Something" />
<TextBlock Text="Something" />
<TextBlock Text="Something" />
<TextBlock Text="Something" />
</ListBox>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Now you are going to need a viewmodel, which you set as datacontext. Here is an example with a simple baseclass (It's nice to expand ViewModelBase provided by galasoft to add functionality.
VM baseclass (simplified):
public class SomeBaseClass : INotifyPropertyChanged
{
// Other common functionality goes here..
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]// Commment out if your don't have R#
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
VM:
public class ViewModelListStuff : SomeBaseClass
{
private string name;
public ICommand PreviewMouseLeftButtonDownCommand { get; set; }
public ICommand KeyUpCommand { get; set; }
public String Name
{
get { return name; }
set
{
if (value == name) return;
name = value;
OnPropertyChanged();
}
}
// I would have exposed your cvsSomething here as a property instead, whatever it is.
public ViewModelListStuff()
{
InitStuff();
}
public void InitStuff()
{
PreviewMouseLeftButtonDownCommand = new RelayCommand<MouseButtonEventArgs>(PreviewMouseLeftButtonDown);
KeyUpCommandnCommand = new RelayCommand<KeyEventArgs>(KeyUp);
}
private void KeyUp(KeyEventArgs e)
{
// Do your stuff here...
}
private void PreviewMouseLeftButtonDown(MouseButtonEventArgs e)
{
// Do your stuff heere
}
}
Hope it helps! Create a breakpoint in the methods which will we invoked by the commands and watch your output and stacktrace of the command methods.
Cheers
Stian