DataContext ComboBox Binding inside of ListBox - c#

I have a ListBox that displays a List<Item> Items with Item being a custom object. Foreach item I want the user to see a ComboBox with List<string> Options as the Source with the selected Item tying back to a property on a Item . In the list box I have no trouble binding the individual Item properties but how do I reach back up into the DataContext to get my list of options?
View Model is being set as the page's DataContext
class ViewModel
{
public ObservableCollection<string> Options { get; set; }
public ObservableCollection<Item> Items { get; set; }
}
Xaml
<ListBox x:Name="ItemsListBox" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="50" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="ItemProperty1TB"
Text="{Binding Path=Property1, Mode=OneWay}"
Grid.Column="0"
/>
<ComboBox x:Name="OptionsCB"
SelectedItem ="{Binding ChosenOptions, Mode=TwoWay}"
ItemsSource="{Binding Path=DataContext.Options}"
Grid.Column="1"
PlaceholderText="Option"
/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I tried to cut out as much extra code and get to a readable example.
How to bind to a source inside a ListBox different from the ItemsSource already specified This uses AncestorType that does not exist?
ComboBox inside Listbox.ItemTemplate Binding problem This binds to a static resource. Should I put my options into a static resource?
ElementName looks promising but my IDE only recommends Elements scoped to inside the ListBox... DO NOT TRUST VISUAL STUDIO
Am I just going about this all wrong?

Try this:
ItemsSource="{Binding Path=DataContext.Options, ElementName=ItemsListBox}"

You can use RelativeSource property on Combobox binding object to find the parent. Something like this should work
ItemsSource="{Binding Path=DataContext.Options, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
Replace UserControl with Page if you are using Page or Window for that matter.

Related

How to access data from ViewModel in ItemsSource tag

I'm making TabControl that can change dynamically using ItemsSource tag.
I want to know the way to access ViewModel data in ItemsSource tag.
I searched through the Internet. but I couldn't find the answer.
CODE
public class ViewModel
{
// this will be used in ItemsSource
private ObservableCollection<ActiveButton> _allExecuteButtonInfos = new ObservableCollection<ActiveButton>();
public ObservableCollection<ActiveButton> AllExecuteButtonInfos
{
get { return _allExecuteButtonInfos; }
set {
_allExecuteButtonInfos = value;
OnPropertyChanged();
}
}
// I want to get this data in ItemsSource
private List<string> _boardNameList = new List<string>();
public string BoardNameList
{
get { return _boardNameList; }
set {
_boardNameList = value;
OnPropertyChanged();
}
}
}
XAML
<Grid>
<TabControl Background="#FF292929" ItemsSource="{Binding AllExecuteButtonInfos}">
<TabControl.ContentTemplate>
<DataTemplate>
<ScrollViewer Grid.Row="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalScrollBarVisibility="Auto">
<Grid VerticalAlignment="Stretch" Margin="0,0,0,0" >
<ComboBox Width="334" Margin="0,0,0,0" Grid.Column="1" Grid.Row="1" Height="22" VerticalAlignment="Top"
<!-- I want to get data from ViewModel not in ItemsSource(AllExecuteButtonInfos) -->
<!-- eg) VM:BoardNameList, ViewModel.BoardNameList etc -->
ItemsSource="{Binding BoardNameList, Mode=TwoWay , UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedBoard, Mode=TwoWay}"/>
</Grid>
</ScrollViewer>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
I hope I can find the answer.
Thank you.
You could bind to the DataContext, i.e. the view model, of the parent TabControl using a RelativeSource:
<ComboBox ...
ItemsSource="{Binding DataContext.BoardNameList, RelativeSource={RelativeSource AncestorType=TabControl}}" />
Note that it's pointless to set the Mode of an ItemsSource binding to TwoWay since the control never sets the property. It's also meaningless to set the UpdateSourceTrigger to PropertyChanged in this case for the same reason.
I am not sure where you've defined the data context but I suppose that it's somewhere above the first 'Grid' markup. Something like this?
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
Then you have to somehow refer to the Datacontext of the window. You can do it this way
<ComboBox
ItemsSource="{Binding DataContext.BoardNameList, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" />
if the name of your view is not 'MainWindow', you have to change it to the view name where you have that code.
One of the best ways is to create a UserControl for each model and then put data templates in TabControl.Resources with DataType specified for all types you could put in ItemsSource - you get full customization of the view with nice seperation of XAML files.
<Grid>
<TabControl Background="#FF292929" ItemsSource="{Binding AllExecuteButtonInfos}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type MyViewModel1}">
<MyViewModel1_View ViewModel="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type MyViewModel2}">
<MyViewModel2_View ViewModel="{Binding}"/>
</DataTemplate>
</TabControl.Resources>
</TabControl>
</Grid>
I'm going from memory, so the binding may be done differently, but that's the basic idea.
That, or you use some kind of ViewResolver as the only item in the TabControl (something like this)
Basically, go even more MVVM :)
Provided that the DataContext of your view is set correctly to your ViewModel and AllExecuteButtonInfos is indeed available in your view, you can use a RelativeBinding to access properties which are not in the DataContext of your current scope.
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.BoardNameList}" />
With that, you are leaving the implicit DataContext of the DataTemplate, which is ActiveButton and access the object of the specified type via AncestorType. From there you can set a Path to the DataContext of the UserControl, which is, in your case, an object of the class ViewModel.
Imaging you are climbing up a ladder. From the ComboBox object up to your UserControl, from where you can access all underlying properties.

