Refactor XAML code with binding - wpf

I try to refactor such XAML by introducing new user control:
<Window ...>
<ComboBox ItemsSource="{Binding Greetings}" />
</Window>
After adding a control I have
ControlA XAML:
<UserControl ...>
<ComboBox ItemsSource="{Binding Items}" />
</UserControl>
ControlA C#:
public static readonly DependencyProperty ItemsProperty =
WpfUtils.Property<IEnumerable, ControlA>("Items");
public IEnumerable Items { get; set; }
New Window XAML:
<Window ...>
<uc:ControlA Items="{Binding Greetings}" />
</Window>
After this I see nothing in ComboBox. What is wrong here?

Your ComboBox is binding to the DataContext. Since your DataContext is still an object with a list called Greetings, this will not work...
Your ContolA should resemble something like this:
<UserControl x:Name="Root" ...>
<ComboBox ItemsSource="{Binding ElementName=Root, Path=Items}" />
</UserControl>
Now, your combobox binds to the Items property of your ControlA, instead of your DataContext property...
Hope this helps..

Related

Can't bind ItemsControl.ItemTemplate to List<object> element

I'm in trouble. I created UserControl that looks below:
http://i.stack.imgur.com/ITxkS.jpg
I have dependency property "Text" bound to TextBlock.Text of the UserControl. I want to create another UserControl that visualize List. Here is my code:
<UserControl x:Class="ListPresenter.ListViewer"
xmlns:dop="clr-namespace:DeletableObjectPresenter;assembly=DeletableObjectPresenter"
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">
<Grid>
<ItemsControl ItemsSource="{Binding List, RelativeSource={RelativeSource AncestorType=UserControl}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel></WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<ItemContainerTemplate>
<dop:DeletableObjectPresenter></dop:DeletableObjectPresenter>
</ItemContainerTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
...
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace ListPresenter
{
/// <summary>
/// Interaction logic for ListViewer.xaml
/// </summary>
public partial class ListViewer : UserControl
{
public static readonly DependencyProperty ListProperty = DependencyProperty.Register(
"List", typeof (IList<object>), typeof (ListViewer), new PropertyMetadata(null));
public List<object> List
{
get { return (List<object>) GetValue(ListProperty); }
set { SetValue(ListProperty, value); }
}
public ListViewer()
{
InitializeComponent();
}
}
}
Here is look of my control:
http://i.stack.imgur.com/JIjHy.jpg
The problem is that i don't know how to bind Text of the Item to list's element. Thank you!
The first thing to do is to create a DependencyProperty for your Text property in your UserControl. You should data bind to this property in your UserControl:
<TextBlock Text="{Binding Text, RelativeSource={RelativeSource
AncestorType={x:Type dop:DeletableObjectPresenter}}}" />
Now you can data bind to the Text property of your UserControl from the ItemsControl.ItemTemplate:
<ItemsControl.ItemTemplate>
<DataTemplate>
<dop:DeletableObjectPresenter Text="{Binding PropertyToBindToText}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
Of course, your objects in your List collection will need to have a property (named PropertyToBindToText in this example) to data bind to the Text property of your UserControl. As such, you'd be better off creating a strongly typed collection instead of your List<object> and furthermore, it is customary to use ObservableCollection<T> in WPF.
See the Data Binding Overview page on MSDN for further information.

WPF user control's datacontext to property in codebehind

Having a simple XAML user control, I'd like to set the DataContext to the code behind (xaml.cs) file.
I'd like to set DataContext and Itemssource in XAML, so I can populate the combobox with property ListOfCars
XAML
<UserControl x:Class="Sample.Controls.MyControl"
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="85" d:DesignWidth="200">
<Grid Height="85" Width="200" Background="{StaticResource MainContentBackgroundBrush}">
<StackPanel Orientation="Vertical">
<ComboBox Height="23.338" x:Name="CarList" />
</StackPanel>
</Grid>
</UserControl>
Code behind
public List<Cars> ListOfCars
{
get { return _store.ListCars(); }
}
In other words, instead of doing this in codebehind, how may I set the binding in XAML
public MyControl()
{
InitializeComponent();
_store = new Store();
CarList.ItemsSource = _store.ListCars();
CarList.DisplayMemberPath = "Name";
}
Just bind the ItemsSource.
<ComboBox ItemsSource="{Binding ListOfCars}"/>
And then for the UserControl:
<MyControl DataContext="{Binding *viewModel*}"/>
You have to bind the DataContext where your UserControl is used rather than in the definition, because in the definition you don't know to what to bind. The Combobox automatically is in the context of the control so you can just bind to the DataContext without any additional work.
Example of binding to a resource:
<Application.Resources>
...
<viewmodels:ViewModelLocator x:Key="ViewModelLocator"/>
...
</Application.Resources>
<MyControl DataContext="{Binding Source={StaticResource ViewModelLocator}}"/>
This creates an instance of the ViewModelLocator and then binds the DataContext of the control to that resource.
Do not do that, you will mess up all external bindings to the DataContext. Use UserControl.Name and ElementName bindings instead (or RelativeSource).

