Binding inside listbox itemtemplate problems - wpf

I have two separate binding problems with listboxes with an itemtemplate which contains a texbox.
1) One listbox binds to a list of strings. How can I display each string inside the textboxes created and allow two way binding at the same time? Two way binding isn't allowed without specifying a Path or XPath.
<ListBox Height="231" HorizontalAlignment="Left" Margin="0,167,0,0" Name="listBoxKeys" VerticalAlignment="Top" Width="219" ItemsSource="{Binding Path=SelectedPlatform.Keys}" SelectedItem="{Binding Path=SelectedKey,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,0,0,0">
<TextBox Text="{Binding Mode=OneWay}" Margin="0,0,0,0" Height="Auto" MinWidth="80" MaxWidth="80" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And 2) I use another listbox which binds to some generic list of a custom KeyValuePair class. The itemtemplate contains a textbox and combobox. The textbox text is bound to the key property of each KeyValuePair object and the combobox selecteditem to the value property. My problem is that I want the combo to get filled by a list of strings declared in my viewmodel which will be changing on runtime. The window's datacontext is the viewmodel where the list is declared. I don't know the exact syntax I need to use to bind the combobox itemssource there. Here's my code :
<ListBox Height="393" HorizontalAlignment="Left" Margin="0,72,0,0" Name="listBoxActions" VerticalAlignment="Top" Width="254" ItemsSource="{Binding Path=SelectedPlayer.ControlProfile.MappedActions}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,0,0,0">
<TextBox Text="{Binding Key, Mode=TwoWay,UpdateSourceTrigger=LostFocus}" Margin="10,0,0,0" Height="Auto" MinWidth="80" MaxWidth="80" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<ComboBox Margin="10,0,0,0" Height="Auto" MinWidth="80" MaxWidth="80" HorizontalAlignment="Left" VerticalAlignment="Center" ItemsSource="{Binding ?}" SelectedItem="{Binding Value, Mode=TwoWay}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

The problem is that the two-way binding on the source itself cannot work because it would mean that the whole object (string), for which the data template is created, must be replaced when user changes the text in the text box. Obviously, this will not work. Two-way binding will work only on a writable property of the bound object.
In your case I would suggest creating a view model for the items in the list box (basically a view model for your strings) and expose a Value property on it and bind to it in the data template:
<ListBox Height="231" HorizontalAlignment="Left" Margin="0,167,0,0"
Name="listBoxKeys" VerticalAlignment="Top" Width="219"
ItemsSource="{Binding Path=SelectedPlatform.KeyViewModels}"
SelectedItem="{Binding Path=SelectedKey,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,0,0,0">
<TextBox Text="{Binding Value, Mode=TwoWay}" Margin="0,0,0,0" Height="Auto" MinWidth="80" MaxWidth="80" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

1) Pavlo Glazkov seems to have a good answer to me
2) This is down to the DataContext for the ComboBox now being the key value pair rather than the ViewModel. There may be other ways to do this but the one that I've used before is to set the bindings RelativeSource source back to it's parent ItemsControl.
RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}
Something like:
<ListBox Height="393" HorizontalAlignment="Left" Margin="0,72,0,0" Name="listBoxActions" VerticalAlignment="Top" Width="254" ItemsSource="{Binding Path=SelectedPlayer.ControlProfile.MappedActions}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,0,0,0">
<TextBox Text="{Binding Key, Mode=TwoWay,UpdateSourceTrigger=LostFocus}" Margin="10,0,0,0" Height="Auto" MinWidth="80" MaxWidth="80" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<ComboBox Margin="10,0,0,0" Height="Auto" MinWidth="80" MaxWidth="80" HorizontalAlignment="Left" VerticalAlignment="Center" ItemsSource="{Binding DataContext.Keys, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" SelectedItem="{Binding Value, Mode=TwoWay}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Related

Bind textbox list inside listbox in wpf

