Binding to the UserControl which contains the ItemControl data - wpf

I created a UserControl (MediaList) which contains an ItemControl which is binded to a property "ToolsBar". The idea is to allow to add custom button/controls from outside the MediaList control.
My problem is that i'm trying to add a button into the ToolsBar which have a binding to the UserControl itself. I don't override the DataContext of my MediaList (UserControl) to be "this" since i'm needing to use my DataModel which is defined in the root window.
Maybe the way I'm doing this is complety wrong?
Here is an exemple of the window which use a custom button to the MediaList :
<localControls:MediaList x:Name="NewMediaList">
<localControls:MediaList.ToolsBar>
<Button Content="{StaticResource ResourceKey=MoveToPlaylist}"
IsEnabled="{Binding ElementName=NewMediaList, Path=SelectedMedia, Converter={localConverters:ObjectToBool}}"/>
</localControls:MediaList.ToolsBar>
</localControls:MediaList>
Until now I tried multiple kind of binding without success such as :
{Binding ElementName=MediaListControl, Path=SelectedMedia, Converter={localConverters:ObjectToBool}}"
{Binding RelativeSource={x:Static RelativeSource.Self}, Path=SelectedMedia, Converter={localConverters:Debug}}
{Binding SelectedMedia, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}
{Binding SelectedMedia, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type localControls:MediaList}}}
Take note I write them back from my memory and that I also tried with (and without) setting the DataContext of the ItemControl and StackPanel (which contains the ItemControm) from MediaList.xaml to be the MediaList object itself.
MediaList.xaml:
<UserControl x:Class="MediaPlaylist.Controls.MediaList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:localConverters="clr-namespace:Suisse.MediaPlaylist.Converters"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="MediaListControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid Grid.Row="0"
AutoGenerateColumns="False"
ItemsSource="{Binding ElementName=MediaListControl, Path=Playlist.Medias}"
SelectedItem="{Binding ElementName=MediaListControl, Path=SelectedMedia, Mode=TwoWay}">
<DataGrid.Columns>
<DataGridTextColumn Header="Title"
Binding="{Binding Title}"
IsReadOnly="True"/>
<DataGridTextColumn Header="File"
Binding="{Binding FilePath}"
IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Right"
Grid.Row="1">
<ItemsControl ItemsSource="{Binding ElementName=MediaListControl, Path=ToolsBar}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<Separator Visibility="{Binding ElementName=MediaListControl, Path=ToolsBar.Count, Converter={localConverters:ObjectToVisibility}}" />
<Button
HorizontalAlignment="Right"
Content="Delete"
Click="OnDelete_Click"
IsEnabled="{Binding ElementName=MediaListControl, Path=SelectedMedia, Converter={localConverters:ObjectToBool}}"/>
</StackPanel>
</Grid>
Thanks

First, you are trying to assign binding data to ItemsSource property of ItemsControl, which is completely wrong.
Instead of that you can declare ItemsControl dependency property in your MedialList.xaml.cs
public partial class MediaList : UserControl
{
public MediaList()
{
InitializeComponent();
}
public static DependencyProperty ToolsBarProperty = DependencyProperty.
Register("ToolsBar", typeof(ItemsControl), typeof(MediaList));
public ItemsControl ToolsBar
{
get { return (ItemsControl)GetValue(ToolsBarProperty); }
set { SetValue(ToolsBarProperty, value); }
}
}
You can then update MediaList xaml as
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Right"
Grid.Row="1">
<ContentControl Content="{Binding ElementName=MediaListControl, Path=ToolsBar}">
</ContentControl>
Then you can assign any collection template from outside to ToolBar property as
<localControls:MediaList>
<localControls:MediaList.ToolsBar>
<ItemsControl >
<ItemsControl.Items>
<Button Content="{StaticResource ResourceKey=MoveToPlaylist}"
IsEnabled="{Binding ElementName=NewMediaList, Path=SelectedMedia, Converter={localConverters:ObjectToBool}}"/>
<Label>Hello </Label>
<Label> How are you?</Label>
</ItemsControl.Items>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</localControls:MediaList.ToolsBar>
</localControls:MediaList>
Hope this will solve your problem.
Note: I have just moved out ItemsControl defined inside MediaList user control and replaced it with ContentControl. This allows to set any template from outside to Content property of ContentControl.

Related

How can I set the binding for a control's property (which is inside DataTemplate and UserControl) to use the ItemSource's given property?