DependencyProperty in UserControl doesn't get updated from binding

Here is the reproduction of my problem:
Create a WPF Application
Add a new UserControl to the project
Replace its content with the following
<UserControl
x:Class="UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<TextBlock Text="{Binding MyText}"/>
</UserControl>
Public Class UserControl1
Public Property MyText As String
Get
Return GetValue(MyTextProperty)
End Get
Set(ByVal value As String)
SetValue(MyTextProperty, value)
End Set
End Property
Public Shared ReadOnly MyTextProperty As DependencyProperty =
DependencyProperty.Register("MyText", GetType(String), GetType(UserControl1))
End Class
Replace the following in the MainWindow.xaml file:
<Window
x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:WpfApplication1"
Title="MainWindow"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel >
<TextBox Text="{Binding Title, UpdateSourceTrigger=PropertyChanged}"/>
<src:UserControl1 MyText="{Binding Title}"/>
</StackPanel>
</Window>
As you can see, the UserControl1.MyText property doesn't get updated when the MainWindow.Title changes.
What am I missing? I want the user control to be connected to the parent property, is there a xamly way to do it?
Okay, when I recreate the app (I'm using C#, so I don't think this will make any difference), I notice in my output the binding is failing because UserControl does not have a Title property.
Add ElementName=this to your UserControl1 binding. And set the Name property on the Window to this and that should fix it.
Something like this:
<src:UserControl1 MyText="{Binding Title, ElementName=this}" />
The binding works for me when I do that. Hope that helps!

WPF -- Anyone know why I can't get this binding to reference?