Data Binding in GridView

I'm trying to bind some data to a GridView in Windows 8.1's Hub control.
Currently, I have a DataTemplate set up under Page.Resources as follows:
<DataTemplate x:Key="Standard240x320ItemTemplateFAV">
<Grid HorizontalAlignment="Left" Width="320" Height="240">
<Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
<Image Source="{Binding FavImage}" Stretch="UniformToFill"/>
</Border>
<StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
<TextBlock Text="{Binding FavTitle}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextBlockStyle}" Height="48" Margin="15,0,15,0"/>
</StackPanel>
</Grid>
</DataTemplate>
I then have this HubSection:
<HubSection x:Name="FavHub" Padding="40,60,40,0" >
<DataTemplate>
<GridView
x:Name="itemGridView"
Margin="-4,-4,0,0"
AutomationProperties.AutomationId="ItemGridView"
AutomationProperties.Name="Items In Group"
ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource Standard240x320ItemTemplateFAV}"
SelectionMode="Single"
IsSwipeEnabled="false"
IsItemClickEnabled="True"
ItemClick="ItemView_ItemClick">
</GridView>
</DataTemplate>
</HubSection>
I use this code to add the DataContext:
FavHub.DataContext = new FavData(Constants.getImage("1002"), "No Favourites");
Where the FavData class is:
public class FavData
{
public static string FavImage { get; set; }
public static string FavTitle { get; set; }
public FavData() { }
public FavData(string itemImageSet, string itemNameSet)
{
FavImage = itemImageSet;
FavTitle = itemNameSet;
}
}
However, no data shows up in the HubSection. What am I doing wrong?
You'll need to bind a list, like a List<FavData> or an ObservableCollection<FavData> to the Hub.
Right now, you've got a GridView that among many other attributes, includes initialization of the ItemsSource property. This property is used as the source for a list of items.
<GridView x:Name="itemGridView"
ItemsSource="{Binding Items}"
</GridView>
The binding is specified as {Binding Items} which means that for whatever object is bound currently to the Hub, grab the List stored on the Items property. As you currently had set a single FavData instance to the Hub via the DataContext property, and it did not have a property called Items, there was nothing to display.
So, my suggestion is to create a list of FavData instances and bind that to the Hub instance instead. If you want to directly bind the list rather than store the list in another "parent" object, you'll also need to adjust the Binding to refer to "self" rather than a specific property. For that, you just use the syntax: {Binding}. It just means, "bind to me." So, the GridView will look for the list of items directly on the bound object (the list of FavData).
<GridView x:Name="itemGridView"
ItemsSource="{Binding}"
</GridView>
And in the C#:
List<FavData> favs = new List<FavData>();
favs.Add(new FavData(Constants.getImage("1002"), "No Favourites"));
FavHub.DataContext = favs;

