Bind selected item to view model - c#

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.

Related

C# WIN UI 3: I have combobox with custom-typed observable collection as itemssource. Need help getting selected item

very new to C# WPF/WIN UI. My combobox displays my collection perfectly. But now when I want to do something with what the user selected I can't find the correct syntax in my 'SelectionChanged' event handler. I want to get the 'Market_ID' somehow. (FYI, I am not using MVVM yet as I don't understand how to implement, but I will learn. (really liking c#))
<ComboBox x:Name="cmbMarketID" PlaceholderText="Select Market ID" Width="500" Margin="5,5,0,0" RelativePanel.RightOf="border1" RelativePanel.Below="cmbState" ItemsSource="{x:Bind marketIdent}" SelectionChanged="cmbMarketID_SelectionChanged" SelectedItem="{Binding Market_ID}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="cmbo_market_ID" Text="{Binding Market_ID}" Width="15" TextAlignment="Right"/>
<TextBlock Text="{Binding Product}" Width="145" Margin="10,0,10,0" FontWeight="SemiBold"/>
<TextBlock Text="{Binding Company}" Width="70" Margin="10,0,10,0"/>
<TextBlock Text="{Binding System}" Margin="10,0,10,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Here is the event handler: (I used a simple string first, and that worked, but now I need to use a typed-collection)
private void cmbMarketID_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (cmbMarketID.SelectedIndex == -1)
{
// Do not execute event
}
else
{
//string mktid = cmbMarketID.SelectedItem.ToString().Substring(0, 2).TrimEnd();
//string mktid = cmbMarketID.SelectedItem;
int mktid = (int)cmbMarketID.SelectedItem(); <-------what should the correct syntax be here?
//v_metric_mktid = mktid;
}
}
Cast the SelectedItem property to your type.
For example, if marketIdent is an IEnumerable<YourClass>:
var item = cmbMarketID.SelectedItem as YourClass;
Or if marketIdent is an IEnumerable<int>:
if (mbMarketID.SelectedItem != null)
{
var mktid = (int)mbMarketID.SelectedItem;
}

using a datatemplate to dynamically load radpanes and property grids

I'm using some telerik components and am trying to do the following:
I have a TabCollection which is a observable collection of ConfigurationTab I want to create a radPane for every ConfigurationTab in this collection (this i have managed sort of) then i want the current tab to display a RadPropertyGrid to which I will bind a (custom) collection of properties.
The code is as follows:
(XAML)
<UserControl.Resources>
<DataTemplate x:Key="TabCollectionTemplate">
<telerik:RadPane CanUserClose="False" Header="{Binding DisplayName}">
<telerik:RadPropertyGrid Margin="0,4,0,4"
assistant:PropertyGridAssistant.AllowDescription="True"
assistant:PropertyGridAssistant.AllowReset="True"
valid:ValidationAssistant.IsEnabled="True"
valid:ValidationAssistant.IsValid="{Binding IsTaskValid,
Mode=OneWayToSource}"
BorderThickness="0,0,0,0"
DockPanel.Dock="Top"
EnableEditorCaching="False"
Item="{Binding Path=TabPropertyCollection,
UpdateSourceTrigger=PropertyChanged}"
SearchBoxVisibility="Collapsed"
SortAndGroupButtonsVisibility="Visible" />
</telerik:RadPane>
</DataTemplate>
</UserControl.Resources>
<Grid>
<telerik:RadDocking Name="ConfigurationDocking">
<telerik:RadDocking.DocumentHost>
<telerik:RadSplitContainer InitialPosition="DockedRight">
<telerik:RadPaneGroup ItemTemplate="{StaticResource TabCollectionTemplate}" ItemsSource="{Binding TabCollection}" />
</telerik:RadSplitContainer>
</telerik:RadDocking.DocumentHost>
</telerik:RadDocking>
</Grid>
C#
public class ConfigurationTab : ObservableObject
{
private string mDisplayName = string.Empty;
private Property.Management.Properties mProperties = new Property.Management.Properties();
public string DisplayName
{
get
{
return mDisplayName;
}
set
{
mDisplayName = value;
this.RaisePropertyChanged(() => this.DisplayName);
}
}
public Property.Management.Properties TabProperties
{
get
{
return mProperties;
}
set
{
mProperties = value;
this.RaisePropertyChanged(() => this.TabProperties);
this.RaisePropertyChanged(() => this.TabPropertyCollection);
}
}
public PropertiesToPropertyGridAdapter<DescriptorOfProperty> TabPropertyCollection
{
get
{
return new PropertiesToPropertyGridAdapter<DescriptorOfProperty>(mProperties);
}
}
The thing i get using the current code looks as follows, the headers of the tabs are filled in correctly with the display name but the part where the content of the pane only ever displays: "IOLAN.ModuleConfigurationTester.ConfigurationTab" which is the problem:
Also when I click on the 2nd tab I get a null reference exception:
System.NullReferenceException occurred
Message: Exception thrown: 'System.NullReferenceException' in Telerik.Windows.Controls.Docking.dll
Additional information: Object reference not set to an instance of an object.
Can anyone see what i'm doing wrong?
EDIT
An interesting thing to add, if i change my data template to a simple textbox It changes nothing, the only thing i see in the radpane = "IOLAN.ModuleConfigurationTester.ConfigurationTab" and i still get a null reference trying to open the other tab:
<DataTemplate x:Key="TabCollectionTemplate">
<telerik:RadPane CanUserClose="False" Header="{Binding DisplayName}">
<TextBox Text="Hallo" />
</telerik:RadPane>
</DataTemplate>
After a while i figured out what I had to do, i simply had to use a TablControl and set the content template, doing it like this solved all the problems:
<UserControl.Resources>
<DataTemplate x:Key="ContentTemplate">
<telerik:RadPropertyGrid Name="PropertyGrid"
Margin="0,4,20,4"
assistant:PropertyGridAssistant.AllowDescription="True"
assistant:PropertyGridAssistant.AllowReset="True"
BorderThickness="0,0,0,0"
IsGrouped="True"
Item="{Binding Path=TabPropertyCollection,
UpdateSourceTrigger=PropertyChanged}"
SearchBoxVisibility="Collapsed" />
</DataTemplate>
<DataTemplate x:Key="HeaderTemplate">
<TextBlock Text="{Binding Path=DisplayName}" />
</DataTemplate>
</UserControl.Resources>
<Grid>
<telerik:RadDocking>
<telerik:RadDocking.DocumentHost>
<DockPanel>
<telerik:RadTabControl ContentTemplate="{StaticResource ContentTemplate}"
ItemTemplate="{StaticResource HeaderTemplate}"
ItemsSource="{Binding TabCollection}"
SelectedItem="{Binding Path=SelectedItem,
Mode=OneWayToSource}" />
</DockPanel>
</telerik:RadDocking.DocumentHost>
</telerik:RadDocking>
</Grid>

