Binding Items inside a DataTemplate with Items from another DataTemplate - wpf

I have two Data Template (one for drawing[draw] and another for Input Data[data]) Also I have the two ContentControls which uses the above DataTemplates.
I want the both DataTemplate's elements to be binded so that when the user fills in a field in the data form DateTemplate it automatically updates the draw Template as well.
How can I bind the elements in draw DataTemplate with the elements of data DataTemplate.
There is no backend data at all. User picks up a value from a combobox and based upon the value selected in combobox I update the two ContentControls with relevant draw and data DataTemplates. User fill in the relevant fields in the data form and draw template draws those elements based upon some business Rules.
-----
<DataTemplate x:Key="data">
<Grid Grid.Row="0" Background="#FFFFFFFF" Name="DocumentRoot" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid Margin="10" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<TextBlock Text="Heading Text" Grid.Row="1"/>
<TextBlock Text="Ticket Text" Grid.Row="2"/>
-----
<TextBox x:Name="txtHeading" Text="Heading Text" Grid.Row="1" Grid.Column="1"/>
<TextBox x:Name="txtTicketText" Text="Ticket Text" Grid.Row="2" Grid.Column="1"/>
-----
</Grid>
</Grid>
</DataTemplate>
<ContentControl Content="{Binding ElementName=cboTemplates, Path=SelectedItem.Name}"
ContentTemplateSelector="{StaticResource formTemplateSelector}">
</ContentControl>
Any ideas how can I bind the two elements from inside different DataTemplates?
Thanks in advance

Consider creating class (named View Model) and bind both templates to single instance of that class (this is Model-View-ViewModel design pattern). Otherwise you probably will have very complex bindings contains hardcoded logical tree.