I would like to make a UserControl which have a DataTemplate, and inside that DataTemplate there are controls. I would like to bind to those nested (inside the DataTemplate) controls' properties so I can set them when I reuse this UserControl. The nested controls will use the ItemSource's properties but the property names of the ItemSource's properties could be different.
The UserControl:
<UserControl x:Class="ContextMenu.BaseFilterUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
x:Name="Self">
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="70" />
</Grid.RowDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Margin="10"
Text="Owners" />
<Button Grid.Column="1"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="10"
Click="FilteButtonClicked"
Width="40"
Height="40"
x:Name="FilterButton">
<Popup x:Name="FilterBoxPopup"
PlacementTarget="{Binding ElementName=FilterButton}"
Placement="Bottom"
StaysOpen="False">
<Border BorderBrush="Black"
Background="White"
Margin="2">
<ListView ItemsSource="{Binding ElementName=Self, Path=FilterList}"
x:Name="FilterListView"
Height="300"
Width="150">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!--<CheckBox IsChecked="{Binding IsChecked}" />-->
<!--<TextBlock Text="{Binding Name}" />-->
<!--This is where I don't know how to properly bind eg. the above control, things I tried:-->
<!--<TextBlock Text="{Binding ElementName=FilterListView, Path=FilterElementName}" />-->
<!--<TextBlock Text="{Binding ElementName=Self, Path=DataContext.FilterElementName}" />-->
<!--<TextBlock Text="{Binding ElementName=FilterListView, Path=DataContext.FilterElementName}" />-->
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Border>
</Popup>
</Button>
<TextBlock Grid.Column="3"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="10"
Text="{Binding ElementName=Self, Path=SelectedNames}" />
</Grid>
</UserControl>
This how the UserControl is used, the FilterElementName="Name" is what I would like to set, depending on the list binded with FilterList list:
<local:BaseFilterUserControl FilterList="{Binding Owners}"
FilterElementName="Name"
SelectedNames="{Binding SelectedNames}"/>
In this case the Owners is a simple IReadOnlyList of an Owner class. The Owner class has a string Name property. But I will use this UserControl again with different list eg. where I would like to use the Versions list's Release property (for the TextBlock inside the UserControl):
<local:BaseFilterUserControl FilterList="{Binding Versions}"
FilterElementName="Release"
SelectedNames="{Binding SelectedReleases}"/>
The ListView is properly populated with items, so the FilterList DependencyProperty is working. But the nested controls are only working when I hard code the bindings:
<TextBlock Text="{Binding Name}" />
For this to work you would need to bind the Path-property of your TextBlocks Text-Binding to the FilterElementName property of your UserControl. Unfortunately the Path property of the Binding class is not a DependencyProperty and therefore not bindable.
One way to to achieve your goal would be to use the DisplayMemberPath property of the ListView, which is bindable:
<ListView x:Name="FilterListView"
Width="150"
Height="300"
ItemsSource="{Binding ElementName=Self, Path=FilterList}"
DisplayMemberPath="{Binding ElementName=self, Path=FilterElementName}"/>
If this approach does not work because you need to specify a more complex ItemTemplate, another way would be create a property of type DataTemplate in your UserControl, use that as ItemTemplate in the ListView and specify it from outside like so:
<local:BaseFilterUserControl FilterList="{Binding Versions}"
SelectedNames="{Binding SelectedReleases}">
<local:BaseFilterUserControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Release}" />
</DataTemplate>
</local:BaseFilterUserControl.ItemTemplate>
</local:BaseFilterUserControl>

Synchronize the size of controls that lay in different ItemsCollections

