Bind ListPicker in MVVM (Selection_Changed) - c#

I am trying to attach a command to the event Selection_Changed of the ListPicker
I have the following code for the listpicker in my xaml :
<toolkit:ListPicker
x:name="picker" ItemsSource="{Binding Sentences}"
SelectionChanged="{Binding PickerCommand, Mode=TwoWay}" >
<toolkit:ListPicker.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding }"/>
</DataTemplate>
</toolkit:ListPicker.ItemTemplate>
</toolkit:ListPicker>
Sentences is just a list of string in my ViewModel, how can I create a simple Commad for example that will just show a MessageBox with the current selected string in the ListPicker ?
Something similar to this function that i wrote in the code Behind :
private void PickerCommand(object sender, SelectionChangedEventArgs e)
{
if (piker.SelectedItem != null)
{
MessageBox.Show(piker.SelectedItem.ToString());
}
}
EDIT :
I just created this simple function :
public void Test()
{
MessageBox.Show("Test!");
}
passed it :
PickerCommand = new RelayCommand<SelectionChangedEventArgs>(Test);
But I have the error saying that the argument that I passed is invalid, why is that ?

You need to do this:
<toolkit:ListPicker x:name="picker" ItemsSource="{Binding Sentences}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<command:EventToCommand Command="{Binding PickerCommand, Mode=OneWay}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<toolkit:ListPicker.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding }"/>
</DataTemplate>
</toolkit:ListPicker.ItemTemplate>
and then
public class MYViewModel :ViewModelBase
{
public MyViewModel()
{
PickerCommand = new RelayCommand<object>(ActionForPickerCommand);
}
public ICommand PickerCommand {get;set;}
}
Use MVVM Light, that has some classes that help in it.

Related

How to execute command on button in WPF?

I need to have a button in each ListViewItem. I've created the Button in DataTemplate, bound the command and it doesn't get executed when I press the button. It just doesn't being called.
I was referring to different tutorials and questions like
WPF Button doesn't execute Command or How to bind WPF button to a command in ViewModelBase? and created a RelayCommand class, which implements ICommand.
Actually, I need to call the action with the parameter, but I can't even get it to work without parameters, so I'm planning to get to it next. Everything else is bound perfectly and works like a charm.
View
<Page.Resources>
<CollectionViewSource x:Key='src'
Source="{Binding TimesheetEntries}"
>
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Date" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Page.Resources>
<Page.DataContext>
<ViewModels:TimesheetViewModel/>
</Page.DataContext>
<ListView
x:Name="TimesheetEntriesListView"
Margin="10"
Grid.Row="1"
Grid.ColumnSpan="2"
ItemsSource="{Binding Source={StaticResource src}}"
SelectedItem="{Binding SelectedEntry, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="30" Margin="3" IsEnabled="{Binding IsEditable}">
<ComboBox
SelectedValuePath="Key" DisplayMemberPath="Value"
ItemsSource="{Binding EmploymentTypesDictionary, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValue="{Binding SelectedEmployment, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="300"/>
<TextBox
Text="{Binding Hours, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, StringFormat=N2}"
Margin="5,0,0,0"
Height="Auto"
IsEnabled="{Binding HoursAvaliable}"
Width="70"/>
<Button Margin="5,0,10,0"
Content="+"
Command="{Binding AddNewTimesheetEntryCommand}"
CommandParameter="{Binding Path=Name}"
></Button>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel Margin="5,5,5,0" Orientation="Horizontal">
<TextBlock FontSize="14" Text="{Binding Path=Name, StringFormat='{}{0:dd/MM/yyyy, dddd}'}"/>
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
ViewModel
class TimesheetViewModel : BaseViewModel
{
public ICommand AddNewTimesheetEntryCommand
{
get
{
return _AddNewTimesheetEntryCommand ?? new RelayCommand(AddNewTimesheetEntry);
}
}
private ICommand _AddNewTimesheetEntryCommand;
public void AddNewTimesheetEntry(object parameter)
{
//Do stuff
}
public TimesheetViewModel()
{
}
}
RelayCommand
public class RelayCommand : ICommand
{
private Action<object> mAction;
public event EventHandler CanExecuteChanged = (sender, e) => { };
public RelayCommand(Action<object> action)
{
mAction = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
mAction(parameter);
}
}
Your button need to have been different bind, beacuse inside the list-template you do not have access to global DataContext only to local. You need to use relative source to access global DataContext.
Command="{Binding Path=DataContext.AddNewTimesheetEntryCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Page}}}"

DataGrid Binding list string displays length but not the string

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=.}".

Windows Phone - custom control how propagate event to ViewModel