Why don't you bind one object (of class with a Draw property and a Data property) to both the templates. When one template changes Data property in the object, you can refresh Draw property in the object which in turn will update the Draw template.
Updated
Example :
Window Content
<Grid>
<StackPanel>
<ContentControl DataContext="{Binding}">
<ContentControl.Template>
<ControlTemplate>
<Rectangle Fill="{Binding Background}"
Width="200"
Height="200" />
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
<ContentControl DataContext="{Binding}">
<ContentControl.Template>
<ControlTemplate>
<TextBox Text="{Binding ColorText}" />
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
</StackPanel>
</Grid>
Code Behind
public partial class MultiViewWindow : Window
{
public MultiViewWindow()
{
InitializeComponent();
DataContext = new BackgroundInfo();
}
}
public class BackgroundInfo : INotifyPropertyChanged
{
protected String _colorText;
public String ColorText
{
get
{
return _colorText;
}
set
{
_colorText = value;
RaisePropertyChanged("ColorText");
RaisePropertyChanged("Background");
}
}
public Brush Background
{
get
{
try
{
return new SolidColorBrush((Color)ColorConverter.ConvertFromString(ColorText));
}
catch (Exception)
{
return new SolidColorBrush(Colors.Transparent);
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(String propertyName)
{
PropertyChangedEventHandler temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Related

Caliburn.Micro : How to bind a specific Item of Conductor.Collection.AllActive to a ContentControl

My goal is to have 4 different active ViewModels displayed in a grid on the ShellView. The issue is that I have not been able to figure out how to wire up a ContentControl to a specific Item in Items of the Conductor. How can his be done?
Here is a simplified version of what I and trying to do.
SolutionExplorer
ShellViewModel:
namespace ContentControlTest.ViewModels
{
public class ShellViewModel : Conductor<object>.Collection.AllActive
{
public ShellViewModel()
{
ActivateItem(new UC1ViewModel());
ActivateItem(new UC2ViewModel());
ActivateItem(new UC3ViewModel());
ActivateItem(new UC4ViewModel());
}
}
}
ShellView:
<Window x:Class="ContentControlTest.Views.ShellView"
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"
xmlns:local="clr-namespace:ContentControlTest.Views"
xmlns:cal="http://www.caliburnproject.org"
mc:Ignorable="d"
Title="ShellView" Height="450" Width="800"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Row="0" Grid.Column="0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ContentControl cal:View.Model="{Binding UC1ViewModel}" cal:View.Context="{Binding Items[0]}"/>
</ScrollViewer>
<ScrollViewer Grid.Row="0" Grid.Column="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ContentControl cal:View.Model="{Binding UC2ViewModel}" cal:View.Context="{Binding Items[1]}"/>
</ScrollViewer>
<ScrollViewer Grid.Row="1" Grid.Column="0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ContentControl cal:View.Model="{Binding UC3ViewModel}" cal:View.Context="{Binding Items[2]}"/>
</ScrollViewer>
<ScrollViewer Grid.Row="1" Grid.Column="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ContentControl cal:View.Model="{Binding UC4ViewModel}" cal:View.Context="{Binding Items[3]}"/>
</ScrollViewer>
</Grid>
</Window>
For simplification each UserControl ViewModel and View are Identical:
UC#ViewModel:
namespace ContentControlTest.ViewModels
{
public class UC1ViewModel : Screen
{
private string id;
public string ID
{
get { return id; }
set
{
id = value;
NotifyOfPropertyChange(() => ID);
}
}
public UC1ViewModel()
{
ID = Guid.NewGuid().ToString();
}
}
}
UC#View:
<UserControl x:Class="ContentControlTest.Views.UC1View"
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:local="clr-namespace:ContentControlTest.Views"
xmlns:cal="http://www.caliburnproject.org"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
>
<Border BorderBrush="Black" BorderThickness="1">
<StackPanel >
<TextBlock Text="{Binding DisplayName}"/>
<TextBlock Text="{Binding ID}"/>
</StackPanel>
</Border>
</UserControl>
For testing I have tried using an ItemControl and it works but doesn't give me exactly what I want.
<ItemsControl x:Name="Items">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel></StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
You need to create properties in your ShellViewModel something like UC1, UC2, UC3 etc. You then need to change your ShellView to bind to UC1 property.
<ContentControl x:Name="UC1" />
...
Caliburn Micro should do the plumbing for you.
namespace ContentControlTest.ViewModels
{
public class ShellViewModel : Conductor<object>.Collection.AllActive
{
// Modify to implement INotifyPropertyChanged event...
public UC1ViewModel UC1 { get; set }
public ShellViewModel()
{
UC1 = new UC1ViewModel();
ActivateItem(UC1);
ActivateItem(new UC2ViewModel());
ActivateItem(new UC3ViewModel());
ActivateItem(new UC4ViewModel());
}
}
}
The Caliburn concept of Context is used to map a view model to multiple views, usually through conventions and mapping namespaces. In this case, however, each of your view models maps to exactly one view. Hence you don't need to / should not provide a context.
Second, your view model binding cannot be resolved without exposing them as public props (as #Jack suggested). Ironically, the binding you used for Context is the right one for the view model binding.
Replacing
<ContentControl cal:View.Model="{Binding UC1ViewModel}" cal:View.Context="{Binding Items[0]}"/>
With
<ContentControl cal:View.Model="{Binding Items[0]}"/>
Should do the trick.
Given the number of items is fixed it's better to follow #Jack's approach and reference the view models in a strongly typed fashion. Rather than relying on their index in the items collection. You can use either:
<ContentControl cal:View.Model="{Binding UC1ViewModel}" />
Or
<ContentControl x:Name="UC1ViewModel" />
Which are synonymous.
As you noticed the Caliburn Conductor really shines when used in combination with ItemControl. You typically don't need to have strongly typed references to the each of the Items then. That doesn't mean you can't use the conductor as you did, you still enjoy all the benefits of the managed lifecycle.
In case anyone is having an issue implementing the [perfectly fine] accepted answer, here is a more in depth answer:
Your main window that contain both (or even more than two) of your User Controls must be inherited from Caliburn.Micro.Conductor<Screen>.Collection.AllActive;
Your User Controls must be inherited from Caliburn.Micro.Screen;
You must also keep naming conventions in mind. If you use MenuUC as the name of a ContentControl in your View, also create a property named MenuUC in your ViewModel;
Initialize your UserControl as I do in Constructor;
Now you can use ActivateItem(MenuUC) and DeactivateItem(MenuUC) everywhere in your code. Caliburn.Micro automatically detects which one you want to work with.
Example XAML View code:
<Window x:Class="YourProject.Views.YourView"
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"
Title="YourViewTitle" Width="900" Height="480">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="4*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Menu Side Bar -->
<ContentControl Grid.Row="0" Grid.Column="0" x:Name="MenuUC" />
<!-- Panel -->
<Border Grid.Column="1" Grid.RowSpan="2" BorderThickness="1,0,0,0" BorderBrush="#FF707070" >
<ContentControl x:Name="PanelUC" />
</Border>
</Grid>
</Window>
Example C# ViewModel code:
class YourViewModel : Conductor<Screen>.Collection.AllActive
{
// Menu Side Bar
private MenuUCViewModel _menuUC;
public MenuUCViewModel MenuUC
{
get { return _menuUC; }
set { _menuUC = value; NotifyOfPropertyChange(() => MenuUC); }
}
// Panel
private Screen _panelUC;
public Screen PanelUC
{
get { return _panelUC; }
set { _panelUC = value; NotifyOfPropertyChange(() => PanelUC); }
}
// Constructor
public YourViewModel()
{
MenuUC = new MenuUCViewModel();
ActivateItem(MenuUC);
PanelUC = new FirstPanelUCViewModel();
ActivateItem(PanelUC);
}
// Some method that changes PanelUC (previously FirstPanelUCViewModel) to SecondPanelUCViewModel
public void ChangePanels()
{
DeactivateItem(PanelUC);
PanelUC = new SecondPanelUCViewModel();
ActivateItem(PanelUC);
}
}
In the above example, ChangePanels() acts as a method to load new User Control into your ContentControl.
Also read this question, it might be help you further.

Fill textbox with a property with Binding MVVM

I try to figure out how MVVM works and want to start simple.
I made a Model, a View and a ViewModel. I hooked up the view to a window. this works more or less.
in the view i have a textbox which i want to fill with the value of a property. The textbox keeps empty!?
This is what i have:
Model:
namespace Qbox_0001.Model
{
public class RegistratieModel
{
}
public class Registratie : INotifyPropertyChanged
{
//Fields
private string programmaNaam;
//eventhandler die kijkt of een property wijzigt
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
//Properies
public string ProgrammaNaam
{
get { return programmaNaam; }
set
{
if (programmaNaam != value)
{
programmaNaam = value;
RaisePropertyChanged("ProgrammaNaam");
}
}
}
}
}
The View:
<UserControl x:Name="UserControlRegistratie" x:Class="Qbox_0001.Views.RegistratieView"
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:local="clr-namespace:Qbox_0001.Views"
mc:Ignorable="d" Height="1000" Width="860" MaxWidth="860" HorizontalContentAlignment="Left" VerticalContentAlignment="Top">
<Grid x:Name="GridRegistratie">
<Grid.RowDefinitions>
<RowDefinition x:Name="RowDef_Menu" Height="21*" MaxHeight="21" MinHeight="21"/>
<RowDefinition x:Name="RowDef_TabControl" Height="500*" MinHeight="500"/>
<RowDefinition x:Name="Rowdef_Bottom" Height="40*" MaxHeight="40" MinHeight="40"/>
</Grid.RowDefinitions>
<Grid x:Name="Grid_Registratie_WorkArea" Grid.Row="1">
<TabControl x:Name="TabControl_Registratie">
<TabItem Header="Registratie">
<Grid x:Name="Grid_Tab_Registratie">
<Border>
<Grid>
<Grid.RowDefinitions>
<RowDefinition x:Name="GridRowDef_Algemeen" MinHeight="68" Height="68*" MaxHeight="68"/>
<RowDefinition x:Name="GridRowDef_Locatie" MinHeight="120" Height="120*" MaxHeight="120"/>
<RowDefinition x:Name="GridRowDef_AantalDagen" MinHeight="105" Height="105*" MaxHeight="105"/>
<RowDefinition x:Name="GridRowDef_MaxDagen" MinHeight="105" Height="105*" MaxHeight="105"/>
<RowDefinition x:Name="GridRowDef_Lokaal" MinHeight="100" Height="100*" MaxHeight="100"/>
<RowDefinition x:Name="GridRowDef_LicBestand" Height="150*" MaxHeight="150" MinHeight="150"/>
</Grid.RowDefinitions>
<GroupBox x:Name="GroupBox_algemeen" Header="Algemeen" Margin="10,4,10,3">
<Grid>
<Label x:Name="Label_Klant" Content="Klant:" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Padding="0,5,5,5"/>
<Label x:Name="Label_Programma" Content="Programma:" HorizontalAlignment="Left" Margin="356,10,0,0" VerticalAlignment="Top"/>
<Label x:Name="Label_Versie" Content="Versie:" HorizontalAlignment="Left" Margin="645,10,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="textBox_KlantNaam" HorizontalAlignment="Left" Height="23" Margin="49,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="288"/>
<!-- the textbox keeps empty -->
<TextBox x:Name="TextBox_ProgrammaNaam" Text="{Binding ElementName=RegistratieViewModel, Path=ProgrammaNaam, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Height="23" Margin="431,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="190" IsEnabled="False" />
<TextBox x:Name="TextBox_Versie" HorizontalAlignment="Left" Height="23" Margin="695,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" IsEnabled="False" />
</Grid>
</GroupBox>
</Grid>
</Border>
</Grid>
</TabItem>
<TabItem Header="Licentie(s)">
<Grid x:Name="Grid_Tab_Licentie" Background="#FFE5E5E5"/>
</TabItem>
</TabControl>
</Grid>
</Grid>
</UserControl>
In the View.cs:
namespace Qbox_0001.Views
{
/// <summary>
/// Interaction logic for RegistratieView.xaml
/// </summary>
public partial class RegistratieView : UserControl
{
public RegistratieView()
{
InitializeComponent();
this.DataContext = new Qbox_0001.ViewModel.RegistratieViewModel();
}
}
}
The ViewModel
using Qbox_0001.Model; //
using System.Collections.ObjectModel; //
namespace Qbox_0001.ViewModel
{
public class RegistratieViewModel
{
public RegistratieViewModel()
{
loadRegistratieOnderdelen();
}
public ObservableCollection<Registratie> RegistratieOnderdelen //Registratie = "public class Registratie : INotifyPropertyChanged" van de Model
{
get;
set;
}
public void loadRegistratieOnderdelen()
{
ObservableCollection<Registratie> regOnderdelen = new ObservableCollection<Registratie>();
regOnderdelen.Add(new Registratie { ProgrammaNaam = "Test" });
}
}
}
I can see a couple of problems with your code.
You are populating a local ObservableCollection (inside your loadRegistratieOnderdelen() method) with data but since its local, it is not a member of the DataContext and hence unavailable to the View. You have to use public properties like RegistratieOnderdelen which you already declared in your RegistratieViewModel.
Next you are using an ObservableCollection whereas you might just want to use a property of type String. Collections are used when you want to represent lists, for example inside a ListView or an ItemsControl. Your view indicates that you want to bind a single text value so a public property of type String makes more sense.
Best, Nico
The DataContext is a RegistratieViewModel. This class has a RegistratieOnderdelen property that returns a collection of Registratie objects.
You could bind to the ProgrammaNaam property of one such item but you need to specify which item to bind to, for example the first one:
<TextBox x:Name="TextBox_ProgrammaNaam" Text="{Binding Path=RegistratieOnderdelen[0].ProgrammaNaam, UpdateSourceTrigger=PropertyChanged}" ... />

It is possible to do 'simple' binding with a UserControl?

I have a user control in which I have some textblocks. I want to include this usercontrol into a listBox (or listview in case it causes any problems).
When I check the output windows, I see no binding exception, but I don't see anything in the textblock either.
Is there anyway to make this work ?
Thanks :
Here is the listBox I use for now :
<ListBox AllowDrop="True" Grid.Row="1"
Style="{StaticResource BaseListBox}" x:Name="LstEquipeDefaut">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<my:ucEquipe x:Name="ucEquipe" Grid.Row="1" Margin="5,0,5,2"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here is the Usercontrol :
<UserControl x:Class="ucEquipe"
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"
mc:Ignorable="d"
d:DesignHeight="350" d:DesignWidth="180" MinWidth="180" >
<Border Style="{StaticResource UControlBorder}">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="32" />
<RowDefinition Height="25" />
<RowDefinition Height="35" />
<RowDefinition Height="100" />
<RowDefinition Height="35" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<TextBox AllowDrop="True" x:Name="TxtChiefEquipe"
Style="{StaticResource BaseTextBox}"
Text="{Binding Mode=OneWay,Path=chefEquipe.NomComplet}"
Grid.Row="1" />
</Grid>
</Border>
Here is the objets I use :
Public Class Equipe
Public Property ID As Long = 0
Public Property Couleur As String = ""
Public Property Semaine As New Date(1900, 1, 1)
Public Property chefEquipe As Employe = Nothing
Public Property ListEquipeEmploye As New List(Of EquipeEmploye)
Public Property ListEquipeEquipement As New List(Of EquipeEquipement)
End Class
The objet Employe have a property called NomComplet. For now I manually added new objects in the listbox for testing.
Your Equipe class needs to implement INotifyPropertyChanged
private Employe _chefEquipe;
public Employe ChefEquipe
{
get { retun _chefEquipe; }
set
{
_chefEquipe = value;
NotifyPropertyChanged("ChefEquipe");
}
}
Sorry about the C#, I don't remember the VB syntax anymore =)

DependencyProperty in UserControl not binding in ListItem Template

Ok hours of google searching and stakoverflow reading, I have been unable to find the answer to this issue.
I have a UserControl that is used to show a ProgressBar with a DependencyProperty of type double.
The MainPage.XAML.cs contains the DataContext:
void MainPage_Loaded(object sender,RoutedEventArgs e)
{
setDataContext();
MainGameListBox.ItemsSource = vm.GameList;
}
This is whats in the MainPage.XAML:
<ListBox Grid.Row="1" Grid.ColumnSpan="2" x:Name="MainGameListBox"
SelectionChanged="listBoxGameSearch_SelectionChanged" >
<!-- set its ItemsPanel to be a WrapPanel -->
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit1:WrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<toolkit1:ContextMenuService.ContextMenu>
<toolkit1:ContextMenu>
<toolkit1:MenuItem Header="Pin to start" Click="PinGameToStart_Click" />
</toolkit1:ContextMenu>
</toolkit1:ContextMenuService.ContextMenu>
<Grid Width="173" Height="173"
Background="{StaticResource PhoneAccentBrush}" Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="86.5"/>
<ColumnDefinition Width="86.5"/>
</Grid.ColumnDefinitions>
<Border Grid.Row="0" Grid.RowSpan="3" Grid.Column="0" Width="64"
Height="64" BorderBrush="#70BC1F" BorderThickness="2"
Margin="6,6,0,0" VerticalAlignment="Top"
HorizontalAlignment="Left">
<Image Source="{Binding GameTile,
Converter={StaticResource imageCacheConverter}}" />
</Border>
<view:CircularProgressChart x:Name="circularProgChart"
Grid.Row="0" Grid.Column="1"
Grid.RowSpan="3" Grid.ColumnSpan="2"
Margin="6"
Loaded="CircularProgressChart_Loaded"
CompletionPercent="{Binding CompletionPercentage}" />
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The CompletionPercent is the DP and the UserControl is below:
public partial class CircularProgressChart:UserControl
{
public double CompletionPercent
{
get { return (double)GetValue(CompletionPercentProperty); }
set { SetValue(CompletionPercentProperty, value); }
}
public static readonly DependencyProperty CompletionPercentProperty = DependencyProperty.Register("CompletionPercent", typeof(double), typeof(CircularProgressChart), new PropertyMetadata(0.0, CompletionPercentChanged));
public CircularProgressChart()
{
try
{
InitializeComponent();
DataContext = this;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
Here's the CompletionPercentage Property:
public class Progress : INotifyPropertyChanged
{
private double _completionPercentage = 0.0;
public double CompletionPercentage
{
get{return _completionPercentage;}
set{
_completionPercentage = value;
NotifyPropertyChanged("CompletionPercentage");
}
}
protected void NotifyPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Question, is why is the
CompletionPercent="{Binding CompletionPercentage}"
not being bound? It gets the default value 0, but when the CompletionPercentage is updated the DP doesn't get the update. I've checked the NotifyPropertyChanged method and it fires correctly and works in all other parts of code.
The reason is DataContext = this line in CircularProgressChart constructor. {Binding CompletionPercentage} looks for CompletionPercentage property in DataContext, which obviously does not exist in CircularProgressChart. Instance of Progress class never makes it's way to the CircularProgressChart (explicit assignment of dependency property has a priority over binding).
Solution 1 (if you really want to keep dependency property): remove the DataContext = this; line. If you need to bind to properties of CircularProgressChart in it's XAML, specify correct binding sources instead of relying on DataContext.
Solution 2 (very common when deriving from UserControl): remove CompletionPercent dependency property completely (including its binding in MainPage.xaml) and bind to CompletionPercentage directly in CircularProgressChart.xaml. Also remove DataContext = this; line.
Solution 3 (true WPF-way): use default ProgressBar control with a custom template instead of creating your own control.

Binding Silverlight UserControl custom properties to its' elements

I'm trying to make a simple crossword puzzle game in Silverlight 2.0. I'm working on a UserControl-ish component that represents a square in the puzzle. I'm having trouble with binding up my UserControl's properties with its' elements. I've finally (sort of) got it working (may be helpful to some - it took me a few long hours), but wanted to make it more 'elegant'.
I've imagined it should have a compartment for the content and a label (in the upper right corner) that optionally contains its' number. The content control probably be a TextBox, while label control could be a TextBlock. So I created a UserControl with this basic structure (the values are hardcoded at this stage):
<UserControl x:Class="XWord.Square"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FontSize="30"
Width="100" Height="100">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock x:Name="Label" Grid.Row="0" Grid.Column="1"
Text="7"/>
<TextBox x:Name="Content" Grid.Row="1" Grid.Column="0"
Text="A"
BorderThickness="0" />
</Grid>
</UserControl>
I've also created DependencyProperties in the Square class like this:
public static readonly DependencyProperty LabelTextProperty;
public static readonly DependencyProperty ContentCharacterProperty;
// ...(static constructor with property registration, .NET properties
// omitted for brevity)...
Now I'd like to figure out how to bind the Label and Content element to the two properties. I do it like this (in the code-behind file):
Label.SetBinding( TextBlock.TextProperty, new Binding { Source = this, Path = new PropertyPath( "LabelText" ), Mode = BindingMode.OneWay } );
Content.SetBinding( TextBox.TextProperty, new Binding { Source = this, Path = new PropertyPath( "ContentCharacter" ), Mode = BindingMode.TwoWay } );
That would be more elegant done in XAML. Does anyone know how that's done?
First, set the DataContext on the UserControl using {RelativeSource Self}:
<UserControl x:Class="XWord.Square"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FontSize="30"
Width="100" Height="100"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
Now you can bind the individual elements to the properties of the usercontrol:
<TextBlock x:Name="Label" Grid.Row="0" Grid.Column="1"
Text="{Binding LabelText}"/>
<TextBox x:Name="Content" Grid.Row="1" Grid.Column="0"
Text="{Binding ContentCharacter}" BorderThickness="0" />
For SL 2.0, you'll need to set the DataContext on the UserControl's Loaded event handler.
private void UserControl_Loaded( object sender, RoutedEventArgs e ) {
LayoutRoot.DataContext = this;
}
As Silverlight cannot use FindAncestor technique you can use a trick similar to the one that sets the UserControl's name, but without breaking its functionality by using the name of the LayoutRoot...
<UserControl x:Class="XWord.Square"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FontSize="30"
Width="100" Height="100">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock x:Name="{Binding Path=Parent.LabelText, ElementName=LayoutRoot}" Grid.Row="0" Grid.Column="1"
Text="7"/>
<TextBox x:Name="{Binding Path=Parent.ContentCharacter, ElementName=LayoutRoot}" Grid.Row="1" Grid.Column="0"
Text="A"
BorderThickness="0" />
</Grid>
</UserControl>
It worked in SL3 without having to add any additional code (I'm using it in a WP7 app), but don't know if you can use it in SL2. Well, I realize now how this question is old, hope it's still helpful, I've arrived here because the answers I got for the same problem in WP7 didn't convince me.
I think you are looking for UI Element to Element Binding which is a feature of Silverlight 3.
I may not be understanding your issue exactly. In Silverlight, you are able to bind to almost any data object. So, if you have a PuzzleSquare class that contains properties Content and Label, you may bind to these properties directly from the object.
Let's say you created a simple object PuzzleSquare:
public class PuzzleSquare
{
public string Content{ get; set; }
public string Label{ get; set; }
public void PuzzleSquare(){};
public void PuzzleSquare(string label, string content):this()
{
Content = content;
Label = label;
}
}
So, if you are building the app with the classic view/code behind model, your code behind would add this object to the DataContext property of the grid on page load:
LayoutRoot.DataContext = new PuzzleSquare("1", "A");
Your Xaml would bind to the Square property:
<TextBlock x:Name="Label" Grid.Row="0" Grid.Column="1"
Text="{Binding Label}"/>
<TextBox x:Name="Content" Grid.Row="1" Grid.Column="0"
Text="{Binding Content}" BorderThickness="0" />
Does that make sense?
ib.
This worked in Silverlight 4.0
Put a name on the UserControl, and then refer to it in the TextBlock
<UserControl x:Class="XWord.Square"
...omitted for brevity ...
x:Name="Square">
<TextBlock x:Name="Label" ...
Text="{Binding Path=LabelText,ElementName=Square}"/>
Try this:
Public ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(ButtonEdit), New System.Windows.PropertyMetadata("", AddressOf TextPropertyChanged))
Public Property Text As String
Get
Return GetValue(TextProperty)
End Get
Set(ByVal value As String)
SetValue(TextProperty, value)
End Set
End Property
Private Sub TextPropertyChanged()
If String.IsNullOrEmpty(Text) Then
TextBox1.Text = ""
Else
TextBox1.Text = Text
End If
End Sub
Private Sub TextBox1_LostFocus(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles TextBox1.LostFocus
Text = TextBox1.Text
End Sub
I can bind in both XAML and code behind.

Resources