I want to synchronize the width of two controls.
<!--DataContext-->
<DockPanel LastChildFill="True" x:Name="HeaderDockPanel">
<DockPanel LastChildFill="True" MinHeight="60" DockPanel.Dock="Top" DataContext="{Binding Month}" d:DataContext="{d:DesignInstance Type=local:Month}" >
<TextBlock Text="{Binding Name}" Style="{StaticResource TitleTextBlock}" DockPanel.Dock="Left" />
<!--this shoud to set the Width-->
<!--Nested_1 DataContext Model_1-->
<local:DaysLine HorizontalAlignment="Left" Width="{Binding ElementName=HeaderDockPanel, Path=DataContext.DaysHeaderWidth, Mode=TwoWay}" />
</DockPanel>
<!--Nested_1 DataContext Model_2-->
<ListView Grid.Row="1" ItemsSource="{Binding CarBusiness}">
<ItemsControl.ItemTemplate>
<DataTemplate >
<DockPanel d:DataContext="{d:DesignInstance Type=local:CarBusiness}" LastChildFill="True">
<TextBlock Text="{Binding Name}" Style="{StaticResource TitleTextBlock}" DockPanel.Dock="Left" Margin="-6 0 0 0"/>
<!--this shoud to get the Width-->
<!--Nested_2 DataContext Model_3-->
<ItemsControl ItemsSource="{Binding Business}" Style="{StaticResource HorizontalItemsControl}" Width="{Binding DaysHeaderWidth}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:BusinessTextBlock d:DataContext="{d:DesignInstance Type=local:Business}" Business="{Binding}" ColumnWidth="20" HorizontalAlignment="Left" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListView>
</DockPanel>
The problem is that i can't find the source by RelativeSource Binding use FindByName.
The target control whatch on nested DataContext that is an item of the first and i cant set it in without adding to all this models the MyWidth property.
Is there a some enother way to bind them more correct?
I am thinking about a static property but it is not as correct as i want because i have planned to have a several instances of this control.

Binding to ViewModel instance from View

I have program which initializes PersonsViewModel in MainWindowViewModel's constructor.
public MainWindowViewModel()
{
PersonsViewModel viewModel = new PersonsViewModel();
}
In MainWindow.xaml, PersonsViewModel and PersonsView are connected.
<Window.Resources>
<DataTemplate DataType="{x:Type vm:PersonsViewModel}">
<vw:PersonsView />
</DataTemplate>
</Window.Resources>
I use viewModel as ItemsControl ItemsSource.
<ItemsControl ItemsSource="{Binding viewModel}" Margin="4" />
Now my program opens UserControl and I need to set instance of PersonsViewModel to UserControl.DataContext.
<UserControl.DataContext>
<vm:PersonsViewModel />
</UserControl.DataContext>
Am I creating a new instance of PersonsViewModel. If I am doing so, then how I can bind it to PersonsViewModel instance? Because I have following code in UserControl. I have PersonsList bound to ItemsSource and I need to bind Command to PersonsViewModel instance.
<ItemsControl ItemsSource="{Binding PersonsList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Width="50" Text="{Binding Name}" />
<Button Content="Ok" Width="20" Margin="3" Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl}}, Path=DataContext.Command}" CommandParameter="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You can remove the UserControl.DataContext and Add ItemsControl.ItemTemplate. Since each ItemsControl is bound to List of person viewmodel. Each item will get the viewmodel as its datacontext. Refer the below code.
<ItemsControl ItemsSource="{Binding ViewModels}" Margin="4">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type vm:PersonsViewModel}">
<vw:PersonsView />
</DataTemplate>
</ItemsControl.ItemTemplate>

Button in ListBox item's ItemsControl doesn't fire in WPF XAML

I've got the following setup for my ListBox in wpf:
ListBox with as ItemsPanel a StackPanel (orientation horizontal)
ItemTemplate of ListBox is a Grid with a TextBox and an ItemsControl
the Items control contains buttons with as ItemPanel a WrapPanel.
Somehow when I click on one of the buttons in the ItemsPanel, the ICommand wont fire in the ViewModel class which is bound to the View.xaml.
It looks like I'm selecting the ListBox item and not the item within the ItemsControl.
Here's the xaml code
<ListBox
Background="Transparent"
ItemTemplate="{StaticResource ProductGroupTemplate}"
FocusVisualStyle="{x:Null}"
VerticalContentAlignment="Top"
ItemsSource="{Binding NodeHeaders}"
Grid.Row="1"
Grid.ColumnSpan="3"
Grid.Column="0"
Grid.RowSpan="2"
SelectedItem="{Binding CurrentItem}"
BorderBrush="Transparent"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
Margin="30,0,55,0"
VerticalAlignment="Top">
<ListBox.Style>
<Style TargetType="ListBox">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding HasParent}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
And the DataTemplate
<DataTemplate x:Key="ProductGroupTemplate">
<Grid Margin="0,0,20,0">
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Description}" FontSize="20" Grid.Row="0" Grid.Column="0" Foreground="{StaticResource DefaultLabelColor}" />
<ItemsControl ItemsSource="{Binding Nodes}" Grid.Row="1" Grid.Column="0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Text="{Binding Description}" Height="75" Width="150" Padding="5" Margin="5" Background="{StaticResource SalesItemsBackground}"
Foreground="{StaticResource SalesItemsForeground}" HorizontalAlignment="Center" VerticalAlignment="Center" TextAlignment="Left" Command="{Binding RelativeSource=
{RelativeSource FindAncestor,
AncestorType={x:Type ContentControl}},
Path=DataContext.Select}" CommandParameter="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
If you have a view model that declares an ICommand instance and is set as the DataContext of a view (UserControl, or Window), then you can use that property to access your ICommand instance. From the Button in your DataTemplate, you can use this RelativeSource Binding:
<Button Text="{Binding Description}" Height="75" Width="150" Padding="5" Margin="5"
Background="{StaticResource SalesItemsBackground}" TextAlignment="Left"
Foreground="{StaticResource SalesItemsForeground}" HorizontalAlignment="Center"
VerticalAlignment="Center" Command="{Binding DataContext.Select, RelativeSource={
RelativeSource AncestorType={x:Type YourViewsPrefix:YourView}}}"
CommandParameter="{Binding}"/>
That's because your data template's DataContext is set to an item from the listbox. If you have List<Person> bound to your list box every list box item's DataContext would be a Person which is why you can write something like "{Binding Description}". You have two ways to execute a command from DataTemplate one of them is the one that "Sheridan" told you. As an alternative you could set your element's DataContext to your parent View or ViewModel.
<DataTemplate>
<Grid DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type View/ViewModel}}">
</Grid>
</DataTemplate>