ComboBox in a ListBox fires SelectionChanged event but value of the selected item of combo box doesnt get updated - MVVM Pattern

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.

How to select an item in a ListBox and pick the item's name from a DataTemplate in WPF

I am implementing a Download UI in WPF, where every file that is being downloaded will be shown inside a list box in a DataTemplate
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="FileName" text={Binding FileName}" />
<ProgressBar ... />
<Button Content="Cancel" click="ButtonCancel_Click" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox>
Now this List is getting populated with all the download information perfectly. Only problem I am having is that when user clicks on Cancel button, to cancel the download, I have to remove an entry from the ObservableCollections. But I don't have the File Name in the click event( I know click event is not MVVM, still I want to do it in click event handler).
Can anyone suggest how do I get the FileName of that particular file when the selectedItem gets cancelled. in The
private void ButtonCancel_Click(...) {}
Although I would still encourage you to use MVVM way of dealing with UI events, here's how you can achieve what you want, using Cancel button's click event handler.
First in your xaml, bind file name to Cancel button's Tag property.
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="FileName" text={Binding FileName}" />
<ProgressBar ... />
<Button Content="Cancel" Tag="{Binding FileName}"
Click="ButtonCancel_Click" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox>
Then in your click event handler
private void ButtonCancel_Click(object sender, RoutedEventArgs e)
{
Button myButton = (Button)sender;
string fileName = myButton.Tag.ToString();
// use fileName
}
Edit
Just to add a complete example, that was tested locally, and ensured that works.
XAML
<Window x:Class="WpfTestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox Name="listBox1">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock x:Name="FileName" Text="{Binding Path=FileName}" />
<Button Content="Cancel" Tag="{Binding Path=FileName}"
Click="ButtonCancel_Click" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var fileNames = new List<DownloadModel>
{
new DownloadModel
{
FileName = "File1"
},
new DownloadModel
{
FileName = "File2"
},
new DownloadModel
{
FileName = "File3"
}
};
listBox1.ItemsSource = fileNames;
}
private void ButtonCancel_Click(object sender, RoutedEventArgs e)
{
var myButton = sender as Button;
if (myButton.Tag == null)
{
MessageBox.Show("Tag value was null.");
}
else
{
MessageBox.Show(string.Format("File name is {0}", myButton.Tag));
}
}
}
public class DownloadModel
{
public string FileName { get; set; }
}

How to retrieve selected RadioButton of dynamic generated RadioButtons in WPF Mvvm

Using WPF and MVVM pattern, I got a Listbox dynamically filled with Radio Buttons.
<ListBox ItemsSource="{Binding SupportedNtgs}" VerticalAlignment="Stretch" Background="Transparent">
<ListBox.ItemTemplate>
<DataTemplate>
<RadioButton GroupName="SupportedNtgsRadioButtonList" Content="{Binding Item2}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and in my ViewModel i got a property
public Ntg SelectedNtg
{
get { return VariantInfo.Ntg; }
set
{
if (VariantInfo.Ntg == value) { return; }
VariantInfo.Ntg = value;
RaisePropertyChanged("SelectedNtg");
}
}
The SupportedNtgs the ListBox is bound to is an IEnumerable
public IEnumerable<Tuple<Ntg, String>> SupportedNtgs
{
get
{
if (this.supportedNtgs == null) {
this.supportedNtgs = new List<Tuple<Ntg, string>>();
foreach (var item in this.provider.SupportedNtgs) {
this.supportedNtgs.Add(new Tuple<Ntg, string>(item, EnumHelper<Ntg>.Description(item)));
}
}
return this.supportedNtgs;
}
}
Can anybody tell me what is the easiest way to store the user selection in my SelectedNtg property, without making any changes to my Ntg class? Thank you
thank you
here you go
added binding to SelectedItem with SelectedNtg SelectedItem="{Binding SelectedNtg}"
your xaml
<ListBox ItemsSource="{Binding SupportedNtgs}"
VerticalAlignment="Stretch" Background="Transparent"
SelectedItem="{Binding SelectedNtg}" >
<ListBox.ItemTemplate>
<DataTemplate>
<RadioButton GroupName="SupportedNtgsRadioButtonList"
Content="{Binding Item2}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
this will push the selected item to your property in your view model hence u'll get the data you are looking for

Categories