I have to make listbox with textbox in it... and it has to be dynamic. I have observable collection in code behind and I want to bind that for listbox. I want dynamic listbox and this list should have editable textbox in it. So, basically I want to bind multiplr textbox from listbox. Any help would be appreciated
<ListBox HorizontalAlignment="Left" Name="ListTwo" Height="100" Margin="286.769,165.499,0,0" VerticalAlignment="Top" Width="100" ItemsSource="{Binding Source=obs}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="TextBoxList"></TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
By doing this, I have number of textbox same as items in observable collection but textbox's text is not set up.
If the items in your ObservableCollection are just plain strings, then you can data bind to the whole string value like this:
<ListBox Name="ListTwo" ItemsSource="{Binding Source=obs}" ... >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="TextBoxList" Text="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
From the Binding.Path Property page on MSDN:
Optionally, a period (.) path can be used to bind to the current source. For example, Text="{Binding}" is equivalent to Text="{Binding Path=.}".
Note that if you had some objects with properties in the collection, then #nit's answer would have been correct as you would need to reference the relevant property name:
<ListBox Name="ListTwo" ItemsSource="{Binding Source=obs}" ... >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="TextBoxList" Text="{Binding PropertyName}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You will have to Bind your textbox to the property in your class of which observable collection you have bound
<ListBox HorizontalAlignment="Left" Name="ListTwo" Height="100" Margin="286.769,165.499,0,0" VerticalAlignment="Top" Width="100" ItemsSource="{Binding Source=obs}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Binding="{Binding PROPERTYINCLASS}"></TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Set Text on combobox while getting data

I'm trying to display a default text in a ComboBox while I'm fetching data from a source, but it doesn't show anything.
<ComboBox
Grid.Row="1"
Grid.Column="2"
Text="Hepper"
ItemsSource="{Binding Builds}"
SelectedItem="{Binding SelectedBuild}"
DisplayMemberPath="VersionNo"
IsReadOnly="True"
IsEnabled="{Binding SelectedBuildEnable}"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Width="180"
Height="30"
MinWidth="180" />
you can try to set the ComboBox.SelectedValue Property instead of ComboBox.Text.
I prefer to show another TextBlock above the ComboBox to display a default text:
<!-- don't forget to define the converter in your resources -->
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</UserControl.Resources>
<!-- your Control -->
<ComboBox
Grid.Row="1"
Grid.Column="2"
x:Name="ComboBoxElement"
ItemsSource="{Binding Builds}"
SelectedItem="{Binding SelectedBuild}"
DisplayMemberPath="VersionNo"
IsReadOnly="True"
IsEnabled="{Binding SelectedBuildEnable}"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Width="180"
Height="30"
MinWidth="180" />
<TextBlock
Grid.Row="1"
Grid.Column="2"
Visibility="{Binding IsEnabled, ElementName=ComboBoxElement, Converter={StaticResource BooleanToVisibilityConverter}}"
IsHitTestVisible="False"
Text="Hepper"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="15,5" />
I guessed that your ComboBox becomes enabled if the data is fetched. Otherwise you have to use another binding for the visibility.
According to MSDN, the ComboBox.Text property
Gets or sets the text of the currently selected item.
Therefore, you could temporarily add an item into your ComboBox with your required message, select it and then remove it before filling the ComboBox when your data arrives.

Commanding with Static Items of Listbox using MVVM