Listview bound via ElementName stays empty

I'm stuck again at some data-binding issue.
This time I want to bind a ListView to the SelectedItem of a GridView. I already suceed with this type of data-binding but now my ListView, which should show some details about my selected item in my GridView just stays empty. There are no items in it although they should exist.
The GridView binds just fine at the property in my MainViewModel.Substituting the ElementName attribute with x:Resouces doesn't seem to be an option, because it doesn't work either.
The source view:
<GridView x:Name="gridViewOrderYears"
ItemsSource="{Binding SelectedCustOrders, Mode=TwoWay}"
HorizontalAlignment="Left"
Grid.Row="1"
VerticalAlignment="Top"
Width="316"
Height="63"
Margin="657,316,0,0"
SelectionMode="Single">
<GridView.ItemTemplate>
<DataTemplate>
<Grid>
<Border BorderThickness="1" BorderBrush="Aquamarine">
<StackPanel>
<TextBlock Text="{Binding Year}" FontSize="20"/>
<TextBlock Text="{Binding OrderCount}"/>
</StackPanel>
</Border>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
this View doesnt bind:
<ListView HorizontalAlignment="Left"
Height="231"
Margin="657,401,0,0"
Grid.Row="1"
VerticalAlignment="Top"
Width="316"
DataContext="{Binding SelectedItem, ElementName=gridViewOrderYears}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DoneOrders.Order_Date, ElementName=gridViewOrderYears}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
SelectedCustOrders porperty is an IList<OrderYears>.
OrderYears is following data value object defined in my MainViewModel:
public class OrderYears
{
public int? Year { get; set; }
public IList<Orders> DoneOrders { get; set; }
public int OrderCount { get; set; }
}
I think the problem is in the ListView binding, because you try to bind to a property named "Orders", which does not exist in the OrderYears object. You have a property named DoneOrders which you can bind to (don't confuse the property name with the type of elements inside the list!), but if you bind a TextBlock to a IList you will just get the guid for the IList object.
Try something like this, replacing you ListView with a ListBox (which is enough for what you are trying to do here):
<ListBox DataContext="{Binding ElementName=gridViewOrderYears,
Path=SelectedItem.DoneOrders}"
DisplayMemberPath="Order_Date"/>
There is no need to create a template, the items inside the ListBox will be displayed like a TextBlock. Note that you can benefit from binding to nested properties like MainProperty.SubProperty.
Let me know if this was helpful, bindings can be such a headache when you are starting...
Finally I got it.
After hours of trial-and-error finally substituting DataContext with ItemsSource did the trick... Sometimes things are easier than you think :)

How to add a menuItem to a context menu which has a set ItemsSource and ItemContainerStyle in XAML

