I am following the MVVM pattern. I have a grid with few columns one column having Button in it . Clicking on button i want to open dialog box which is expected to display data related to that particular row in which button was clicked. But the problem is with binding , as i am unable to bind the control with viewmodel.
<Button Command="{Binding Path=ParentRow.DataContext,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}},
UpdateSourceTrigger=PropertyChanged, Mode=Default}"
lib:Event.Binding="Click.[**NameOfViewModelMethod**]" >
</Button>
First things first, if your Button is inside a Grid with a defined DataContext you don't need to set the Path like Path=ParentRow.DataContext.
Your Command binding should be like this:
<Button Command="{Binding YourVMICommand"} />
You have to define a public ICommand in your VM and then bind it to the button.
you don't show all of your code and context but it should work like this
i assume you are in a usercontrol caling parent datacontext ...
(exemple with listview):
<ListView ItemsSource="{Binding listFromDataContext, IsAsync=True}" Margin="3,51,0,10" >
<ListView.ItemTemplate >
<DataTemplate>
<grid>
<Button Command="{Binding DataContext.MyMethode, RelativeSource={RelativeSource AncestorType={x:Type controls:thisUserControl}}}" CommandParameter="{Binding}" />
</grid>
</DataTemplate>
</ListView.ItemTemplate >
</ListView>
then in model
private ICommand _MyMethode;
public ICommand MyMethode
{
get
{
return _MyMethode ?? (_MyMethode = new CommandHandler<MyModel.item>(x => showMessage(x), _canExecute));
}
}
public void showMessage(MyModel.item x)
{
MessageBox.Show(x.Info);
}
Related
I want to open a window on button click from another window and go to specific tab in that window. Let's say new window (tabsWindow) has 3 tabs - tab1, tab2 and tab3 and I have 3 buttons on main window (buttonsWindow) btn1, btn2 and btn3. All three tabs are on one window and all three buttons are on another window. On click of btn1, tab1 should be opened from tabsWindow. On btn2_Click, tab2 should open. I have heard of RoutedCommand but not good in it. Suggest me any other possible or simpler way.
I made a toy sample by using MVVM pattern.
You can see full source in GitHub.
ButtonsWindow.xaml
I used Command and handed over the Button object to the CommandParameter to specify the SelectedIndex of TabControl using the Content of the Button.
<UniformGrid Columns="3">
<Button Margin="50" Content="1" Command="{Binding BtnClick}" CommandParameter="{Binding RelativeSource={RelativeSource Self}}"/>
<Button Margin="50" Content="2" Command="{Binding BtnClick}" CommandParameter="{Binding RelativeSource={RelativeSource Self}}"/>
<Button Margin="50" Content="3" Command="{Binding BtnClick}" CommandParameter="{Binding RelativeSource={RelativeSource Self}}"/>
</UniformGrid>
TabsWindow.xaml
<Grid>
<TabControl x:Name="tab" Width="300" Height="300">
<TabItem Header="Tab1">
<TextBlock TextAlignment="Center" VerticalAlignment="Center" Text="Tab1" FontSize="20" />
</TabItem>
<TabItem Header="Tab2">
<TextBlock TextAlignment="Center" VerticalAlignment="Center" Text="Tab2" FontSize="20" />
</TabItem>
<TabItem Header="Tab3">
<TextBlock TextAlignment="Center" VerticalAlignment="Center" Text="Tab3" FontSize="20" />
</TabItem>
</TabControl>
</Grid>
TabsWindow.xaml.cs
public partial class TabsWindow : Window
{
public TabsWindow()
{
InitializeComponent();
}
internal void SetTab(string content)
{
tab.SelectedIndex = int.Parse(content) - 1;
}
}
MainViewModel.cs
public class MainViewModel
{
private TabsWindow win;
public ICommand BtnClick { get; set; }
public MainViewModel()
{
BtnClick = new RelayCommand<object>(Click);
}
private void Click(object obj)
{
if (obj is Button btn)
{
if (win is null || !win.IsVisible)
{
win = new TabsWindow();
win.Show();
}
win.SetTab(btn.Content.ToString());
}
}
}
When Button is clicked, TabsWindow is activated and moves to a specific tab according to the Content of the Button. Also, if TabsWindow is already activated, tab can be moved without showing a new TabsWindow.
I am having a data grid, one of its cells is a combo box like:
<DataGrid x:Name="Applications" RowStyle="{StaticResource CollapsedRow}" AutoGenerateColumns="false" CanUserAddRows="false" ItemsSource="{Binding Applications}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content='˅' FontSize="9" Name="ExpanderButton" Click="OnGroupChange" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Width="181" Header="Name" Binding="{Binding Name, Mode=OneWay}" />
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.Cabins,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
SelectedValuePath="Id" IsSynchronizedWithCurrentItem="True"
SelectedValue="{Binding Path=DataContext.SelectedCabin,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
mah:TextBoxHelper.Watermark="{Binding Path=DataContext.CabinsWatermark, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
Height="2" Width="300" Margin="10 5 10 10" HorizontalAlignment="Left">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource GuidConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
And as you see in each row there ia a combo box in detail row (expanded row using button), each combo box is binded to one property:
private Guid? selectedCabin;
public override Guid? SelectedCabin
{
get => selectedCabin;
set
{
selectedCabin = value;
if (value.HasValue)
{
Console.WriteLine(value);
}
OnPropertyChanged();
}
Now problem is when i select item in combo box i am getting not single value but couple of them (I suppose there are all values from one combo box I made a selection on), to make sure i double checked with test code behind:
private void ComboBox_OnSelectCabinChanged(object sender, RoutedEventArgs e)
{
var combo = (ComboBox)sender;
if (combo != null && combo.IsDropDownOpen)
{
((ApplicationsViewModel)DataContext).SelectedCabin = (Guid?)sender;
combo.IsDropDownOpen = false;
}
}
And I am getting here and combo box item list and casting exception. What could be the root cause of this and is there a way to bind multiple combo box values to one property, so is i select one it will override another.
It seems like you are binding the SelectedValue of all row details ComboBoxes to the same source property. And you can't cast the sender argument to a Guid?. Try to cast the SelecteedValue property of the ComboBox:
SelectedCabin = (Guid?)combo.SelectedValue;
If you don't want to handle the SelectionChanged event in the view, you could use an interaction trigger that executes a command that sets the source property. Please refer to this blog post for more information about this.
What is required to bind a ListBox selectedItem to my ViewModel?
The view models SelectedClient is always null.
The ClientSelected is successfully called through a command called ClientClickedCommand. But when I try to access the view models SelectedClient in the ClientSelected method its null and throws an exception.
XAML
<ListBox x:Name="lbSlaves" Width="300" Grid.Row="1" ItemsSource="{Binding Slaves}" ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectedItem="{Binding SelectedClient, Mode=TwoWay}"
>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Checked ,Mode=TwoWay}"/>
<Button
Command="{Binding ElementName=MainGrid, Path=DataContext.ClientClickedCommand}"
>
<TextBlock Text="{Binding MachineName, Mode=OneWay}" />
</Button>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
VIEVMODEL (bound to DataContext)
private MyClient _selectedClient;
public MyClient SelectedClient
{
get {
return _selectedClient;
}
set
{
if (value != _selectedClient)
{
_selectedClient = value;
NotifyPropertyChanged("SelectedClient");
}
}
}
public string _infoText;
public string InfoText {
get {
return _infoText;
}
set {
if (value != _infoText)
{
_infoText = value;
NotifyPropertyChanged("InfoText");
}
}
}
private void ClientSelected()
{
var message = " - " + SelectedClient.MachineName + " was clicked";
InfoText += message;
}
ClientClickedCommand = new Command(ClientSelected, ()=> true);
public ICommand ClientClickedCommand
{
get;
set;
}
UPDATE: Im now trying to bind SelectedClient through CommandParameter like this
<ListBox x:Name="lbSlaves" Width="600" Grid.Row="1"
ItemsSource="{Binding Slaves}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel Orientation="Horizontal" Width="150" Height="60">
<CheckBox IsChecked="{Binding Checked ,Mode=TwoWay}"/>
<TextBlock Text="{Binding MachineName, Mode=OneWay}" />
<Button
Content="Do something"
Command="{Binding ElementName=MainGrid, Path=DataContext.ClientClickedCommand}"
CommandParameter="{Binding ElementName=MainGrid, Path=DataContext.SelectedClient, Mode=TwoWay}" />
<Button Content="Do another thing>" />
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The button might swallow the mouse click, but besides that, it's not clear where your SelectedClient property resides. It seems like it's in the MyClient class, whereas it should be in at the same level of Slaves.
Edit:
if you want to keep your own button use CommandParameter somewhat like this:
Command="{Binding ElementName=MainGrid, Path=DataContext.ClientClickedCommand}" CommandParameter="{Binding}"
I'm not sure about the works of new Command (...) but there are commands that take parameters so the next part should look like this:
private void ClientSelected(MyClient client)
{
SelectedClient = client;
var message = " - " + SelectedClient.MachineName + " was clicked";
InfoText += message;
}
Most people have already told you the problem - the button inside your ListViewItem is consuming your click event. When you assign a command to a button, WPF will, in the background, subscribe to the button's click event. Default behavior of handling click event is to set e.Handled = true;, which causes other event arising from this single mouse click to stop working.
It is not too clear whether you have separate use for SelectedClient and the command. If all you want to know is that the user has clicked on that ListViewItem, you can simply not use commands.
public MyClient SelectedClient
{
get
{
return _selectedClient;
}
set
{
if (value != _selectedClient)
{
_selectedClient = value;
ClientSelected();
NotifyPropertyChanged("SelectedClient");
}
}
}
If selecting the ListViewItem has different objective from the button, then you need to consider why you need one button for each ListViewItem. Logically, if all the items need to do something of similar nature, you can put the button outside of the ListView. This way, the button does not mess up your ListView.
hi i have one list box that every row contain one textbox and one button;
with click button that row delete from listbox; this work with mvvm pattern
i use command for this.
this is my xaml:
<DataTemplate x:Key="CategoryTemplate">
<Border Width="400" Margin="5" BorderThickness="1" BorderBrush="SteelBlue" CornerRadius="4">
<StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBlock Width="300" Margin="5" Text="{Binding Path=Name}"></TextBlock>
<Button Name="btnDeleteCategory" Width="50" Margin="5"
Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}" Content="-" />
</StackPanel>
</Border>
</DataTemplate>
<ListBox Grid.Column="0" Grid.Row="0" Name="lstCategory"
ItemTemplate="{StaticResource CategoryTemplate}"
ItemsSource="{Binding Path=GetAllCategories}">
</ListBox>
and in viewmodel class i have this command :
private ObjectButtonCommand<Category> _deleteCommand;
public ObjectButtonCommand<Category> DeleteCommand
{
get
{
return _deleteCommand
?? (_deleteCommand = new ObjectButtonCommand<Category>(
_category =>
{
GetAllCategories.Remove(_category);
}));
}
}
that GetAllCategories is observecollection propert;
and this is my ObjectButtonCommand code:
public class ObjectButtonCommand<T> : ICommand
where T:class
{
private Action<T> WhatToExecute;
public ObjectButtonCommand(Action<T> What)
{
WhatToExecute = What;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
WhatToExecute((T)parameter);
}
}
now every thing is ok and when click button that row delete;
now i want that this process repeat when i select one row of listbox
i try this code :
<ListBox Grid.Column="0" Grid.Row="0" Name="lstCategory" ItemTemplate="{StaticResource CategoryTemplate}" ItemsSource="{Binding Path=GetAllCategories}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding DataContext.DeleteCommand , RelativeSource={RelativeSource AncestorType=ListBox}}" CommandParameter="{Binding Path=SelectedItems,ElementName=lstCategory}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
but i get this error at this code : WhatToExecute((T)parameter);
{"Unable to cast object of type 'System.Windows.Controls.SelectedItemCollection' to type 'Sepand.WPFProject.Model.Model.Category'."}
what should i do?
You pass the selection to the delete command which is a list, hence you cannot use the same command for both cases unless you would wrap the individual items (as passed from the DataTemplate) in a list first.
You should probably define a new command which takes an IList as parameter (type of ListBox.SelectedItems), whose items you then cast to Category and remove individually.
If you just want to delete a single selected item you need to change the binding to SelectedItem and need be able to handle the case of SelectedItem being null in your existing command. e.g. change CanExecute to parameter != null if that is respected by InvokeCommandAction.
Hi Mohammad can you try this:
For your Listbox, make sure your SelectionMode is set to Single. Then change that you refer to SelectedItem in the CommandParameter instead of SelectedItems. Like so:
<ListBox Grid.Column="0" Grid.Row="0" Name="lstCategory" ItemTemplate="{StaticResource CategoryTemplate}" ItemsSource="{Binding Path=GetAllCategories}" SelectionMode="Single">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding DataContext.DeleteCommand , RelativeSource={RelativeSource AncestorType=ListBox}}" CommandParameter="{Binding Path=SelectedItem,ElementName=lstCategory}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
I have a UWP app, using the Prism framework\toolkit. The app has a ListView. Every row composites of various TextBlocks and a ComboBox. Initially, when the grid gets loaded, I want ComboBox to show no item selected but the item source loaded. The user now has to choose any item from the combo box. The ListBox and ComboBox are populated from 2 different ObservableCollection from the ViewModel. The SelectionChanged event does fire for me for the ComboBoxes in the ListBox as I am using Dependency Property but the item which is selected by the user from Combo Box, the properly of Selected Value is not updating in the View Model.
I have a ListView within which I have ComboBox.
I have added the code-snippet from my application below:
I am using Behaviors SDK here as well.
<ListView x:Name="AssignCountryStateGridData" Grid.Row="1"
HorizontalAlignment="Left" ItemsSource="{Binding AssignCCPGridInfo,Mode=TwoWay}" MinWidth="805" MinHeight="480">
<ListView.ItemTemplate>
<DataTemplate>
<Grid MinHeight="25" MinWidth="805">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions >
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<TextBlock FontSize="13" Foreground="White" Grid.Column="0" Text="{Binding CompanyCode}" ToolTipService.ToolTip="{Binding CompanyCode}" TextAlignment="Center"/>
<TextBlock FontSize="13" Foreground="White" Grid.Column="1" Text="{Binding CustomerNbr}" ToolTipService.ToolTip="{Binding CustomerNumber}" TextAlignment="Center"/>
<ComboBox Grid.Column="3" Height="25" Width="20" Margin="20,5,0,0" VerticalAlignment="Center" x:Name="comboBoxCountryCode"
ItemsSource="{Binding Path=DataContext.EFSSAPCountryCode,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="CountryCode"
SelectedValue="{Binding SelectedCountryCode,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" >
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SelectionChanged">
<core:InvokeCommandAction Command="{Binding ElementName=AssignCountryStateGridData,Path=DataContext.LoadSelectedCountryCodeCommand}"
CommandParameter="{Binding SelectedValue,ElementName= AssignCountryStateGridData}"/>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ComboBox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And this is View Model Code
private ObservableCollection<EFSSAPCountryCode> _EFSSAPCountryCode;
private EFSSAPCountryCode _SelectedCountryCode;
public DelegateCommand LoadSelectedCountryCodeCommand { get; set; }
[RestorableState]
public ObservableCollection<EFSSAPCountryCode> EFSSAPCountryCode
{
get { return _EFSSAPCountryCode; }
set { SetProperty(ref _EFSSAPCountryCode, value); }
}
[RestorableState]
public EFSSAPCountryCode SelectedCountryCode
{
get { return _SelectedCountryCode; }
set { SetProperty(ref _SelectedCountryCode, value); }
}
public AssignCountryStateProvinceCodesViewModel()
{
this.LoadSelectedCountryCodeCommand = DelegateCommand.FromAsyncHandler(GetColumnsForSelectedCountryCode);
}
public async override void OnNavigatedTo(NavigatedToEventArgs e, Dictionary<string, object> viewModelState)
{
//Set EFSSAPCountryCode and other call for service
}
private async Task GetColumnsForSelectedCountryCode()
{
//I am getting call here when I select something from Combo Box but I never get what was selected by the user from the Combo Box and "SelectedCountryCode" stays null
}
I saw something about using RelativeSource but that gives syntax error.
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}
I am not sure what is wrong in this code.