How to bind wpf UserControl? - wpf

I am having a problem to binding viewmodel to my created usercontrol. The viewmodel doesn't reflect the value changes.
UpDown.xaml (Usercontrol xaml part)
btnUp and btnDown are used to change the text of txtNum. I don't know whether it is right?
<Grid DataContext="{Binding ElementName=btnDownRoot}">
<Grid.RowDefinitions>
<RowDefinition Height="0*"/>
<RowDefinition/>
<RowDefinition Height="0*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0*"/>
<ColumnDefinition Width="78*"/>
<ColumnDefinition Width="104*"/>
<ColumnDefinition Width="77*"/>
<ColumnDefinition Width="0*"/>
</Grid.ColumnDefinitions>
<TextBox Name="txtNum" Text="{Binding ElementName=btnDownRoot, Path=NumValue}" FontSize="20" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" BorderBrush="White" Grid.Column="2" Grid.RowSpan="2"/>
<Button Name="btnUp" Style="{StaticResource ResourceKey=btnUp}" Click="btnUp_Click" Grid.Column="3" Grid.RowSpan="2"/>
<Button Name="btnDown" Style="{StaticResource ResourceKey=btnDown}" Click="btnDown_Click" Grid.RowSpan="2" Grid.ColumnSpan="2"/>
</Grid>
UpDown.xaml.cs (Usercontrol codepart)
public string NumValue
{
get
{
return GetValue(NumValueProperty).ToString();
}
set
{
SetValue(NumValueProperty, value);
}
}
public static readonly DependencyProperty NumValueProperty =
DependencyProperty.Register("NumValue", typeof(string), typeof(UpDown),
new PropertyMetadata("1", new PropertyChangedCallback(OnNumChanged)));
private static void OnNumChanged(DependencyObject d,DependencyPropertyChangedEventArgs e)
{
var instannce = d.ToString();
}
In the MainWindow,I am going to bind the MainViewModel to the UserControl
MainViewModel.cs
public class MainViewModel: BaseViewModel
{
private string _myValue;
public string MyValue
{
get { return _myValue; }
set
{
_myValue = value;
OnPropertyChanged("MyValue");
}
}
}
MainWindow.xaml
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<local:UpDown Grid.Row="0" NumValue="{Binding MyValue}" x:Name="udGuestNum" Width="220" Margin="0 20"/>
<Button Name="btnOK" Grid.Row="1" Content="OK" Click="btnOK_Click"/>
</Grid>

The NumValue Binding is OneWay by default. Either explicitly set it to TwoWay
NumValue="{Binding MyValue, Mode=TwoWay}"
or make TwoWay the default:
public static readonly DependencyProperty NumValueProperty =
DependencyProperty.Register(
"NumValue", typeof(string), typeof(UpDown),
new FrameworkPropertyMetadata(
"1", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnNumChanged));

numeric up/down control have already been developed...don't waste your time, you can even grab the code > https://github.com/xceedsoftware/wpftoolkit

Related

How to align controls in WPF custom user controls?

