I am trying to bind a ListBox to another ListBox within the same window. The left hand sided Listbox has data in it that one can select. But I want a user to be able to click on the item(s) in the left hand listbox and those same item(s) would be displayed in the other listbox on the right hand side.
EDITED: Of course you can bind a UI property to another UI property (Dependency Property actually) using ElementName, but I recommend to bind the properties to one view model. See a simplified example below.
View model:
public ObservableCollection<ItemObject> Items { get; set; }
public ObservableCollection<ItemObject> SelectedItems { get; set; }
Left:
<ListBox ItemsSource="{Binding Items}" SelectedItems="{Binding SelectedItems}" />
(Note that there is no SelectedItems dependency property actually. See question like: Select multiple items from a DataGrid in an MVVM WPF project)
Right:
<ListBox ItemsSource="{Binding SelectedItems}" />
This works fine. Furthermore, with this approach, the list on the right hand can be customized with ease (eg order, filter, ... by using CollectionView).
private ICollectionView _collectionView;
private ICollectionView _CollectionView {
get { return _collectionView
?? (_collectionView = CollectionViewSource.GetDefaultView(SelectedItems)); }
}
public ICollectionView FilteredItems {
get { _CollecitionView.Filter(...); }
}
<ListBox ItemsSource={"Binding FilteredSelectedItems"} />
Such an MVVM approach is sometimes laborious, but eventually found as beneficial.
You name the first listbox, then any other control on the xaml will bind to that control using it's name in the ElementName attribute of the binding.
For example there are two listboxes and one text box. The top listbox has multiselections and those selection(s) are shown on the lower listbox. While the textbox only gets the first item selected.
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<converters:PathToFilenameConverter x:Key="FilenameConverter" />
<x:Array x:Key="FileNames" Type="system:String">
<system:String>C:\Temp\Alpha.txt</system:String>
<system:String>C:\Temp\Beta.txt</system:String>
<system:String>C:\Temp\Gamma.txt</system:String>
</x:Array>
</StackPanel.Resources>
<ListBox Name="lbFiles"
SelectionMode="Multiple"
ItemsSource="{StaticResource FileNames}"
Margin="10"/>
<ListBox ItemsSource="{Binding SelectedItems, ElementName=lbFiles }" Margin="10" />
<TextBlock Text="{Binding SelectedItem,
ElementName=lbFiles,
Converter={StaticResource FilenameConverter}}"
Margin="10" />
</StackPanel>
Note...the code is binding using the SelectedItems property for the lower list box and not SelectedItem used by the TextBlock.
As an aside, another answer has the use of an ObservableCollection, that is not needed unless the array is dynamically changing; otherwise any array can be used. Depending on loading, say from a VM, it may need to adheres to the INotifyPropertyChanged.
Related
I have a class PricingData and PricingSchedule. Where PricingSchedule is a List<> inside PricingData class. I want to bind data of this class to UWP controls.
Sample code is available to download here : https://github.com/jigneshdesai/SampleOfBindingIssue1.git
How Code looks: i have a start page(mainpage) that hosts ListView control, Listview has PricingUserControl within it. PricingUserControl looks like this
<TextBlock x:Name="lblPriceHeader" Text="{Binding PricingTitle}" Margin="0,0,50,0" />
<ComboBox x:Name="cbPriceValueList" ItemsSource="{x:Bind dpl}" DisplayMemberPath="PriceValue" SelectedValuePath="PriceValue" SelectedValue="{Binding DisplayPricing}" />
<ListView x:Name="lbPriceChangeSchedule" ItemsSource="{Binding PricingScheduleList}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<ComboBox x:Name="cbSchedulePriceValueList" ItemsSource="{x:Bind dpl}" DisplayMemberPath="PriceValue" SelectedValuePath="PriceValue" />
<TextBlock Text="{Binding SchedulePricingTimeZone }" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
What i want to achieve: Combobox should populate a list of values (eg. 1USD, 2USD, 3USD etc.). Then when you provide List of records from database, the listbox will repeat PricingUserControl and combobox within it should set its value property (SelectedValue) as per record.
Issue:
ComboBox x:Name="cbPriceValueList" uses x:bind dpl where dpl is a local variable of PricingUserControl. It populates the list properly. The trouble is ComboBox x:Name="cbSchedulePriceValueList" it also has x:bind dpl but during compilation it display error "Invalid binding path 'dpl' : Property 'dpl' not found on type 'DataTemplate'."
I am wondering why x:bind dpl does not work at this point. ?
I have now realized that your problem is in fact that you need to reach to a Page property from within the DataTemplate, so here is a updated answer.
You cannot use x:Bind if you need to access an outside element's property from within a DataTemplate. Instead, you can use classic {Binding} expression. First add a name to your page:
<Page
...
x:Name="Page">
And now refer to this name from within the DataTemplate:
<ComboBox
x:Name="cbSchedulePriceValueList"
ItemsSource="{Binding ElementName=Page, Path=dpl}"
DisplayMemberPath="PriceValue"
SelectedValuePath="PriceValue" />
Original answer
To be able to use x:Bind inside of a DataTemplate, you must specify the data type the individual items of the control will have, using x:DataType. Suppose your PricingScheduleList is a List<MyApp.Models.MyType>, then you will first need to add this XML namespace to the <Page> element:
xmlns:models="using:MyApp.Models"
And then set the x:DataType attribute as follows:
<DataTemplate x:DataType="models:MyType">
...
</DataTemplate>
You can confirm this works by the fact that IntelliSense should now suggest you the properties of MyType when you start writing the x:Bind expression.
By checking your code, the reason why SelectedValue does not take effect is when you choose the item from ComboBox, you didn't notify your DisplayPricing to change. So you need to implement INotifyPropertyChanged interface in your PricingData. Do the same behavior in PricingSchedule.
public class PricingData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
......
public string DisplayPricing
{
get => $"{PricingValue} {PricingCurrency}";
set
{
var sp = value.Split(' ');
PricingValue = sp.First();
PricingCurrency = sp.Last();
OnPropertyChanged();
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
PScheduleUserControl.xaml:
<ComboBox x:Name="cbPriceValueList" ItemsSource="{x:Bind myList}" DisplayMemberPath="PriceValue" SelectedValuePath="PriceValue" SelectedValue="{Binding DisplayPricing,Mode=TwoWay}" />
I implemented editable ListBox items like it is posted in this answer Inline editing TextBlock in a ListBox with Data Template (WPF)
.
But the new value does not get updated in the ItemsSource object of my ListBox.
This is the XAML:
<ListBox Grid.Row="2" Name="ds_ConfigProfiles" ItemsSource="{Binding ConfigProfiles}" SelectedItem="{Binding ActiveConfigProfile}" IsSynchronizedWithCurrentItem="True" Panel.ZIndex="-1">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- TODO: this is meant for allowing edit of the profile names, but the new name does not get stored back to ConfigProfiles -->
<local:TextToggleEdit Text="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" MinWidth="40" Height="23" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This is the ConfigProfiles property in the view model:
/// <summary>Configuration profiles that were found in the active storage path</summary>
public ObservableCollection<string> ConfigProfiles { get; private set; } = new ObservableCollection<string>();
Did I understand something wrong?
May it be the reason, that the items source is of type ObservableCollection<string> instead of ObservableCollection<ProperClassImplementation> (which is of legacy reasons).
I am relatively new to WPF and am out of ideas on how to debug this.
May it be the reason, that the items source is of type ObservableCollection<string> instead of ObservableCollection<ProperClassImplementation> (which is of legacy reasons).
Yes, exactly. You can't modify a string since it is immutable. You need to bind to a string property of a class which means that you need to replace the ObservableCollection<string> with an ObservableCollection<ProperClassImplementation>.
I am afraid the binding engine won't replace the string in the ObservableCollection<string> with a new string for you if that's what you had hoped for.
I have a list of notifications:
public List<Notification> Notifications { get; set; }
A ListBox in my UI binds to this list:
<ListBox ItemsSource="{Binding Notifications}" DisplayMemberPath="ServiceAddress" Name="NotificationsList"/>
In my UI, I also have this TextBox:
<TextBox Name="MatchWindowTextBox"/>
MatchWindow is a property in Notification objects...so I can access it like this from the above list: Notifications[SomeIndex].MatchWindow. Anyways, when someone changes the selection on the ListBox, this effectively selects a different Notification...so is there some way to bind my TextBox to the selected notification's MatchWindow property?
one quick way is to use an ElementName binding:
<TextBox Text="{Binding SelectedItem.MatchWindow,
ElementName=NotificationsList}"/>
However, a better approach would be to create a SelectedItem property of type Notification in the ViewModel and bind to that instead:
<ListBox ItemsSource="{Binding Notifications}"
SelectedItem="{Binding SelectedNotification}"/>
<!-- ... -->
<TextBox Text="{Binding SelectedNotification.MatchWindow}"/>
Update: I've updated the code based on your help so far, and still no luck. When the application loads the ListBox has no items. I assign junk values to Customers in the windows's contructor, and then am also trying to set the ListBox's DataContext as follows:
CustomerList.DataContext = Customers;
--- Original Question (with updated code) ---
I'm having trouble with databinding in a WPF project.
I have a class, Customer, as follows:
public class Customer
{
public String Name { get; set; }
public String Email { get; set; }
}
In my XAML's code behind I have a collection of customers as follows:
public List<Customer> Customers { get; set; }
I'm trying to bind each customer to a ListBox with a ListItemTemplate displaying the customer's information (name/email) in TextBoxes along with a button which locks/unloacks the TextBoxes (sets the IsEnabled property to true or false).
What's the best way to go about this?
So far I've been tryingt he following with no success.
In the XAML I currently have the following (ignoring the toggle part for now, I'm just trying to get the collection itself to be listed.):
<Window.Resources>
<CollectionViewSource x:Key="Customers" Source="{Binding Path=Customers, Mode=TwoWay}"/>
<DataTemplate x:Key="Customer">
<StackPanel Orientation="Horizontal">
<TextBox Content="{Binding Name}" />
<TextBox Content="{Binding Email}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ListBox ItemsSource="{Binding Source={StaticResource Customers}}"
ItemTemplate="{StaticResource ResourceKey=Customer}"
Name="CustomerList"
Height="300" />
</StackPanel>
You need to change
ItemsSource="{Binding Source=Customers}"
to
ItemsSource="{Binding Source={StaticResource Customers}}" DataContext="{StaticResource Customers}"
Code similar to the updated one works for me after changing
<TextBox Content="{Binding Name}" />
to
<TextBox Text="{Binding Name}" />
As TextBox doesn't have Content property(like a Label), the former refused to compile in VS.
Well, it is set to Text in definition:
[ContentPropertyAttribute("Text")]
public class TextBox : TextBoxBase, IAddChild
But I thought it is only used between the brackets(<TextBox>Like so</TextBox>)?
Could this be the source of the problem?
Try setting the ItemsSource of your CustomerList as follows: ItemsSource="{Binding}". You've set the DataContext of the ListBox to the list of customers, you need to set the ItemsSource to the same collection, hence, the direct binding.
Another thing that you can do, in case you prefer to use the CollectionViewSource, is to set the DataContext of your window to the same class DataContext=this, because without this, the resource definition won't be able to locate the "Customers" collection that you defined in the code behind. If you do this, however, you don't need CustomerList.DataContext = Customers; because you're directly assigning the ItemsSource to a static resource, not relatively to the DataContext.
One more thing. I think you should give the CollectionViewSource and the corresponding collection in the code behind different names. This isn't going to cause a runtime issue, but it makes it hard to maintain the code ;)
Hope this helps :)
I'm having an application using MVC. It has a canvas and property grid. When an item is selected in the canvas. The property grid should display its details.
So I made an event listener and when item is selected in the canvas it raises an event to the controller which pass the selected item to the property grid to display the details.
Model :
Item object containing name, description
Controller :
protected Controller(object model, FrameworkElement view)
{
this._model = model;
this._view = view;
}
public virtual void Initialize()
{
View.DataContext = Model;
}
View :
<TextBlock>Status</TextBlock>
<ComboBox ItemsSource="?????"/>
Where view is the property grid and model is the selected item.
The problem is in the property grid there is a dropdown list containing lookup values how can I get the dropdown values given that the datacontext of the property grid has already been set to the selected item which doesn't contain reference to these lookup items.
I know that it's easy to use custom code to do that. But I don't want to violate the MVC aproach.
Bind to a source rather than DataContext, sources are provided by ElementName, RelativeSource & Source, so you can name the View for example and use ElementName to get it as source then the Path could be DataContext.LookupValues or whatever your property in the model (- the DataContext of the View is your model -) is called.
e.g.
<Window ...
Name="Window">
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<ComboBox ItemsSource="{Binding ElementName=Window, Path=DataContext.Occupations}"
SelectedItem="{Binding Occupation}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- ... --->
Edit: Your problem seems to be that you do not pass the information you need, consider a design which still grants you access to more than just the SelectedItem of some list, e.g.
<Window ...
Name="Window">
<ListBox Name="listBox" ItemsSource="{Binding Data}" />
<ContentControl DataContext="{Binding ElementName=listBox, Path=SelectedItem}">
<ComboBox ItemsSource="{Binding ElementName=Window, Path=DataContext.Occupations}"
SelectedItem="{Binding Occupation}" />
</ContentControl>
<!-- ... --->
The DataContext of the ContentControl may be the SelectedItem of the ListBox but the ComboBox inside can still reference the DataContext of the Window which should provide the necessary information.
This is similar to my first example in that the DataContext inside the DataTemplate is always an item of the collection but you can access external DataContexts using sources in your bindings.