This question already has answers here:
XAML binding not working on dependency property?
(1 answer)
Issue with DependencyProperty binding
(3 answers)
Closed 4 years ago.
I am designing a NumericUpDownControl UserControl and have successfully implemented it in my MainWindow.xaml as follows:
<UserControl x:Class="SettingsDialog.Controls.NumericUpDownControl"
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:SettingsDialog.Controls"
xmlns:viewModels="clr-namespace:SettingsDialog.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="18"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox Text="{Binding Value}" Grid.Row="0" Grid.RowSpan="2" Height="20" VerticalContentAlignment="Center"/>
<RepeatButton Content="5" Grid.Column="1" Grid.Row="0" FontSize="8" Height="10" FontFamily="Marlett" VerticalContentAlignment="Center"/>
<RepeatButton Content="6" Grid.Column="1" Grid.Row="1" FontSize="8" Height="10" FontFamily="Marlett" VerticalContentAlignment="Center"/>
</Grid>
</UserControl>
In the code-behind of my UserControl, I have defined the following:
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value", typeof(int),
typeof(NumericUpDownControl)
);
public int Value
{
get => (int)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
It works fine as I can use it in my MainWindow.xaml like this:
<controls:NumericUpDownControl Width="100" Value="10"/>
However, when I attempt to set a ViewModel for my UserControl, the TextBox within the UserControl no longer recognizes the Value dependency property. How can I properly implement a ViewModel for my UserControl while still allowing for the ability to set the Value property from outside the control? Is there an issue with my current implementation that is causing this issue?
This should bind the Text property of the TextBox to the Value property of the UserControl regardless of its DataContext:
<TextBox Text="{Binding Value, RelativeSource={RelativeSource AncestorType=UserControl}}" Grid.Row="0" Grid.RowSpan="2" Height="20" VerticalContentAlignment="Center"/>
You cannot bind a UserControl to itself and to a ViewModel at the same time.
You would have to remove DataContext="{Binding RelativeSource={RelativeSource Self}}" from within the UserControl and make sure to pass the right DataContext to the UserControl when integrating it in your UI.
Since you are making a sort of custom control another way to solve your problem is simply to use the propertychanged callback of your dependency property and every time it changes you simply update your Textbox's Text property:
public partial class NumericUpDownControl : UserControl
{
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(int),typeof(NumericUpDownControl),new PropertyMetadata(OnValueChanged));
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((NumericUpDownControl) d).TextBox.Text = e.NewValue?.ToString() ?? string.Empty;
}
public int Value
{
get => (int)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public NumericUpDownControl()
{
InitializeComponent();
}
}
}
Firstly a Disclaimer: I have read dozens of posts with hundreds of answers and yet I have not found a solution. I have attempted to resolve this every which way I can find and yet the UI does not update.
I am writing a UWP app as a personal project. In this particular scenario I have a Grid with an assigned DataContext with a series of TextBlocks which are Bound to Properties. I am using "Binding" and not "x:Bind" due to the Grid been part of a ControlTemplate.
I have implemented the Properties with INotifyPropertyChanged.
I have backing fields.
I have stepped through the debugging and the Properties are updated and retain the values assigned.
I have tried using UpdateSourceTrigger=PropertyChanged.
I have tried different Modes - OneWay/TwoWay etc.
The issue: The UI completely ignores the changes and NEVER updates.
I have set the properties with a value within the Page Constructor, and that is set, but no changes thereafter are applied... EVER.
Please see the relevant code snippets below and maybe someone can see something I have not.
Thanks in advance.
Grid XAML:
<Grid
Grid.Row="1"
BorderBrush="{StaticResource SystemControlChromeLowAcrylicElementBrush}"
BorderThickness="2,0,2,2">
<Grid.DataContext>
<views:MenusPage />
</Grid.DataContext>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="152" />
<ColumnDefinition Width="152" />
<ColumnDefinition Width="152" />
<ColumnDefinition Width="152" />
<ColumnDefinition Width="152" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="3"
Padding="15,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontWeight="Bold"
Text="Totals" />
<Border
Grid.Row="0"
Grid.Column="3"
Margin="0,2,0,-2"
BorderBrush="{StaticResource SystemControlChromeLowAcrylicElementBrush}"
BorderThickness="2,0,0,0">
<TextBlock
Padding="10,0,0,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding CaloriesTotal, Mode=OneWay}" />
</Border>
<Border
Grid.Row="0"
Grid.Column="4"
Margin="0,2,0,-2"
BorderBrush="{StaticResource SystemControlChromeLowAcrylicElementBrush}"
BorderThickness="2,0,0,0">
<TextBlock
Padding="10,0,0,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding ProteinTotal}" />
</Border>
<Border
Grid.Row="0"
Grid.Column="5"
Margin="0,2,0,-2"
BorderBrush="{StaticResource SystemControlChromeLowAcrylicElementBrush}"
BorderThickness="2,0,0,0">
<TextBlock
Padding="10,0,0,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding FatTotal}" />
</Border>
<Border
Grid.Row="0"
Grid.Column="6"
Margin="0,2,0,-2"
BorderBrush="{StaticResource SystemControlChromeLowAcrylicElementBrush}"
BorderThickness="2,0,0,0">
<TextBlock
Padding="10,0,0,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding CarbTotal}" />
</Border>
<Border
Grid.Row="0"
Grid.Column="7"
Margin="0,2,0,-2"
BorderBrush="{StaticResource SystemControlChromeLowAcrylicElementBrush}"
BorderThickness="2,0,0,0">
<TextBlock
Padding="15,0,0,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding SugarTotal}" />
</Border>
</Grid>
Properties:
private double _caloriesTotal;
public double CaloriesTotal
{
get { return _caloriesTotal; }
set { Set(ref _caloriesTotal, value); }
}
private double _proteinTotal;
public double ProteinTotal
{
get { return _proteinTotal; }
set { Set(ref _proteinTotal, value); }
}
private double _fatTotal;
public double FatTotal
{
get { return _fatTotal; }
set { Set(ref _fatTotal, value); }
}
private double _carbTotal;
public double CarbTotal
{
get { return _carbTotal; }
set { Set(ref _carbTotal, value); }
}
private double _sugarTotal;
public double SugarTotal
{
get { return _sugarTotal; }
set { Set(ref _sugarTotal, value); }
}
PropertyChanged:
public event PropertyChangedEventHandler PropertyChanged;
private void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return;
}
storage = value;
OnPropertyChanged(propertyName);
}
private void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
Code Setting the values:
(NOTE: this is the shortened version but the same effect applies, setting the values in the constructor applies the values and UI updates but setting the values anywhere else on the page, within any method that is executed, they are ignored and the UI shows zeros.)
CaloriesTotal = 10;
ProteinTotal = 20;
FatTotal = 30;
CarbTotal = 40;
SugarTotal = 50;
I found the solution, and while I am dumbfounded as to how it actually worked while the other 'normal' solutions that should have worked did not, is beyond me.
Anyway thanks to #jsmyth886 and #Alexey for your help and suggestions in troubleshooting.
Since posting this question I have tried separating out the properties into another class, referencing <Models:MenuTypesModel x:Key="MenuTotals" /> within the <Page.Resources> block -- failed
I Tried setting the DataContext of the Page itself -- failed
The <Grid> ... </Grid> XAML in my question is, as said, part of a ControlTemplate I even moved the block out of the Control Template and onto the Page itself to eliminate any clashing with other datasources - mind you all different naming structures and paths, so ultimately should not have clashed -- this did not work either, so -- failed
It was #Alexey response that got me thinking in a different line and my searching ultimately led me to this post Binding to Self/'this' in XAML which solved my problem.
I removed the following datacontext from the Grid:
<Grid.DataContext>
<views:MenusPage />
</Grid.DataContext>
And replaced it with this DataContext="{Binding ElementName=_this}" and made sure of my textblock bindings, the end result (Very Short Version):
<Grid ... DataContext="{Binding ElementName=_this}">
...
<TextBlock ... Text="{Binding CaloriesTotal}" />
...
</Grid>
And finally added the a Name attribute to the Page itself:
x:Name="_this"
And it worked!!!!
I have changes nothing in the way I have set my properties.
My properties are still defined as per the question, I have no viewmodels or additional classes, nor am I setting DataContext in the CodeBehind.
Simply adding DataContext="{Binding ElementName=_this}"the grid and x:Name="_this" to the Page allows the UI to reflect the changes to the properties every time.
An oddly simple solution.
A NOTE: though, while I was trying different things though, when I had the Grid on the page itself outside of the ContentTemplate, I set the DataContext of the page in codebehind DataContext = this; and it started populating but still failed to affect the Grid in the Control template.
So unless there is a clash that I didn't see or because I have other DataSources which are unrelated as well as assigned directly the to control that needs it... I cannot figure why it works now with this simple binding.
Anyway, thanks for the assistance and I hope this helps someone else in the future.
If you want to benefit from NavigationCacheMode (Enabled/Required) for the sake of performance, you have to update the bindings on data context change.
public MainPage()
{
this.InitializeComponent();
this.DataContextChanged += (s, e) => this.Bindings.Update();
}
Please use binding DataContext for your main object, not for DataGrid:
<Window.DataContext>
<views:MenusPage />
</Window.DataContext>
I have a problem with binding a button located in a sidebar in my windows phone app. It seems like the buttons binding just dissapears..
Here's my code at the moment
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<sidebar:SidebarControl x:Name="sidebarControl"
HeaderText="WP"
HeaderBackground="YellowGreen"
HeaderForeground="White"
SidebarBackground="{StaticResource PhoneChromeBrush}">
<sidebar:SidebarControl.SidebarContent>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="380">
<Button Content="Go to page 2" x:Name="GoToPage2"/>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
</Grid>
</sidebar:SidebarControl.SidebarContent>
<Grid VerticalAlignment="Top" HorizontalAlignment="Stretch"
Margin="12">
<TextBlock Style="{StaticResource PhoneTextNormalStyle}">Your current view goes here</TextBlock>
</Grid>
</sidebar:SidebarControl>
</Grid>
</Grid>
At the moment I am using a nuget for the sidebar called SidebarWP8. Maybe Calinbrun.Micro doesnt work with this? Or do I have to insert a binding to the VM in a grid?
Here's the method in the ViewModel:
private readonly INavigationService navigationService;
public MainPageViewModel(INavigationService navigationService)
{
this.navigationService = navigationService;
}
public void GoToPage2()
{
navigationService.UriFor<Page2ViewModel>().Navigate();
}
<Button cm:Message.Attach="[Event Click] = [Action GoToPage2()]" />
This should work, because of the other commenter is correct with respect to the default controls... Custom controls require adding some more handling in which can be a pain in the butt but doing it with the short hand above CM will look for that property in the button and process accordingly.
I checked the source for Caliburn Micro: I handles the convention binding by traversing the visual tree to find elements with a name. But it only checks the default Content property for each control.
That means it will only go through the Content or Children elements and not Custom properties like SidebarContent, so named elements will not be found there.
You have to wire it up by hand (by binding a command or adding a click handler).
I have my app up and running with CM. The shell view contains multiple ContentControls which itself contain views. Those are assigned by convention as they got a name matching a property in the view model.
I got a WPF window (at least its class inherits from Window) which is opened from my app with windowManager.ShowDialog(viewModel). Inside this window again I have ContentControls but those do not bind to view model properties.
I already tried to set cal:View.ApplyConventions="True" on the window but that did not help either. I also used ViewModelBinder.Bind(viewmodel, view, null) to bind vm and view before showing the window - does not work either.
How can I make sure my opened window (modal dialog) makes use of the same CM enhancements?
This is the view:
<Window x:Class="Client.Views.History.HistoryView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"
Title="Historie" Height="300" Width="300"
cal:View.ApplyConventions="True"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="64" />
<RowDefinition Height="*" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<ContentControl x:Name="HeaderView" Grid.Row="0" Grid.Column="0" />
<ContentControl x:Name="RecordView" Grid.Row="1" Grid.Column="0" />
<Border Grid.Row="2" Grid.Column="0" Background="DarkKhaki" BorderThickness="2" BorderBrush="DarkSeaGreen" />
</Grid>
</Window>
The border is displayed so the view is loaded. The DataContext should be set by caliburn micro. This is the view model:
public class HistoryViewModel : PropertyChangedBase
{
#region Fields --------------------------------------------------------
private readonly HeaderViewModel headerView;
private readonly RecordViewModel recordView;
#endregion
public HistoryViewModel()
{
this.headerView = IoC.Get<HeaderViewModel>();
this.recordView = IoC.Get<RecordViewModel>();
}
public HeaderViewModel HeaderView
{
get { return this.headerView; }
}
public RecordViewModel RecordView
{
get { return this.recordView; }
}
}
The constructor runs, the view models are created (not null). But the properties HeaderView and RecordView are never accessed.
To launch this I use this code:
HistoryViewModel viewModel = IoC.Get<HistoryViewModel>();
windowManager.ShowDialog(viewModel);
It works when binding the model explicitly like this:
<ContentControl x:Name="HeaderView" cal:View.Model="{Binding HeaderView}" Grid.Row="0" Grid.Column="0" />
<ContentControl x:Name="RecordView" cal:View.Model="{Binding RecordView}" Grid.Row="1" Grid.Column="0" />
But I would like to know how the standard mechaniscs can be re-enabled.
I think your HistoryViewModel needs to be a Conductor to get this to work. Since you want both child VMs to show at the same time, you would inherit from Conductor.Collection.AllActive.
John
I am currently binding an object in my code-behind (C#) to my XAML by giving a name to the XAML control, and setting the DataContext in the code-behind.
public partial class SmsControl: UserControl
{
private readonly DataOrganizer _dataOrganizer;
public FunctionalTester _funcTester;
public SmsControl()
{
InitializeComponent();
_dataOrganizer = new DataOrganizer();
_funcTester = new FunctionalTester();
// Set the datacontext appropriately
grpModemInitialization.DataContext = _funcTester;
}
private async void Button_Click_2(object sender, RoutedEventArgs e)
{
await _funcTester.Test();
}
}
And my XAML...
<!-- SMS Test Modem Initialization GroupBox -->
<GroupBox x:Name="grpModemInitialization" Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="3" Style="{StaticResource groupboxViewItem}">
<GroupBox.Header>
<Label Content="SMS Test Modem Initialization" Style="{StaticResource boldHeaderItem}"/>
</GroupBox.Header>
<!-- SMS Test Modem Initialization Grid -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" Content="COM:" Style="{StaticResource boldHeaderItem}" />
<ComboBox Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" Style="{StaticResource comboBoxItem}" ItemsSource="{Binding AvailableCommPorts}" SelectedItem="{Binding SelectedCommPort}" />
<Label Grid.Column="2" Grid.Row="0" Content="Modem Ready:" Style="{StaticResource boldHeaderItem}" />
<Label Grid.Column="2" Grid.Row="1" Content="RSSI:" Style="{StaticResource boldHeaderItem}" />
<Label Content="{Binding ModemReady}" Grid.Column="3" Grid.Row="0" HorizontalContentAlignment="Left" VerticalAlignment="Center"/>
<Label Content="{Binding ModemRssi}" Grid.Column="3" Grid.Row="1" HorizontalContentAlignment="Left" VerticalAlignment="Center" />
<Label Grid.Column="4" Grid.Row="0" Content="Modem #:" Style="{StaticResource boldHeaderItem}"/>
<Button Grid.Column="4" Grid.Row="1" Grid.ColumnSpan="2" Content="Initialize" />
<Label Content="{Binding ModemNumber}" Grid.Column="5" Grid.Row="0" HorizontalContentAlignment="Left" VerticalAlignment="Center"/>
</Grid>
</GroupBox>
The code above works fine - no problems. What I'm asking is, if there is a way to set the DataContext of the GroupBox in XAML, referencing my _funcTester object, instead of setting the DataContext in the code-behind? The reason I ask, is because different controls need to be bound to different objects in the code-behind and I'm not finding good resources on how to do so, except as I show above (giving a "x:Name" to each XAML control and setting the DataContext in code-behind). Any help is appreciated! Thanks!
You don't want to reference UI elements by name in the code-behind. Actually any time you can avoid naming an object you save a little in performance. And by setting up your app to use MVVM properly, you gain in performance, readability, maintainability, and code separation.
You want to abstract things further to use the MVVM pattern. You're doing your bindings correctly but consider the pattern. Your view is all correct. Consider adding a class that holds the properties defined currently in your code-behind and the methods called in your event handlers.
public class ViewModel : INotifyPropertyChanged
{
private FunctionalTester _funcTester;
public FunctionalTester FuncTester
{
get
{
return _funcTester;
}
set
{
_funcTester = value;
OnPropertyChanged( "FuncTester" );
}
}
public async void TestAsync( )
{
await _funcTester.Test( );
}
}
A binding to the FuncTester would simply be SomeProperty="{Binding FuncTester}" because the object is set as the DataContext of your view. A decent article that expands on this idea is found here.
Obviously left out some things (like INotifyPropertyChanged implementation and other properties you've defined) for brevity. But just make this class and assign it as your view model. Then the UI (the Xaml and the code-behind) only really deal with the UI and the ViewModel really deals with the data and logic. Great separation. For your event handler, just call ((ViewModel)this.DataContext).Test( ); and you can plug-and-play your ViewModel's to change functionality on the fly. Hope this helps!
Just set the DataContext of whole UserControl to self i.e do
this.DataContext = this; in constructor.
Then define the Property for _functinTester
public FunctionalTester FuncTester { get {return _funcTester} };
Now in your xaml you can do
<GroupBox x:Name="grpModemInitialization" DataContext="{Binding FuncTester}"/>
In this way since you have the DataContext set for your whole usercontrol, you can bind any control to any property within that DataContext