I am creating custom control for menu and I have ListBox in my control. Something like this:
<ListBox x:Name="MenuItemsList"
Grid.Row="1"
ItemsSource="{Binding ProgramList, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<TextBlock Text="{Binding Title}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now when I want to catch Tap event, get properties from class and keep MVVM model. How can I catch this in MainPage.xaml.cs or in MainViewModel?
I have this code in my MainPage.xaml:
<controls:BottomMenu x:Name="BottomMenu" Canvas.Top="{Binding MenuCanvasTop}"
Width="480" Height="400">
</controls:BottomMenu>
I have prepare this code in my MainViewModel:
public RelayCommand<string> GoToSectionCommand
{
get
{
return goToArticleCommand
?? (goToArticleCommand = new RelayCommand<string>(
NavigateToSection));
}
}
But I don't know how can I call it. What's the best way?
Edit:
I tried to extend listbox:
<ListBox x:Name="MenuItemsList"
Grid.Row="1"
ItemsSource="{Binding ProgramList, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<Button Content="{Binding Title}"
Command="{Binding ListButtonClickCommand, Source={RelativeSource Self}}"
CommandParameter="{Binding Url}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
With code-behind:
public ICommand ListButtonClickCommand
{
get { return (ICommand)GetValue(ListButtonClickCommandProperty); }
set { SetValue(ListButtonClickCommandProperty, value); }
}
public static readonly DependencyProperty ListButtonClickCommandProperty =
DependencyProperty.Register("ListButtonClickCommand", typeof(ICommand), typeof(BottomMenu), new PropertyMetadata(null));
public BottomMenu()
{
InitializeComponent();
}
Then in MainPage.xaml:
<controls:BottomMenu x:Name="BottomMenu" Canvas.Top="{Binding MenuCanvasTop}"
Width="480" Height="400"
ListButtonClickCommand="{Binding MenuItemButtonCommand}">
</controls:BottomMenu>
And in MainViewModel:
private ICommand menuItemButtonCommand;
public ICommand MenuItemButtonCommand
{
get
{
return menuItemButtonCommand
?? (menuItemButtonCommand = new RelayCommand(
NavigateToArticleSection));
}
}
For now without luck. It's not working. RelayCommand isn't triggered.
Edit2
I guess the problem is with binding command in custom control but I don't know how to fix it.
You just need to bind to the UserControl -- using "RelativeSource Self" will bind to the Button, which is not what you want. You should be able to use an "ElementName" binding to locate the user control:
<UserControl x:Name="UserControlName" ... >
...
<Button Content="{Binding Title}"
Command="{Binding ElementName=UserControlName,Path=ListButtonClickCommand}"
CommandParameter="{Binding Url}"/>
...
</UserControl>

How to set a on click effect on a textBlock and open a new WPF window?

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

MVVM Light RaisePropertyChanged

I am not able to get the UI to respond to RaisePropertyChanged for some reason. Well stuck.
CurrentClient is the part not firing to the UI. Really strange.
Hopefully someone can help me out. Thanks Scott
public class ClientViewModel : ViewModelBase
{
public RelayCommand<ClientDetail> SelectedClient { get; private set; }
public ICollectionView ClientView { get; private set; }
private readonly IClients _clientsService;
public ClientViewModel(IClients clientsService)
{
_clientsService = clientsService;
SelectedClient = new RelayCommand<ClientDetail>(InSelectedClient);
ClientDetailsList = new ObservableCollection<ClientDetail>(_clientsService.LoadClientList());
//CurrentClient = ClientDetailsList.ToList().FirstOrDefault();
ClientView = CollectionViewSource.GetDefaultView(ClientDetailsList);
}
private void InSelectedClient(ClientDetail obj)
{
CurrentClient = obj as ClientDetail;
}
private ObservableCollection<ClientDetail> _clientDetailsList;
public ObservableCollection<ClientDetail> ClientDetailsList
{
get { return _clientDetailsList; }
set { _clientDetailsList = value;
RaisePropertyChanged("ClientDetailsList"); }
}
private ClientDetail _currentClient;
public ClientDetail CurrentClient
{
get { return _currentClient; }
set
{
if (_currentClient == value)
{ return; }
_currentClient = value;
RaisePropertyChanged("CurrentClient");
}
}
}
My XAML :
<ListBox Name="lbClientList"
ItemsSource="{Binding ClientView}" ItemTemplate="{DynamicResource DTClientList}"
Background="{x:Null}" BorderThickness="0,0,1,0" BorderBrush="#FF434343" >
<ListBox.Resources>
<DataTemplate x:Key="DTClientList">
<StackPanel Orientation="Horizontal">
<TextBlock TextWrapping="Wrap" Text="{Binding CM_CompanyID}" Margin="0,0,5,0" Width="25" Foreground="#FF3590FC"/>
<TextBlock TextWrapping="Wrap" Text="{Binding CM_CompanyName}" Width="150" Foreground="#FF3590FC"/>
<TextBlock TextWrapping="Wrap" Text="{Binding CM_MainContact}" Width="100" Foreground="#FF3590FC"/>
</StackPanel>
</DataTemplate>
</ListBox.Resources>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<Command:EventToCommand Command="{Binding SelectedClient, Mode=OneWay}" CommandParameter="{Binding SelectedItem, ElementName=lbClientList}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.DataContext>
<Binding Path="ClientView" Source="{StaticResource ServiceLocator}" UpdateSourceTrigger="PropertyChanged"/>
</ListBox.DataContext>
</ListBox>
In debug the code is hitting RaiseProertyChange but I am seeing nothing on my UI
<TextBox Text="{Binding CurrentClient.CM_Address1}" TextWrapping="Wrap" VerticalAlignment="Center" Width="210" Background="{DynamicResource MainBackgrouund}" BorderThickness="0,0,0,1" >
Ok not really sure about your code structure since I cannot reproduce your issue with the current info.
However couple things that might be worth you knowing about,
In InSelectedClient(...) the argument is already of type ClientDetail making the as cast redundant inside the function.
Next why are you even using a EventToCommand for this? you hold the ListBox selected item via the EventToCommand in CurrentClient. Rather just bind it directly.
something like:
<ListBox ...
SelectedItem="{Binding CurrentClient}">
...
Finally If you don't have any specific logic in the VM relating to CurrentClient and if there is really no necessity holding on to it, then you can get rid of it by binding your TextBox directly to the ListBox.
something like
<TextBox Text="{Binding ElementName=lbClientList, Path=SelectedItem.CM_Address1}" />
I'm guessing CM_Address1 is a "property" in ClientDetail class
Now all these approaches work fine for me. I'd suggest putting together a reproducible stand-alone example if none of these work for you. I can attach a sample of these methods in a demo if you want(not really sure it's gonna be of much help if your code is structured well different)

Categories