Binding textboxes inside a usercontrol to datagrid

Is the way that I'm binding the UserControl to the DataGrid correct?
In my MainWindow I have a datagrid (code below):
<DataGrid x:Name="MusicListGrid" ItemsSource="{Binding MusicList}" AutoGenerateColumns="False" IsSynchronizedWithCurrentItem="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Artist" Binding="{Binding Artist,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
<DataGridTextColumn Header="Album" Binding="{Binding Album,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
<DataGridTextColumn Header="Track" Binding="{Binding Track,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</DataGrid.Columns>
</DataGrid>
The usercontrol inside my MainWindow, that I'm binding to the selected item of the datagrid:
DataTemplate(in MainWindow):
<Window.Resources>
<DataTemplate x:Key="MusicDetailListTemplate" >
<v:MusicDetailView DataContext="{Binding ElementName=MusicListGrid,Path=SelectedItem}" />
</DataTemplate>
</Window.Resources>
ContentControl(in MainWindow):
<ContentControl x:Name="musicDetail" Content="{Binding}" ContentTemplate="{StaticResource MusicDetailListTemplate}" Grid.Column="1" />
The textboxes inside my UserControl looks like this:
<TextBox Grid.Column="1" Grid.Row="0" Width="200" Margin="10,5" Text="{Binding Artist,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
<TextBox Grid.Column="1" Grid.Row="1" Width="200" Margin="10,5" Text="{Binding Album,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
<TextBox Grid.Column="1" Grid.Row="2" Width="200" Margin="10,5" Text="{Binding Track,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
This works, but I'm not sure if I'm doing the correct way?
Can I also bind a textbox to the datagrid like this:
<TextBox Grid.Column="1" Grid.Row="3" Text="{Binding Path=MusicListGrid.Artist.SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" />
The data in the datagrid is an ObservableCollection<Music> (music is my model).
If I replace my UserControl with a tabcontrol, should I have the tabcontrol be a separate view or just be part of MainWindow? And have the content of the tabcontrol be separate views? I was thinking of having the datagrid and a tabcontrol with 2 tabs, one for editing and the other for displaying (to look more presentable).
Sorry if these are very basic questions, I just want to be on the right path.
It is more common to set the Content property of the ContentControl element to the data object:
<ContentControl x:Name="musicDetail" Content="{Binding ElementName=MusicListGrid,
Path=SelectedItem}" ContentTemplate="{StaticResource MusicDetailListTemplate}" />
The DataContext defines what the data of the relevant type should look like in the UI, so you shouldn't really set the DataContext there... it is automatically set to the relevant data object instance... it should look more like this (where Prefix is the XML Namespace Prefix that you set up for your project and YourClass is the type of object in the DataGrid):
<Window.Resources>
<DataTemplate x:Key="MusicDetailListTemplate" DataType="{x:Type Prefix:YourClass}">
<v:MusicDetailView DataContext="{Binding}" />
</DataTemplate>
</Window.Resources>

Resources