<StackPanel x:Name="stkWaitingPatients" Width="300" Margin="0,0,0,-3"
DataContext="{Binding Mode=OneWay, Source={StaticResource local:oPatients}}">
I'm getting StaticResource reference 'local:oPatients' was not found.
Here is the codebehind:
public partial class MainWindow : Window
{
ListBox _activeListBox;
clsPatients oPatients;
public MainWindow()
{
oPatients = new clsPatients(true);
...
To be able to address the object as a StaticResource, it needs to be in a resource dictionary. However, since you're creating the object in MainWindow's constructor, you can set the DataContext in the code-behind like so.
oPatients = new clsPatients(true);
stkWaitingPatients.DataContext = oPatients;
And then change the Binding to this:
{Binding Mode=OneWay}
This is an ok practice if you're not going to be changing the DataContext again, otherwise you'd want a more flexible solution.
Edit: You mentioned ObjectDataProvider in your comment. Here's how you'd do that. First, add an xmlns:sys to the Window for the System namespace (I'm assuming you already have one for xmlns:local):
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Then you can add an ObjectDataProvider to your resource dictionary like this:
<Window.Resources>
<ObjectDataProvider
x:Key="bindingPatients"
ObjectType="{x:Type local:clsPatients}">
<ObjectDataProvider.ConstructorParameters>
<sys:Boolean>True</sys:Boolean>
</ObjectDataProvider.ConstructorParameters>
</ObjectDataProvider>
</Window.Resources>
And refer to it in a Binding with the StaticResource markup like this, using the same string we specified in the x:Key attached property we gave it in the dictionary:
{Binding Source={StaticResouce bindingPatients}, Mode=OneWay}
Edit 2: Ok, you posted more code in your answer, and now I know why it's throwing an exception during the constructor. You're attempting to do this...
lstWaitingPatients.DataContext = oPatients;
... but lstWaitingPatients doesn't actually exist until after this.InitializeComponent() finishes. InitializeComponent() loads the XAML and does a bunch of other things. Unless you really need to do something before all of that, put custom startup code after the call to InitalizeComponent() or in an event handler for Window's Loaded event.
The following sets the ItemsSource in Code Behind and correctly handles the DataBinding:
public partial class MainWindow : Window
{
public MainWindow()
{
clsPatients oPatients = new clsPatients(true);
//assuming oPatients implements IEnumerable
this.lstWaitingPatients.ItemsSource = oPatients;
And the XAML:
<ListBox x:Name="lstWaitingPatients"
IsSynchronizedWithCurrentItem="true"
ItemTemplate="{StaticResource WaitingPatientsItemTemplate}"
FontSize="21.333" Height="423.291"
ScrollViewer.VerticalScrollBarVisibility="Visible"
GotFocus="lstWaitingPatients_GotFocus"
/>
Now, I can't get this to work...I get a general Windows startup error.
Here is the codebehind with the Initializer and the class being instantiated:
public partial class MainWindow : Window
{
ListBox _activeListBox;
public MainWindow()
{
clsPatients oPatients = new clsPatients(true);
lstWaitingPatients.DataContext = oPatients;
this.InitializeComponent();
Here's the top of my XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Orista_Charting"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
x:Class="Orista_Charting.MainWindow"
x:Name="windowMain"
Title="Orista Chart"
Width="1024" Height="768" Topmost="True" WindowStartupLocation="CenterScreen" Activated="MainWindow_Activated" >
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/ButtonStyles.xaml"/>
<ResourceDictionary Source="Resources/OtherResources.xaml"/>
<ResourceDictionary Source="Resources/TextBlockStyles.xaml"/>
<ResourceDictionary Source="Resources/Converters.xaml"/>
</ResourceDictionary.MergedDictionaries>
Here's the pertinent XAML, as you see, I went ahead and moved the DataContext down to the ListBox from the StackPanel. This doesn't run, but it does render in Design View (however, with no data present in the ListBox):
<!-- Waiting Patients List -->
<Border BorderThickness="1,1,1,1" BorderBrush="#FF000000" Padding="10,10,10,10"
CornerRadius="10,10,10,10" Background="#FFFFFFFF" Margin="15.245,187.043,0,41.957" HorizontalAlignment="Left" >
<StackPanel x:Name="stkWaitingPatients" Width="300" Margin="0,0,0,-3">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Waiting Patients:" VerticalAlignment="Center" FontSize="21.333" Margin="0,0,0,20"/>
<TextBlock HorizontalAlignment="Right" Margin="0,0,38.245,0" Width="139" Height="16"
Text="Minutes Waiting" TextWrapping="Wrap" Foreground="#FF9C2525" FontWeight="Bold" VerticalAlignment="Bottom"
TextAlignment="Right"/>
<!-- Too be implemented, this is the wait animation -->
<!--<Image x:Name="PollGif" Visibility="{Binding Loading}"
HorizontalAlignment="Left" Margin="100,0,0,0" Width="42.5" Height="42.5"
Source="Images/loading-gif-animation.gif" Stretch="Fill"/>-->
</StackPanel>
<ListBox x:Name="lstWaitingPatients"
DataContext="{Binding Mode=OneWay}" ItemsSource="{Binding Mode=OneWay}"
IsSynchronizedWithCurrentItem="true"
ItemTemplate="{StaticResource WaitingPatientsItemTemplate}"
FontSize="21.333" Height="423.291" ScrollViewer.VerticalScrollBarVisibility="Visible"
GotFocus="lstWaitingPatients_GotFocus"
/>
</StackPanel>
</Border>
Ok, but if I just take comment out the assigment line in the codebehind, it does run (albeit with no data in the listbox):
public partial class MainWindow : Window
{
ListBox _activeListBox;
public MainWindow()
{
clsPatients oPatients = new clsPatients(true);
//lstWaitingPatients.DataContext = oPatients;
THANKS!

WPF binding user control with data in C# code

I've create user control like this:
public partial class View
{
public View()
{
InitializeComponent();
}
public static DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(TeaserView) );
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
}
XAML:
<UserControl x:Class="Controls.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="200" Width="164">
<Grid VerticalAlignment="Stretch"
x:Name="Preview">
<Label Height="28" Content="{Binding ElementName=Preview, Path=Name}" Background="LightYellow" x:Name="name" VerticalAlignment="Top" ></Label>
</Grid>
</UserControl>
and use it in Window1 simply in XAML:
<controls:View Height="200" Name="View1" Width="164" />
and I try set the Content in C# (Name property in this sample) but it does'n work, label's content is still empty. (All refereces, etc. are good) What's wrong?
Your code is wrong. You bind to Grid.Name property, which is "Preview", not to View.Name.
I really encourage you to go read from A to Z "DataBinding Overview" on MSDN. It worth your time, trust me :). In fact whole "Windows Presentation Foundation" section would be worth your attention.
As for your code, the following will work:
<UserControl x:Class="WpfApplication5.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300"
Width="300"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Label Height="28"
Content="{Binding Path=Name}"
Background="LightYellow"
VerticalAlignment="Top"/>
</Grid>
</UserControl>
But are you sure you want to hide "Name" property from parents?
Have you set the datacontext on the user control? Try setting it to point to its own codebehind:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
I've put the Name property just as sample. I'm trying to set Label Content in Window1.xaml.cs like:
View1.Name = "Casablanca";
Try the following binding, it should work:
<Label Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:View}}, Path=Name}" />
You should also define a xmlns:local="whatever_path_you_have" on the top of the file.
I also suggest renaming "Name" DP to something else to avoid name collusion.
Copied your exact code and it works fine.
However, it's not doing what you're probably expecting it to do. You're setting the source of the binding to the Grid instance. Therefore, the Name property will yield "Preview". The Name property you've defined in your UserControl is ignored because there's already a Name property on UserControl.

Resources