So I programmed a WPF custom user control like this:
XAML:
<UserControl x:Class="PEINC.LableWithText"
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:PEINC"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" x:Name="label" Content="{Binding Beschriftung, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<TextBox Grid.Column="1" x:Name="feld"/>
</Grid>
</UserControl>
cs:
public partial class LableWithText : UserControl
{
public string Beschriftung
{
get => (string)GetValue(BeschriftungProperty);
set => SetValue(BeschriftungProperty, value);
}
public static readonly DependencyProperty BeschriftungProperty = DependencyProperty.Register(nameof(Beschriftung), typeof(string), typeof(LableWithText), new PropertyMetadata(string.Empty));
public LableWithText()
{
InitializeComponent();
}
}
I use them in solutionDlg.xaml like this:
<control:LableWithText Grid.Row="2" Beschriftung="Bezeichnung:"/>
<control:LableWithText Grid.Row="3" Beschriftung="Projektnummer:"/>
My problem is that the TextBoxes don't align. It looks like this:
But it should look like this:
It would be cool if the user control would be able to adjust onto a grid - or some other kind of reference line - which is provided by solutionDlg.xaml.
I tried:
I could work with a fixed Width for the column the label is in. This is my fallback option, but it removes a lot of the flexibility of my custom control + I have to set it for every instance of LableWithText.
I tried to work with Grid.ColumnSpan, but this didn't work
I worked with the SharedSizeGroup-Feature:
This is the xaml I used in my UserControl:
<UserControl x:Class="RadBaseGUIElements.LabelWithText"
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:RadBaseGUIElements"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="{Binding LabelSizeGroup, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" x:Name="label" Content="{Binding Beschriftung, RelativeSource={RelativeSource AncestorType=UserControl}}" Style="{DynamicResource FeldLabel}"/>
<TextBox Grid.Column="1" x:Name="feld" Style="{DynamicResource Feld}" Text="{Binding Inhalt, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</Grid>
</UserControl>
And this is the code I use:
public partial class LabelWithText : UserControl
{
public string Beschriftung
{
get => (string)GetValue(BeschriftungProperty);
set => SetValue(BeschriftungProperty, value);
}
public static readonly DependencyProperty BeschriftungProperty = DependencyProperty.Register(nameof(Beschriftung),
typeof(string),
typeof(LabelWithText),
new PropertyMetadata("Beschriftung:"));
public string Inhalt
{
get => (string)GetValue(InhaltProperty);
set => SetValue(InhaltProperty, value);
}
public static readonly DependencyProperty InhaltProperty = DependencyProperty.Register(nameof(Inhalt),
typeof(string),
typeof(LabelWithText),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string LabelSizeGroup
{
get => (string)GetValue(LabelSizeGroupProperty);
set => SetValue(LabelSizeGroupProperty, value);
}
public static readonly DependencyProperty LabelSizeGroupProperty = DependencyProperty.Register(nameof(LabelSizeGroup),
typeof(string),
typeof(LabelWithText),
new PropertyMetadata("Labelspalte"));
public LabelWithText()
{
LabelSizeGroup = "Labelspalte";
InitializeComponent();
}
}
So I can use my User Control like this:
<Grid Grid.IsSharedSizeScope="true">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" SharedSizeGroup="Labelspalte"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
...
<control:LabelWithText Grid.Row="1" Beschriftung="SVN-Trunk-Pfad:" Grid.ColumnSpan="2" Inhalt="{Binding SvnTrunkFolder}"/>
</Grid>

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.

object sender is always null in RelayCommand

I am using RelayCommand to handle a button click, I need to get the sender parameter but it is always null, any idea why?
ViewModel.cs
private RelayCommand _expandClickCommand;
public ICommand ExpandClickCommand
{
get
{
if (_expandClickCommand == null)
{
_expandClickCommand = new RelayCommand(ExpandClickCommandExecute, ExpandClickCommandCanExecute);
}
return _expandClickCommand;
}
}
public void ExpandClickCommandExecute(object sender)
{
//sender is always null when i get here!
}
public bool ExpandClickCommandCanExecute(object sender)
{
return true;
}
View.xaml
<ListBox ItemsSource="{Binding Path=MyList}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Grid.Column="0" Grid.Row="0" Content="Expand" Command="{Binding DataContext.ExpandClickCommand,ElementName=SprintBacklog}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I need to get the index of the currect ListboxItem in ExpandClickCommand
That object in all likelihood is not the sender but the CommandParameter that is passed by the control. You could bind the CommandParameter of the button to itself to immitate the sender.
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
(But that might not really help you that much, so think about what you pass in that helps you get that value.)

How to propagate silverlight usercontrol properties to parent?

I would like to have some of the properties in the custom user control to be available to the parent page. I created a small sample to illustrate what I am looking for.
I am trying to use MVVM pattern and all the binding mechanisms to achieve it.
USERCONTROL XAML
<UserControl x:Class="TestCustomUserControl.MyControls.UserNameControl"
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:TestCustomUserControl.ViewModels"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<local:UserNameViewModel x:Key="TheViewModel"/>
</UserControl.Resources>
<Grid x:Name="NameCtrlRoot" Background="White" DataContext="{StaticResource TheViewModel}">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="First Name:" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Last Name: "/>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="txtFirstName" Text="{Binding FirstName, Mode=TwoWay}">
<i:Interaction.Behaviors>
<!-- This behavior updates the binding after a specified delay
instead of the user having to lose focus on the TextBox. -->
<local:TextChangedDelayedBindingBehavior RefreshTimeMilliseconds="750" />
</i:Interaction.Behaviors>
</TextBox>
<TextBox Grid.Row="1" Grid.Column="1" x:Name="txtLastName" Text="{Binding LastName, Mode=TwoWay}">
<i:Interaction.Behaviors>
<!-- This behavior updates the binding after a specified delay
instead of the user having to lose focus on the TextBox. -->
<local:TextChangedDelayedBindingBehavior RefreshTimeMilliseconds="750" />
</i:Interaction.Behaviors>
</TextBox>
<TextBlock Grid.Row="3" Grid.Column="0" Text="fullname inside control:" />
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding FullName}" />
<Border Height="1" Background="Black" Grid.Column="2" Grid.Row="4" />
</Grid>
</StackPanel>
</Grid>
the above Usercontrol is binded to the following VIEWMODEL
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public class UserNameViewModel : BaseViewModel
{
private String _firstName;
public String FirstName
{
get { return _firstName; }
set
{
_firstName = value;
NotifyPropertyChanged("FirstName");
OnNameChange();
}
}
private String _lastName;
public String LastName
{
get { return _lastName; }
set
{
_lastName = value;
NotifyPropertyChanged("LastName");
OnNameChange();
}
}
private void OnNameChange()
{
FullName = String.Format("{0} {1}", FirstName, LastName);
}
public String _fullName;
public String FullName
{
get { return _fullName; }
set {
_fullName = value;
NotifyPropertyChanged("FullName");
}
}
}
Consumer Page that uses the above USERCONTROL
<navigation:Page xmlns:my="clr-namespace:TestCustomUserControl.MyControls" x:Class="TestCustomUserControl.Views.ConsumeName"
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"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
d:DesignWidth="640" d:DesignHeight="480"
Title="ConsumeName Page">
<Grid x:Name="LayoutRoot">
<StackPanel>
<my:UserNameControl x:Name="MyNameControl"/>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Full Name in Parent: " />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding FullName, ElementName=MyNameControl}"/>
</Grid>
</StackPanel>
</Grid>
Here is my question now, If you look at the view model associated with user control, it has a property called FullName and I would like that to be exposed via Usercontrol, so that I can access it from the consuming page. Its like consuming page want to access some of the properties of usercontrol. I am not quite sure as to how that can be acheived. I would like to stick with MVVM pattern.
you have already decalred a StaticResource, so you can use it in both views.
Do this
<UserControl.Resources>
<local:UserNameViewModel x:Key="TheViewModel"/>
</UserControl.Resources>
in the "ConsumeName Page". If yous simply add the DataContext to your Grid
<Grid x:Name="LayoutRoot" DataContext="{StaticResource TheViewModel}">
this should work. (You don't need ElementName=MyNameControl any longer).
The my:UserNameControl should inherit the DataContext. If not, you have to add it here.
<my:UserNameControl DataContext="{StaticResource TheViewModel}"/>
This should work.
Right now the local:UserNameViewModel with the key TheViewModel is only achievable where you defined it. If you define it in your app.xaml you can access it from everywhere in the app.
Hope this hels.
BR,
TJ
I am answering my own question. But before answering my question, just want to clarify somethings. I was trying to create a fully encapsulated UserControl with its own ViewModel. And where ever the usercontrol is consumed, the consumer should know nothing about the usercontrol's internal viewmodel. Only communication option I want the consumer to have is by setting some properties and using binding mechanism.
So the way I resolved my problem is, I created dependency property inside the UserControl and setting it whenever something changes in the usercontrol's viewmodel.
UserNameControl.xaml (usercontrol)
<UserControl x:Class="TestCustomUserControl.MyControls.UserNameControl"
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:TestCustomUserControl.ViewModels"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<local:UserNameViewModel x:Key="TheViewModel"/>
</UserControl.Resources>
<Grid x:Name="NameCtrlRoot" Background="White" DataContext="{StaticResource TheViewModel}">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="First Name:" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Last Name: "/>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="txtFirstName" Text="{Binding FirstName, Mode=TwoWay}">
<i:Interaction.Behaviors>
<!-- This behavior updates the binding after a specified delay
instead of the user having to lose focus on the TextBox. -->
<local:TextChangedDelayedBindingBehavior RefreshTimeMilliseconds="750" />
</i:Interaction.Behaviors>
</TextBox>
<TextBox Grid.Row="1" Grid.Column="1" x:Name="txtLastName" Text="{Binding LastName, Mode=TwoWay}">
<i:Interaction.Behaviors>
<!-- This behavior updates the binding after a specified delay
instead of the user having to lose focus on the TextBox. -->
<local:TextChangedDelayedBindingBehavior RefreshTimeMilliseconds="750" />
</i:Interaction.Behaviors>
</TextBox>
<TextBlock Grid.Row="3" Grid.Column="0" Text="fullname inside control:" />
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding FullName}" />
<Border Height="1" Background="Black" Grid.Column="2" Grid.Row="4" />
</Grid>
</StackPanel>
</Grid>
BaseViewModel.cs
public class BaseViewModel : INotifyPropertyChanged
{
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
UserNameViewModel.cs
public class UserNameViewModel : BaseViewModel
{
private String _firstName;
public String FirstName
{
get { return _firstName; }
set
{
_firstName = value;
NotifyPropertyChanged("FirstName");
OnNameChange();
}
}
private String _lastName;
public String LastName
{
get { return _lastName; }
set
{
_lastName = value;
NotifyPropertyChanged("LastName");
OnNameChange();
}
}
public Action NameChanged { get; set; }
private void OnNameChange()
{
FullName = String.Format("{0} {1}", FirstName, LastName);
if(NameChanged != null) NameChanged.Invoke();
}
private String _fullName;
public String FullName
{
get { return _fullName; }
set {
_fullName = value;
NotifyPropertyChanged("FullName");
}
}
}
UserNameControl.xaml.cs (Usercontrol code behind with DependencyProperty declaration)
public partial class UserNameControl : UserControl
{
private UserNameViewModel _TheViewModel;
public UserNameControl()
{
InitializeComponent();
_TheViewModel = Resources["TheViewModel"] as UserNameViewModel;
_TheViewModel.NameChanged = OnNameChanged;
}
public String SelectedFullName
{
get { return (String) GetValue(SelectedFullNameProperty); }
set { SetValue(SelectedFullNameProperty, value); }
}
public static readonly DependencyProperty SelectedFullNameProperty =
DependencyProperty.Register("SelectedFullName", typeof (String), typeof (UserNameControl), null);
private void OnNameChanged()
{
SelectedFullName = _TheViewModel.FullName;
}
}
ConsumeName.xaml (Consumer navigation Page, user above usercontrol and pulls SelectedFullName into UserFullName)
<navigation:Page xmlns:my="clr-namespace:TestCustomUserControl.MyControls" x:Class="TestCustomUserControl.Views.ConsumeName"
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"
xmlns:local="clr-namespace:TestCustomUserControl.ViewModels"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
d:DesignWidth="640" d:DesignHeight="480"
Title="ConsumeName Page">
<navigation:Page.Resources>
<local:ConsumeNameViewModel x:Key="TheConsumeNameViewModel"/>
</navigation:Page.Resources>
<Grid x:Name="LayoutRoot" DataContext="{StaticResource TheConsumeNameViewModel}">
<StackPanel>
<my:UserNameControl x:Name="MyNameControl" SelectedFullName="{Binding UserFullName, Mode=TwoWay}" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Full Name in Parent: " />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding UserFullName}"/>
</Grid>
</StackPanel>
</Grid>
ConsumeNameViewModel.cs
public class ConsumeNameViewModel : BaseViewModel
{
private string _UserFullName;
public string UserFullName
{
get { return _UserFullName; }
set
{
if (value != _UserFullName)
{
_UserFullName = value;
NotifyPropertyChanged("UserFullName");
}
}
}
}

Silverlight 4: Pattern for displaying data with a bit of logic?

I'm building a wp7 app.
I have a UserControl that displays a news article headline, teaser, and image. The entire class is pretty short:
public partial class StoryControl : UserControl
{
public Story Story { get; private set; }
public StoryControl()
{
InitializeComponent();
}
internal StoryControl(Story story) : this()
{
this.Story = story;
Teaser.Text = story.Teaser;
Headline.Text = story.Title;
if (story.ImageSrc == null)
{
Thumbnail.Visibility = Visibility.Collapsed;
} else
{
Thumbnail.Source = new BitmapImage(story.ImageSrc);
}
}
}
And the corresponding XAML:
<Grid x:Name="LayoutRoot" Background="Transparent" Margin="0,0,0,20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image x:Name="Thumbnail" Grid.Column="0" Width="89" HorizontalAlignment="Left" VerticalAlignment="Top" />
<!-- sometimes there's a hanging word in the headline that looks a bit awkward -->
<TextBlock x:Name="Headline" Grid.Column="1" Grid.Row="0" Style="{StaticResource PhoneTextAccentStyle}" TextWrapping="Wrap" HorizontalAlignment="Left" FontSize="23.333" VerticalAlignment="Top" />
<TextBlock x:Name="Teaser" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" HorizontalAlignment="Left" Style="{StaticResource PhoneTextSubtleStyle}" TextWrapping="Wrap" VerticalAlignment="Top" Width="384"/>
</Grid>
Is there a way to do with with less code-behind and more XAML? Some way to use binding to bind the text for Headline and Teaser to properties of the Story, while not crashing if Story is null?
About about the image? I've got a bit of logic there; is there some way to automatically do that in XAML, or am I stuck with that in C#?
Looks like a ViewModel is in order:
public class StoryViewModel
{
readonly Story story;
public StoryViewModel(Story story)
{
this.story = story;
}
public string Teaser { get { return story == null ? "" : story.Teaser; } }
public string Title { get { return story == null ? "" : story.Title; } }
public bool IsThumbnailVisible { get { return story != null && story.ImageSrc != null; } }
public BitmapImage Thumbnail { get { return IsThumbnailVisible ? new BitmapImage(story.ImageSrc) : null; } }
}
Making your codebehind nice and simple:
public partial class StoryControl : UserControl
{
public Story Story { get; private set; }
public StoryControl()
{
InitializeComponent();
}
internal StoryControl(Story story)
: this()
{
this.DataContext = new StoryViewModel(story);
}
}
And your XAML becomes a set of bindings:
<Grid x:Name="LayoutRoot" Background="Transparent" Margin="0,0,0,20">
<Grid.Resources>
<BooleanToVisibilityConverter x:Key="booleanToVisiblityConverter"/>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Visibility="{Binding IsThumbnailVisible, Converter={StaticResource booleanToVisiblityConverter}}" Source="{Binding Thumbnail}" Grid.Column="0" Width="89" HorizontalAlignment="Left" VerticalAlignment="Top" />
<!-- sometimes there's a hanging word in the headline that looks a bit awkward -->
<TextBlock Text="{Binding Title}" Grid.Column="1" Grid.Row="0" Style="{StaticResource PhoneTextAccentStyle}" TextWrapping="Wrap" HorizontalAlignment="Left" FontSize="23.333" VerticalAlignment="Top" />
<TextBlock Text="{Binding Teaser}" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" HorizontalAlignment="Left" Style="{StaticResource PhoneTextSubtleStyle}" TextWrapping="Wrap" VerticalAlignment="Top" Width="384"/>
</Grid>
Ok, it would probably be possible to do this with just model (story) and view (xaml) via fallbackvalues and more complex converters, but i hope you'll find that viewmodels give you the most power in terms of testable, brick-wall-less, view-specific logic...

Resources