I have the following XAML code. The contents in the ItemsSource are displayed as MenuItems.
<controls:DropDownButton x:Name="btnOwner"
DockPanel.Dock="Left"
Style="{StaticResource btnStyle}"
HorizontalAlignment="Left"
Visibility="{Binding IsOwnerVisible}">
<controls:DropDownButton.Content>
<ContentControl Width="22"
Height="22"
Style="{StaticResource iconOwner}"/>
</controls:DropDownButton.Content>
<controls:DropDownButton.DropDown>
<ContextMenu HorizontalContentAlignment="Stretch"
ItemsSource="{Binding Owners, Mode = TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemContainerStyle="{StaticResource OwnerStyle}">
</ContextMenu>
</controls:DropDownButton.DropDown>
How can I add a new menuItem something like a SubMenuHeader via XAML to this List?
It will create itself. All that you need to provide the ItemTemplate in which you will decide what to show and how to show in each MenuItem. Otherwise, the default implemention will call ToString() method for each item in Owners, and will display it in MenuItem.
<ContextMenu ItemsSource="{Binding Owners}">
<ContextMenu.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
Here, I assumed that the type of owner has a property name Title. For example, if Owners is ObservableCollection<Owner>, then Owner is defined as:
public class Owner
{
public string Title { get; set;}
//...
}
That is basic idea as to how to use ItemTemplate. Now if you want submenuitem in the context menu, then you've to use HierarchicalDataTemplate instead of DataTemplate in the ItemTemplate definition.

Get the ListBox row object from a button that is on the DataTemplate

I have a ListBox with a DataTemplate. The template has a Button on it. When the Button is clicked I want to do some logic with the object that is each row (in this case an object called WorkItemTypeMappings).
In theOnClick how can I go from the Button (object sender) to the object that is row that the button is on?
Here is the XAML of my ListBox:
<ListBox ItemsSource="{Binding Source={StaticResource WorkItemTypeMappingsCollectionView}}" HorizontalContentAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Disabled" Name="lstWITypes">
<ListBox.GroupStyle>
<x:Static Member="GroupStyle.Default"/>
</ListBox.GroupStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding SourceType, Converter={StaticResource WorkItemTypeToStringConverter}}"/>
<ComboBox Grid.Column="1" SelectedItem="{Binding DestType}" ItemsSource="{Binding WorkItemTypesForCurrentDestProject, Source={x:Static loc:MainMediator.Instance}, diagnostics:PresentationTraceSources.TraceLevel=High}" DisplayMemberPath="Name" />
<!-- This is the button-->
<Button Grid.Column="2" Content="{Binding PercentMapped}"
Click="ManualMappingClick"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
What you can do as an alternative is to use Command instead of Event. If you bind your button to a command and pass along with it a command parameter, you should be able to get the item that is related to that button. Example code:
<!-- This is the button-->
<Button
Grid.Column="2"
Content="{Binding PercentMapped}"
Command="SolutionNamespace:CommandClass.ButtonClickedCommand"
CommandParameter="{Binding}" />
I am not sure how familiar you are with WPF commanding but you will notice that the CommandParameter binds to the context without a path name, that is to say it binds to the WorkItemTypeMappings that you need.
Command Example Code:
public static SimpleCommand ButtonClickedCommand { get; set; }
static CommandClass()
{
ButtonClickedCommand = new SimpleCommand
{
ExecuteDelegate =
x=> ButtonClicked(x as WorkItemTypeMappings)
};
}
public static ButtonClicked(WorkItemTypeMappings mappings)
{
if(mappings != null) MessageBox.Show(mapping.PercentMapped)
}
You will notice that the ButtonClickedCommand is static, this is required because the button cannot access the command from its current binding context.
SimpleCommand is just a simplified implementation of the ICommand, can Google this one if you're not sure. I hope this is not an overkill to your problem, but you cannot go wrong with WPF Commands.
Good luck.
Try using VisualTreeHelper to find the parent ListBoxItem for the button. A few good general all-purpose helper functions can be found here:
How can I find WPF controls by name or type?
so, for example, you could find the ListBoxItem from the click event with a call like this:
ListBoxItem item = UIHelper.FindVisualParent<ListBoxItem>(sender);
have you tried ListBox.SelectedItem?
Additionally, if you have a reference to the ListBoxControl already, and you also have the data item representing the row (I assume so since you have the binding, and thus can drag it out of the DataContext on the button), you can ask the ItemsContainerGenerator. ContainerFromItem to give you give you the actual UIElement for the row.
The itemcontainergenerator can be found as a property on the listview itself. (technically the itemscontrol, since thats where it's defined)

Categories

Resources