I have a list box in my view with three static items(Name,Age,Gender), and I want my ViewModel to do something when an item in the ListBox is selected. I want to do this without any code-behind, using the MVVM pattern.
My goal is to navigate to a page when an item is selected also the items stated above does not come from an observable list, it is hardcoded in my XAML. How would I do that? Please teach me. If you don't mind to send a sample, that would be a great help. Thanks much in advance.
<ListBox x:Name="lbviewlist">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged" >
<Command:EventToCommand Command ="{Binding ViewCommand}"
PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.Items>
<StackPanel x:Name="lbiview1" Orientation="Vertical">
<ListBoxItem Content="Name" FontSize="35" Margin="10,0,0,0"
Foreground="OrangeRed"/>
<TextBlock TextWrapping="Wrap" Text="View name of the patient"
FontSize="25" Margin="10,0,0,0" Foreground="White"/>
</StackPanel>
<StackPanel x:Name="lbiview2" Orientation="Vertical">
<ListBoxItem Content="Age" FontSize="35" Margin="10,20,0,0"
Foreground="OrangeRed"/>
<TextBlock TextWrapping="Wrap" Text="View age of the patient"
FontSize="25" Margin="10,0,0,0" Foreground="White"/>
</StackPanel>
<StackPanel x:Name="lbiview3" Orientation="Vertical">
<ListBoxItem Content="Gender" FontSize="35" Margin="10,20,0,0"
Foreground="OrangeRed"/>
<TextBlock TextWrapping="Wrap" Text="View the gender of the patient"
FontSize="25" Margin="10,0,0,0" Foreground="White"/>
</StackPanel>
</ListBox.Items>
</ListBox>
You can use data binding to bind the SelectedItem of the ListBox to a property on your view model. Then, in the setter of your view model property, you can run the logic you need to when the selected item changes.
<ListBox SelectedItem="{Binding MySelectedItem}" ... />
Update
Ok, firstly I would strongly recommend (no insist) that you use an MVVM framework. If you're doing MVVM, then you need to use a framework.
Next, there's no reason why these list items can't be on your view model. It would certainly make your view a lot cleaner.
Then you can set your ListBox ItemsSource to bind to your view model collection, and then use data templating in your view to render each item in the list consistently.
E.g. your view can end up something like:
<ListBox ItemsSource="{Binding MyItems}" SelectedItem="{Binding MySelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Value}" ... />
<TextBlock Text="{Binding Description}" ... />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Much cleaner, I think you'll agree.

Dynamically adding controls to View from MVVM

In WPF, I am using the MVVM model.
I have a View with a Dockpanel and I want to add dynamically StackPanels with a Label and TextBox for all Harddisks found over the Binding.
Therefore my XAML looks like:
<DockPanel Grid.Row="1" HorizontalAlignment="Stretch" Margin="5">
<ItemsControl ItemsSource="{Binding Harddisks}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel DockPanel.Dock="Right" HorizontalAlignment="Right" Margin="2.5,0,0,0">
<Label Content="{Binding Path=Label}" />
<TextBox Text="{Binding Path=GB_Free}" Width="100" IsReadOnly="True"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
It should be four Labels and TextBoxes, but only the first Label and TextBox are shown.
Why?
Your items in your ItemsControl are not actually direct children of the DockPanel. You need to change the ItemsControl to specify that the DockPanel is the Panel. The following will cause the ItemsControl to create a DockPanel and place all the items inside it (rather than the StackPanel that ItemsControl uses by default).
More Info: MSDN: ItemsControl.ItemsPanel Property
<ItemsControl ItemsSource="{Binding Harddisks}">
<ItemsControl.ItemsPanel>
<DockPanel Grid.Row="1" HorizontalAlignment="Stretch" Margin="5" />
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel DockPanel.Dock="Right" HorizontalAlignment="Right" Margin="2.5,0,0,0">
<Label Content="{Binding Path=Label}" />
<TextBox Text="{Binding Path=GB_Free}" Width="100" IsReadOnly="True"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Binding to local variable in codebehind

I have to bind to local variable from code behind in ListView.ItemTemplate. I have tried using datacontext:
<ListView Name="listView" ItemsSource="{Binding}" DataContext="{Binding}" VerticalAlignment="Stretch" MaxHeight="300" Background="White" Foreground="Black" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" BorderBrush="#FFCDCDCD" BorderThickness="1" ScrollViewer.VerticalScrollBarVisibility="Auto" IncrementalLoadingTrigger="None" SelectionChanged="listView_SelectionChanged" >
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" MaxHeight="25" >
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Text}" Width="{Binding Width}" />
<Image Grid.Row="0" Grid.Column="1" Source="{Binding Image}" Stretch="None" Width="40" HorizontalAlignment="Left" Margin="4,0,0,0" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In place of "Width="{Binding Width}"" I want to have property from datacontext - rest of data is connected with itemsSource. How to "tell" xaml to use property from datacontext? (right now it returns error "property not found"). I don't want to change my itemsource and add there width because it is constant for every element (it's obvious ;) ).
Thank you in advance :)
It does use the DataContext, which for the ItemTemplate is the item. You can walk up the tree to find another DataContext, in this case you want the ListView's:
{Binding DataContext.Width,
RelativeSource={RelativeSource AncestorType=ListView}}

Resources