I'm trying to use compiled bindings in UWP with a simple use case.
In order to make my XAML more readable easy to manage, I've extracted the XAML of a DataTemplate to a UserControl. So I transformed this
MainPage.xaml
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView ItemsSource="{x:Bind ViewModel.Items}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:ProjectItem">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Bind Name, Mode=OneWay}" />
<TextBlock Text="{x:Bind Description, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Page>
Into this
MainPage.xaml
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView ItemsSource="{x:Bind ViewModel.Items}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:ProjectItem">
<local:MyUserControl1 />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Page>
MyUserControl1.xaml
<UserControl
x:Class="App1.MyUserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Bind Name}" />
<TextBlock Text="{x:Bind Description}" />
</StackPanel>
</UserControl>
The problem is that it doesn't even compile because x:Bind doesn't know the context.
How does x:Bind cover this use case?
I'd suggest create Dependency Property for ProjectItem on your MyUserControl1.xaml.cs
public static readonly DependencyProperty ProjectItemProperty =
DependencyProperty.Register(
nameof(ProjectItem),
typeof(ProjectItem),
typeof(MyUserControl1),
null);
public ProjectItem ProjectItem
{
get => (ProjectItem)GetValue(ProjectItemProperty);
set => SetValue(ProjectItemProperty, value);
}
Then on your XAML, bind the properties of your ProjectItem Dependency Property:
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Bind ProjectItem.Name, Mode=OneWay}" />
<TextBlock Text="{x:Bind ProjectItem.Description, Mode=OneWay}" />
</StackPanel>
Then on your MainPage.xaml, pass on the 'ProjectItem' collection item.
<DataTemplate x:DataType="local:ProjectItem">
<local:MyUserControl1 ProjectItem="{x:Bind}"/>
</DataTemplate>
If you use this approach, you could (or rather need to) add a property to MyUserControl1.xaml.cs that casts the current DataContext to a ProjectItem and returns it:
public ProjectItem Item => DataContext as ProjectItem;
public MyUserControl1()
{
InitializeComponent();
DataContextChanged += (s, e) => Bindings.Update();
}
You then bind to this property in the XAML markup:
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Bind Item.Name}" />
<TextBlock Text="{x:Bind Item.Description}" />
</StackPanel>
The other option would to use non-compiled {Bindings} or get rid of MyUserControl1 and revert to the inline DataTemplate.
This could be faster as the DataContext is casted only once when the Control is Loaded.
public ProjectItem ProjectItem { get; private set; }
public MyUserControl1()
{
InitializeComponent();
Loaded += (s, e) =>
{
ProjectItem = (ProjectItem)DataContext;
Bindings.Update();
};
}
Add this if DataContext change in MainPage:
DataContextChanged += (s, e) =>
{
ProjectItem = (ProjectItem)DataContext;
Bindings.Update();
};
Based on the answer given by #mm8.
Related
I have
three WPF UserControls with their Viewmodels
one WPF container for displaying one of the WPF Usercontrols
WinForm Usercontrol
I want: display different WPF UserControl, which I set is in WinForms User Control
public partial class WinContainer : UserControl
{
public WinContainer()
{
InitializeComponent();
WPFContainer WPFControl = new WPFContainer();
PartPageViewModel ss = new PartPageViewModel();
WPFControl.DataContext = ss;
ElementHost elHost = new ElementHost();
elHost.Child = WPFControl;
elHost.Dock = DockStyle.Fill;
this.Controls.Add(elHost);
}
}
<UserControl x:Class="MDMSpecification.Views.WPFContainer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MDMSpecification.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:services="clr-namespace:MDMSpecification.Services"
xmlns:viewModels="clr-namespace:MDMSpecification.ViewModels"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<UserControl.Resources>
<DataTemplate x:Key="Assembly" DataType="{x:Type viewModels:AssemblyPageViewModel}">
<local:AssemblyPageView />
</DataTemplate>
<DataTemplate x:Key="Part" DataType="{x:Type viewModels:PartPageViewModel}">
<local:PartPageView />
</DataTemplate>
<DataTemplate x:Key="Drawing" DataType="{x:Type viewModels:DrawingPageViewModel}">
<local:DrawingPageView />
</DataTemplate>
</UserControl.Resources>
<StackPanel>
<ContentPresenter Content="{Binding}" />
</StackPanel>
</UserControl>
In result I have this:
What's wrong with my code?
Remove x:Key="XYZ" from DataTemplate.
In order to apply a DataTemplate automatically to DataType you should omit the key.
For example:
<DataTemplate DataType="{x:Type viewModels:AssemblyPageViewModel}">
<local:AssemblyPageView />
</DataTemplate>
This xaml is equivalent to:
<DataTemplate x:Key={x:Type viewModels:AssemblyPageViewModel}
DataType="{x:Type viewModels:AssemblyPageViewModel}">
<local:AssemblyPageView />
</DataTemplate>
DataTemplates with a key should be applied explicitly like this:
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource Assembly}" />
Hallo and thank you for your time.
I have a peculiar problem. I have created a usercontol, which has some custom dependency properties.
If i implement the usercontrol, and bind to a static text. Everything is working fine.
However, if i try to set it to the value of a selected objects properties. It does not work.
This is the error i am getting in the output window:
Error: BindingExpression path error: 'SelectedUseCase' property not
found on 'Helper.UserControls.UseCasePropertyDisplay'.
BindingExpression: Path='SelectedUseCase.Name'
DataItem='Helper.UserControls.UseCasePropertyDisplay'; target element
is 'Helper.UserControls.UseCasePropertyDisplay' (Name='null'); target
property is 'Text' (type 'String')
UserControl:
https://github.com/Toudahl/SoftwareDesignHelper/blob/master/Helper/UserControls/DisplayAndEditControl.xaml
<UserControl
x:Class="Helper.UserControls.UseCasePropertyDisplay"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Helper.UserControls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel Orientation="Horizontal"
Margin="260,0,0,0">
<TextBlock Text="{Binding Label}"
Style="{StaticResource UseCaseTextBlock}"
x:Name="textblock_label"/>
<TextBlock x:Name="textblock_propertyContent"
Text="{Binding Text}"
Style="{StaticResource UseCaseFrameWorkTextElement}"
DoubleTapped="textblock_DoubleTapped" />
<TextBox x:Name="textbox_propertyContent"
Text="{Binding Text}"
Visibility="Collapsed"
Style="{StaticResource UseCaseTextBox}"
LostFocus="textbox_LostFocus" />
</StackPanel>
</UserControl>
Decleration of dependency properties in codebehind:
https://github.com/Toudahl/SoftwareDesignHelper/blob/master/Helper/UserControls/DisplayAndEditControl.xaml.cs
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text",
typeof(string),
typeof(UseCasePropertyDisplay),
new PropertyMetadata(null));
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
"Label",
typeof(string),
typeof(UseCasePropertyDisplay),
new PropertyMetadata(null));
public string Text
{
get { return (string)GetValue(TextProperty); }
set {SetValue(TextProperty, value);}
}
public string Label
{
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value);}
}
This is how i implement it on in the view:
https://github.com/Toudahl/SoftwareDesignHelper/blob/master/Helper/ViewsAndViewModels/ViewUseCases.xaml
<uc:UseCasePropertyDisplay Label="Name" Text="{Binding SelectedUseCase.Name, Mode=TwoWay}" />
From reading very similar questions here, im guessing that it has something to do with the way i set the context. However, the solution that has been provided to people (setting the relative source to the ancestor), doesnt work for me. Since its not available on my platform.
I am not really sure where to go from here, as this is the first time i try to use usercontrols, and the first time i use dependency properties.
School doesnt start untill a few weeks, so i cant get a hold of my teacher for this.
Instead of setting the DataContext global, set the first ui element to the inherited DataContext:
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
As you have written, you can not use FindAncestor so you can try using a name and reference to it:
<UserControl
x:Class="Helper.UserControls.UseCasePropertyDisplay"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Helper.UserControls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
x:Name="Root">
<StackPanel DataContext="{Binding ElementName=Root}"
Orientation="Horizontal"
Margin="260,0,0,0">
<TextBlock Text="{Binding Label}"
Style="{StaticResource UseCaseTextBlock}"
x:Name="textblock_label"/>
<TextBlock x:Name="textblock_propertyContent"
Text="{Binding Text}"
Style="{StaticResource UseCaseFrameWorkTextElement}"
DoubleTapped="textblock_DoubleTapped" />
<TextBox x:Name="textbox_propertyContent"
Text="{Binding Text}"
Visibility="Collapsed"
Style="{StaticResource UseCaseTextBox}"
LostFocus="textbox_LostFocus" />
</StackPanel>
</UserControl>
so i had the same problem as this person.. WPF: Bind Collection with Collection to a ListBox with groups
I tried to implement the suggested answer to my problem but found the error "The property 'Content' is set more than once". The only difference is id like to implement this in a User Control like so:
here is my c# code:
public class Dog
{
public int dogID { get; set; }
public String Name { get; set; }
List<Puppy> puppies { get; set; }
}
public class Puppy
{
public String Name { get; set; }
}
and this is XAML:
<UserControl x:Class="PetStore.Menu"
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"
d:DesignHeight="300" d:DesignWidth="300">
<CollectionViewSource x:Name="dogs" Source="{Binding}" >
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Name" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<DataTemplate x:Key="dogTemplate" DataType="Project">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
<ListBox ItemsSource="{Binding Source={StaticResource dogs}}">
<ListBox.GroupStyle>
<GroupStyle HeaderTemplate="{StaticResource dogTemplate}" />
</ListBox.GroupStyle>
<ListBox.ItemTemplate>
<DataTemplate DataType="Puppy">
<ListBox ItemsSource="{Binding puppies}">
<ListBox.ItemTemplate>
<DataTemplate DataType="Puppy">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
Any help is greatly appreciated!!
You're missing the UserControl.Resources tag:
<UserControl x:Class="Tackle.View.Menu"
...>
<UserControl.Resources> <!-- You're missing this -->
<CollectionViewSource x:Key="dogs" Source="{Binding}" > <!-- Change x:Name to x:Key here -->
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Name" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<DataTemplate x:Key="dogTemplate" DataType="Project">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<!-- here goes your window content -->
The error message tells you what you did wrong: you attempted to set the Content property more than once. When you declare an element as a direct child of a control, then you are implicitly setting whichever property is specified in the [ContentProperty("...")] attribute on that control type. For ContentControl (and its subclass UserControl), that property is Content. If you want to set a different property, or add something to a collection/dictionary property, you must specify the property name. This means either using the Property="value" attribute syntax or the <Owner.Property> element syntax.
I just started working with WPF and I'm trying to wrap my head around the XAML Bindings.
After a lot of reading I thought I had an easy enough usecase to test my knowledge. But nada :-( I can't figure it out.
What I'm trying to do is the following:
I have Address-Objects:
public IEnumerable<Address> Addresses
{
get
{
if (addresses == null)
{
addresses = new Repository<Address>().GetAll().ToList<Address>();
}
return addresses;
}
}
which have Rental-Objects e.g.
address.Rentals
also as IList.
On my MainWidow I want to have 2 UserControls, one for the address and one for the rental information. If I click the Navigator on the address user control, I only want to see the rental records of the current address.
MainWindow
<Window x:Class="Swopt.Mira.WPFApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:local="clr-namespace:Swopt.Mira.WPFApp"
Title="MainWindow" Height="500" Width="525">
<Window.Resources>
<sampleData:DataContext x:Key="testDataContext" />
</Window.Resources>
<Grid x:Name="rootGrid" Background="White" DataContext="{StaticResource testDataContext}">
<StackPanel>
<local:RentalCustomer x:Name="rentalCustomer" Height="100" />
<local:Rentals Height="100" />
</StackPanel>
</Grid>
Address
<UserControl
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:telerik="http://schemas.telerik.com/2008/xaml/presentation" x:Class="Swopt.Mira.WPFApp.RentalCustomer"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="RentalCustomerUserControl">
<Grid x:Name="rootGrid" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<StackPanel>
<TextBlock Text="Firma1"/>
<TextBox x:Name="Firma1" Text="{Binding ElementName=collectionNavigator, Path=CurrentItem.Firma1}" />
</StackPanel>
<telerik:RadCollectionNavigator Grid.Row="1"
Height="30"
Margin="2"
Grid.ColumnSpan="2"
x:Name="collectionNavigator"
Source="{Binding Addresses}"
/>
</Grid>
Rentals
<UserControl
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:telerik="http://schemas.telerik.com/2008/xaml/presentation" x:Class="Swopt.Mira.WPFApp.Rentals"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="RentalsUserControl">
<Grid x:Name="rootGrid" Background="White" >
<StackPanel>
<TextBlock Text="VertragsNr"/>
<TextBox x:Name="VertragsNr" Text="{Binding ElementName=collectionNavigator2, Path=CurrentItem.VertragsNr}" />
</StackPanel>
<telerik:RadCollectionNavigator Grid.Row="1"
Height="30"
Margin="2"
x:Name="collectionNavigator2"
Source="{Binding Rentals}" />
</Grid>
Essentially what I want to do is Bind the DataContext of UserControl2 (Rentals) to the CurrentItem of collectionNavigator in UserControl1 (Addresses)
But I can't figure out how. I tried many things, with Binding Path and RelativeSource but none seem to work. the only way I could get it to work was to copy the content of UserControl1 into my MainWindow.xaml, but then I can't reuse it.
Any help is much appreciated.
UPDATE >>>>>
Working solution from Sheridan.
MainWindows
...
<local:RentalCustomer x:Name="rentalCustomer" Height="400" />
<local:Rentals Height="100" DataContext="{Binding CurrentAddress, ElementName=rentalCustomer}" />
RentalCustomer.xaml
...
<telerik:RadCollectionNavigator Grid.Row="1"
Height="30"
Margin="2"
Grid.ColumnSpan="2"
x:Name="collectionNavigator"
Source="{Binding Addresses}"
CurrentItem="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=CurrentAddress, Mode=OneWayToSource}"
/>
RentalCustomer.xaml.cs
public Address CurrentAddress
{
get { return (Address)GetValue(CurrentAddressProperty); }
set { SetValue(CurrentAddressProperty, value); }
}
// Using a DependencyProperty as the backing store for CurrentAddress. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CurrentAddressProperty =
DependencyProperty.Register("CurrentAddress", typeof(Address), typeof(RentalCustomer), new PropertyMetadata(null));
You can data bind the current item of a collection using the "/" Binding notation. Try something like this:
<UserControl Name="Control1" DataContext="{Binding SomeCollection}" />
<UserControl Name="Control2" DataContext="{Binding SomeCollection/}" />
Binding SomeCollection/ means bind to the current item of the SomeCollection collection. To make this work, you may need to set the Selector.IsSynchronizedWithCurrentItem Property to True on any container controls that you are selecting the current item in:
<ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" ... />
UPDATE >>>
Without an IsSynchronizedWithCurrentItem property, the "/" Binding notation won't work. Instead, you could expose the selected item from the collection as a DependencyProperty on the relevant UserControl. Then you could do something like this:
<UserControl Name="Control2" DataContext="{Binding SelectedItem, ElementName=Control1}" />
You could provide the Rentals from your viewModel by binding the CurrentItem to your viewModel and expose the Rentals like this:
<telerik:RadCollectionNavigator CurrentItem={Binding CurrentItem} />
public Address CurrentItem
{
get; set;
}
public IEnumerable<Rentals> Rentals
{
get { return CurrentItem.Rentals; }
}
I use Caliburn Micro for my WPF application. I implemented a little UserControl:
<UserControl Name="ImageButtonUserControl"
x:Class="SportyMate.Utility.Controls.ImageButton"
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">
<Grid>
<Button>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ElementName=ImageButtonUserControl, Path=Image}" />
<TextBlock Text="{Binding ElementName=ImageButtonUserControl, Path=Text}" />
</StackPanel>
</Button>
</Grid>
</UserControl>
Now I want to use these Control in my view:
<uc:ImageButton Name="Cancel" Image="/Images/Icons/cancel_16x16.png" Text="Abbrechen" Margin="3" />
When I want to open my view (in my case it's opened as a dialog) it doesn't work. The View does not openend.
When I remove the Name-Attribute everthing is fine, but the Button have no binding to an action. Can anyone tell me what I have to do for a correct binding? A regular Button worked.
You are on a completely wrong way. You shouldn't create a user control for such a simple modification. You need to create a DataTemplate and use it for a Button.ContentTemplate. First you need to define a helper type for button content:
public class ImageButtonContent
{
public BitmapImage Image { get; set; }
public string Label { get; set; }
}
After that you can use it for DataTemplate:
<Window x:Class="Trys.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Trys="clr-namespace:Trys"
Title="MainWindow"
Height="350"
Width="525">
<Window.Resources>
<Trys:ImageButtonContent x:Key="YourImageButtonHere"
Label="Your ImageButtonHere">
<Trys:ImageButtonContent.Image>
<BitmapImage UriSource="your-icon.png" />
</Trys:ImageButtonContent.Image>
</Trys:ImageButtonContent>
<DataTemplate x:Key="ImageButton"
DataType="{x:Type Trys:ImageButtonContent}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Image}"
Margin="5" />
<TextBlock Text="{Binding Label}"
VerticalAlignment="Center"
Margin="10,0,0,0" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Button ContentTemplate="{StaticResource ImageButton}"
Content="{StaticResource YourImageButtonHere}"
Height="50"
Width="250" />
</Grid>
</Window>
I used a resource for an object, but you can use a property on your ViewModel. The result is:
And this just a normal button. You can use for it all power of Caliburn.Micro conventions like Click event default binding. Enjoy!