DataTemplate for MVVM: where does it go? - c#

I'm working on a "Modern UI" application so the syntax is a bit new for me and I just can't seem to get my bindings to work properly.
My desire is to have a ViewModel first design so that on my apps pages I can just do things like have a ListView and add a UserViewModel as a child, and have the DataTemplate be found automatically to create a UserView and bind to the supplied UserViewModel.
I do something similar for a different app written for Win 7 desktop and it just works but for the life of me I can't figure out why it doesn't work here. I just get in my ListView "UserViewModel" as text (no UserControl created).
The only other difference here is it is the first time I'm using async functions since it pretty much is forced on you for Win 8 development, and that is the methods I get from the WCF service I'm pulling my data from.
Here's an example of my view model:
public class UserViewModel
{
private UserDTO _user { get; set; }
public UserViewModel(UserDTO user)
{
_user = user;
}
public UserViewModel(int userId)
{
SetUser(userId);
}
private async void SetUser(int userId)
{
ServiceClient proxy = new ServiceClient();
UserDTO referencedUser = await proxy.GetUserAsync(userId);
}
public string FirstName
{
get
{
return _user.FirstName;
}
}
public string LastName
{
get
{
return _user.LastName;
}
}
public string Email
{
get
{
return _user.email;
}
}
}
The view is supposed to be all XAML and glued together in the application resources as follows:
<UserControl x:Class="TaskClient.Views.UserView" ...
xmlns:root="using:TaskClient"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="30"
d:DesignWidth="200">
<StackPanel Orientation="Horizontal" Margin="5, 0, 0 ,0" DataContext="{Binding}">
<TextBlock x:Name="FirstNameLabel" Text="{Binding FirstName}"/>
<TextBlock x:Name="LastNameLabel" Text="{Binding LastName}"/>
<TextBlock x:Name="EmailLabel" Text="{Binding Email}"/>
</StackPanel>
</UserControl>
and :
<Application x:Class="TaskClient.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TaskClient"
xmlns:localData="using:TaskClient.Data"
xmlns:vm="using:ViewModels"
xmlns:vw="using:TaskClient.Views">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!--
Styles that define common aspects of the platform look and feel
Required by Visual Studio project and item templates
-->
<ResourceDictionary Source="Common/StandardStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!-- Application-specific resources -->
<x:String x:Key="AppName">TaskClient</x:String>
<DataTemplate x:Key="vm:UserViewModel">
<vw:UserView />
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
I've tried searching for an hour or so now through various examples (eg. http://joshsmithonwpf.wordpress.com/a-guided-tour-of-wpf/) and haven't been able to find an example that works in my case.
Any idea what I'm doing wrong?

Could be a typo but you don't seem to update the "_user" field when you fetch a user via WCF service. You probably need to change this:
private async void SetUser(int userId)
{
ServiceClient proxy = new ServiceClient();
UserDTO referencedUser = await proxy.GetUserAsync(userId);
}
To this:
private async void SetUser(int userId)
{
ServiceClient proxy = new ServiceClient();
_user = await proxy.GetUserAsync(userId);
}
Also I don't see your ViewModel class implementing INotifyPropertyChange interface which is the key to WPF databinding. Once that's done and you have loaded a user, you need to notify WPF about properties being updated:
private async void SetUser(int userId)
{
ServiceClient proxy = new ServiceClient();
_user = await proxy.GetUserAsync(userId);
NotifyOfPropertyChange();
}
private void NotifyOfPropertyChange()
{
NotifyChanged("FirstName"); //This would raise PropertyChanged event.
NotifyChanged("LastName");
NotifyChanged("Email");
}

I just get in my ListView "UserViewModel" as text (no UserControl created)
Your DataTemplate needs be defined with the DataType property and no x:Key property, for the DataTemplate to get applied implicitly
<DataTemplate DataType="{x:Type vm:UserViewModel}">
<vw:UserView />
</DataTemplate>
A DataTemplate with a DataType specified but no x:Key is an Implicit DataTemplate, meaning it will be used implicitly anytime WPF needs to draw an object of the specified data type.
A DataTemplate with an x:Key property needs to actually be specified in your code by key, such as
<ListView ItemTemplate="{StaticResource MyKey}" ... />
Also, I'm not sure if the title of your question ("DataContext for MVVM: where does it go?") is a typo or not since your question body doesn't appear to be asking about the DataContext, however I have an beginners article on my blog explaining the DataContext that you may be interested in if you're struggling to understand the DataContext

Related

Binding MAUI ControlTemplate to ContentPage view model

I'm trying to add a common control I want to appear at the bottom of every content page in the app (these will all be inside a TabBar). I made a ControlTemplate in my App.xaml, and the Picker I placed in the bottom appears, but the ItemsSource property binding isn't working (there are no items visible).
I'm not sure how to get this to work. I'm new to Xamarin/MAUI, and am open to suggestions for different approaches if I'm going in the wrong direction to accomplish this.
I've tried using TemplateBinding instead of Binding in the XAML, and I've also placed the same properties in the App.xaml.cs and the AppShell.xaml.cs code-behind files, in case the bindings were being redirected there, which didn't make a difference. I also started out with the Environments property just being of type Env[], and switched to ObservableCollection as a troubleshooting measure (even though the collection is obviously static).
App.xaml
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:My.App"
x:Class="My.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
<ResourceDictionary>
<ControlTemplate x:Key="InputPageTemplate">
<VerticalStackLayout BindingContext="{Binding Source={RelativeSource TemplatedParent}}">
<ContentPresenter />
<!-- ******************** BINDINGS NOT WORKING ******************** -->
<Picker ItemsSource="{Binding Environments}"
SelectedItem="{Binding AppConfig.Environment}" />
</VerticalStackLayout>
</ControlTemplate>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
MyPage.cs
public class MyPage : ContentPage
{
public MyPage()
{
if (!Application.Current!.Resources.TryGetValue("InputPageTemplate", out var resource) ||
resource is not ControlTemplate template)
{
throw new Exception("Missing InputPageTemplate control template");
}
var appConfig = new AppConfig { Environment = Env.B };
ViewModel = new MyViewModel(appConfig);
BindingContext = ViewModel;
ControlTemplate = template;
}
}
MyViewModel.cs
public class MyViewModel
{
public MyViewModel(AppConfig appConfig)
{
AppConfig = appConfig;
}
public AppConfig AppConfig { get; }
public ObservableCollection<Env> Environments => new(Enum.GetValues<Env>());
}
AppConfig.cs
public class AppConfig : INotifyPropertyChanged
{
private Env _environment;
public Env Environment
{
get => _environment;
set
{
_environment = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string name = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
Env.cs
public enum Env
{
A,
B
}
The way to get the template to share a binding context with the content page is to include a path in the binding, like so:
<VerticalStackLayout
BindingContext="{Binding Source={RelativeSource TemplatedParent}, Path=BindingContext}">
<!-- Add this: ^^^^^^^^^^^^^^^^^^^^^ -->
Ok this is going to be long.
When you want to bind something in your ControlTemplate, you do not bind it directly.
You have to use different source.
There are few ways to do this. One is to use the word TemplateBinding.
The other is to set the BindingContext to Templated parent.
For example:
<Control Template...
<Grid BindingContext="{Binding Source={RelativeSource TemplatedParent}"...
<Button Command="{Binding MyCommand}"...
When you press that button, the binding will execute MyCommand, of "whatever" the control is during runtime.
So, If in your ViewModel you have GetDataCommand, you will do this in your View:
<mycontrols:MyView MyCommand="{Binding GetDataCommand}...
The deal here is to have this custom control class MyView with BindableProperty. That will allow you to bind between the ViewModel and the ControlTemplate.
public class MyView : ContentView{
public static readonly MyCommandProperty = ...
public ICommand MyCommand...
}
When I am not sure how something is done, I usually click F12 and browse the platform code. First, you get the idea how everything works, second you learn how to do it yourself.
Also, I recommend that you use the Dependency Injection, and limit the use of constructors. I also recommend CommunityToolkit.MVVM. It will save you time from implementing INotifyPropertyChanged. (And the possible mistakes from doing it wrong).

Binding tab controls with mahapps and prism - WPF

I am building a WPF application with mahapps, prism[modularity]. I have below HomeWindow.xaml code.
<Controls:MetroWindow x:Class="Project.Views.HomeWindow"
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:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:local="clr-namespace:Project.Views"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
<!--The above code is for automatically binding of viewmodel into view-->
Height="700" Width="1200" Background="White">
<Grid>
<TabControl ItemsSource="{Binding TabCollection}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock Text="{Binding Name}"/>
</TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<Label Content="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Controls:MetroWindow>
I have below structure in my HomeViewModel.cs under ViewModels directory.
public class HomeViewModel : BindableBase
{
private ObservableCollection<Item> _tabCollection;
public ObservableCollection<Item> TabCollection { get { return _tabCollection; } set { SetProperty(ref _tabCollection, value); } }
//Prism way of getting and setting data
}
public class Item
{
private string Name;
private string Content;
public Item(string name, string content)
{
Name = name;
Content = content;
}
}
below is how I add data into TabCollection property through HomeWindow.xaml.cs.
private HomeViewModel _model=new HomeViewModel();
public HomeWindow(EmployeeViewModel model)
{
InitializeComponent();
_model.UserViewModel = model;
LoadHomeData(_model.UserViewModel.EmpRole);
DataContext = this;
}
private void LoadHomeData(string Role)
{
if (string.Equals(Role, "Admin"))
{
_model.TabCollection= new ObservableCollection<Item>()
{
new Item("Test1", "1"),
new Item("Test2", "2"),
new Item("Test3", "3")
};
}
}
Now matter what, the tabs will not get displayed. Its a blank empty window. I have followed the example in the issue here and have went through few similar posts having same kind of approach. But none of them helped. Is this because of prism way of databinding or is there anything else am missing here? Hope to find some help on this..
Your problem is not connected to MahApps or Prism but to how WPF works in general. In your case Name and Content are private fields and should be public properties
public string Name { get; set; }
public string Content { get; set; }
private or field is not a valid binding source. You can find more as to what is a valid binding source under Binding Sources Overview but in your case, as far as CLR object goes:
You can bind to public properties, sub-properties, as well as indexers, of any common language runtime (CLR) object. The binding engine uses CLR reflection to get the values of the properties. Alternatively, objects that implement ICustomTypeDescriptor or have a registered TypeDescriptionProvider also work with the binding engine.
Another problem is that DataContext is set wrong. At the moment is set to HomeWindow and I think it should be set to instance of HomeViewModel which holds TabCollection property
DataContext = _model;

Transfer data in WPF using MVVM pattern

I'm stuck in this for too long and I just need someone to show me the direction...
The thing is that Im trying to develop using MVVM pattern and I can't seem to know how can I transfer some data from a method and bind it to my XAML. Also I have problems with setting up an interface ( INotifyPropertyChanged ) in all this structure. Could any of you show where does it have to be implemented?
I will try to explain on my code...
I have a DataModel that for example will be an API that will fetch some data from the web:
public class DataModel
{
public string apiResult = "null";
private void GetDataFromApi()
{
// Some web service
apiResult = "SOME RESULT FROM WEB API";
}
}
Now I have a ViewModel for the logic:
public class ViewModel
{
private DataModel dm = new DataModel();
public string ApiResult
{
get { return dm.apiResult; }
set { dm.apiResult = value; }
}
public void GetApi()
{
dm.GetDataFromApi();
}
}
And finaly the View:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFexample" x:Class="WPFexample.MainWindow"
DataContext="{Binding ''}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBlock HorizontalAlignment="Left"
Margin="110,126,0,0"
TextWrapping="Wrap"
Text="{Binding ApiResult}"
VerticalAlignment="Top"
RenderTransformOrigin="0.296,-1.239">
<TextBlock.DataContext>
<local:ViewModel/>
</TextBlock.DataContext>
</TextBlock>
</Grid>
</Window>
Actually I don't know how I can implement this as my "apiResult" is always the initial value "null", and I want it to get the result from the METHOD GetDataFromApi
How can I get all this working in the MVVM, and with some interface implemented.
I have looked at various tutorials but cant seem to grasp it as they all have something lacking from the beginning or I don't quite understand the logic...
Have also pushed this to GIT: https://github.com/lklancir/WPFexample/tree/master/WPFexample/WPFexample
Hope you can just point me in the right direction...
It worked for me, if GetDataFromApi is actually called. Add this code to DataModel.cs and the gui shows "SOME RESULT FROM WEB API"
public DataModel()
{
Task.Factory.StartNew( () => this.GetDataFromApi() );
}
But this is a timing issue. If you add a sleep to the task it will no longer work, because nothing propagates changes of the properties. You should implement INotifyPropertyChanged or use DependencyProperties.

Bind SelectedValue of UserControl to ViewModel

In my solution; I have two projects: One is a WPF UserControl Library, and the other is a WPF Application.
The usercontrol is pretty straightforward; it's a label and a combo box that will show the installed printers.
In the WPF application; I want to use this usercontrol. The selected value will be stored in user settings.
The problem I'm having is that I can't seem to get the proper binding to work. What I need to happen is to be able to set the SelectedValue of the UserControl when the MainWindow loads; as well as access the SelectedValue of the UserControl when I go to save my settings.
My code is below, could someone point me in the right direction?
PrintQueue user control:
<UserControl x:Class="WpfControls.PrintQueue"
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:wpfControls="clr-namespace:WpfControls"
mc:Ignorable="d">
<UserControl.DataContext>
<wpfControls:PrintQueueViewModel/>
</UserControl.DataContext>
<Grid>
<StackPanel Orientation="Horizontal">
<Label Content="Selected Printer:"></Label>
<ComboBox ItemsSource="{Binding Path=PrintQueues, Mode=OneWay}" DisplayMemberPath="Name" SelectedValuePath="Name" Width="200" SelectedValue="{Binding Path=SelectedPrinterName, Mode=TwoWay}"></ComboBox>
</StackPanel>
</Grid>
</UserControl>
Print Queue Codebehind:
public partial class PrintQueue : UserControl
{
public static readonly DependencyProperty CurrentPrinterNameProperty =
DependencyProperty.Register("CurrentPrinterName", typeof (string), typeof (PrintQueue), new PropertyMetadata(default(string)));
public string CurrentPrinterName
{
get { return (DataContext as PrintQueueViewModel).SelectedPrinterName; }
set { (DataContext as PrintQueueViewModel).SelectedPrinterName = value; }
}
public PrintQueue()
{
InitializeComponent();
DataContext = new PrintQueueViewModel();
}
}
PrintQueue View Model:
public class PrintQueueViewModel : ViewModelBase
{
private ObservableCollection<System.Printing.PrintQueue> printQueues;
public ObservableCollection<System.Printing.PrintQueue> PrintQueues
{
get { return printQueues; }
set
{
printQueues = value;
NotifyPropertyChanged(() => PrintQueues);
}
}
private string selectedPrinterName;
public string SelectedPrinterName
{
get { return selectedPrinterName; }
set
{
selectedPrinterName = value;
NotifyPropertyChanged(() => SelectedPrinterName);
}
}
public PrintQueueViewModel()
{
PrintQueues = GetPrintQueues();
}
private static ObservableCollection<System.Printing.PrintQueue> GetPrintQueues()
{
var ps = new PrintServer();
return new ObservableCollection<System.Printing.PrintQueue>(ps.GetPrintQueues(new[]
{
EnumeratedPrintQueueTypes.Local,
EnumeratedPrintQueueTypes.Connections
}));
}
}
Main Window:
<Window x:Class="WPFApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfControls="clr-namespace:WpfControls;assembly=WpfControls" xmlns:wpfApp="clr-namespace:WPFApp"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<wpfApp:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<wpfControls:PrintQueue CurrentPrinterName="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.PrinterName, Mode=TwoWay}"></wpfControls:PrintQueue>
</StackPanel>
</Grid>
</Window>
Main Window View Model:
public class MainWindowViewModel : ViewModelBase
{
private string printerName;
public string PrinterName
{
get { return printerName; }
set
{
printerName = value;
NotifyPropertyChanged(() => PrinterName);
}
}
public MainWindowViewModel()
{
PrinterName = "Lexmark T656 PS3";
}
}
Controls in a library need to expose DependencyProperties that you can bind to in your view. Just like WPF's TextBox exposes a Text property.
Your PrintQueue control doesn't expose anything, and instead keeps all its state in a viewmodel that nothing outside can access. Your MainWindowViewModel has no way of getting at the stuff inside PrintQueueViewModel.
You need to expose SelectedPrinterName as a DependencyProperty in the code behind of your PrintQueue xaml. Then in MainWindow.xaml you can bind it to MainWindowViewModel.PrinterName.
If you want to user ViewModels all the way through instead, then MainWindowViewModel should be creating PrintQueueViewModel itself so it can access the properties within.
As per your update / comment:
Unfortunately DependencyProperties don't work like that. The getters/setters aren't even used most of the time, and they should ONLY update the property itself. You're sort of halfway between two worlds at the moment.
If I were in your position, and assuming you can change the library so PrintQueue.xaml doesn't have a hardcoded VM instance in the view, I would just create the PrintQueueViewModel yourself. That's how MVVM is supposed to work:
ViewModel:
public class MainWindowViewModel : ViewModelBase
{
public PrintQueueViewModel PrintQueue { get; private set; }
public MainWindowViewModel()
{
PrintQueue = new PrintQueueViewModel();
PrintQueue.SelectedPrinterName = "Lexmark T656 PS3";
}
}
View:
<Window x:Class="WPFApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfControls="clr-namespace:WpfControls;assembly=WpfControls" xmlns:wpfApp="clr-namespace:WPFApp"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<wpfApp:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<wpfControls:PrintQueue DataContext="{Binding PrintQueue}"/>
</StackPanel>
</Grid>
</Window>
Again though, control libraries generally don't have view models, and expose their state via dependency properties since they're designed to be used in XAML.
Component libraries may expose view models, but in that case they wouldn't hard code the view model in the view.
Did you write the library? If not, how did the author expect people to use it?
I think with this small changes everything should work
<ComboBox ItemsSource="{Binding Path=PrintQueues, Mode=OneWay}" DisplayMemberPath="Name" Width="200" SelectedItem="{Binding Path=SelectedPrinter, Mode=TwoWay}"></ComboBox>
private System.Printing.PrintQueue selectedPrinter;
public System.Printing.PrintQueue SelectedPrinter
{
get { return selectedPrinter; }
set
{
selectedPrinter = value;
NotifyPropertyChanged(() => SelectedPrinter);
}
}
Now from the main window you can modify SelectedPrinter on the viewmodel and the change should be reflected on the view
(PrintQueue.DataContext as PrintQueueViewModel).SelectedPrinter = ...
I tried your code and your bindings of the PrintQueueView to the corresponding view model work fine. Your problem is that the MainWindowViewModel does not know about the PrintQueueViewModel and thus cannot retrieve the value of the selected printer when the main window closes (I guess that is the scenario you want to implement).
The quickest solution to your problem would be to do the following steps:
In MainWindow.xaml, give PrintQueue a Name so you can access it in the code behind
In MainWindow.xaml.cs, override the OnClosing method. In it you can retrieve the view model as follows: var viewModel = (PrintQueueViewModel)PrintQueue.DataContext;. After that you can retrieve the selected value and save it or whatever.
In the MainWindow constructor after InitializeComponent, you can retrieve your saved value from a file and set it on the PrintQueueViewModel by retrieving it the same way as in the previous step.
Whole code in MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Retrieve your selected printer here; in this case, I just set it directly
var selectedPrinter = "Lexmark T656 PS3";
var viewModel = (PrintQueueViewModel)PrintQueue.DataContext;
viewModel.SelectedPrinterName = selectedPrinter;
}
protected override void OnClosing(CancelEventArgs e)
{
var viewModel = (PrintQueueViewModel)PrintQueue.DataContext;
var selectedPrinterName = viewModel.SelectedPrinterName;
// Save the name of the selected printer here
base.OnClosing(e);
}
}
Please remember that the major point of view models is the ability to unit-test GUI logic and to disconnect GUI appearance and logic. Your view models should not be able to retrieve all the possible printers of your system but should obtain these values by e.g. Dependency Injection. I would advise you to read about SOLID programming.

Binding to properties in both the ViewModel and CodeBehind

I have what I'm sure is a ridiculously ignorant question, but I'm asking it anyways because I've searched and searched and either don't understand the solutions I'm seeing or not finding exactly the answer I seek.
I have an MVVM application. My XAML is setup with the DataContext set to the VM where the data items on the screen are populated from the VM's properties. My CodeBehind doesn't fiddle with the data, only things relating to the screen.
What I want to do now is bind certain UI elements to properties in the foo.xaml.cs (CodeBehind) file. For example, I want to specify FontSize's bound to properties in the CB so that in the WindowInitialized handler in the CB, it can detect screen sizes and change one variable to which all the screen items' FontSize= are bound.
I can solve this the wrong way by creating a public property in my VM and then "inject" the value from the CB into the VM. I know that will work, but it's a roundabout way to get the behavior I want, it's not at all straightforward, and I feel confident it's the wrong way to proceed.
I searched around and have tried things like:
FontSize="{Binding RelativeSource={RelativeSource Self},Path="MyFontSize"
(where "MyFontSize" is a public int property) and a variety of other examples I found, but none have worked.
So specifically, if my CodeBehind class is called NameChangeSetupMainWindow and that's where the "MyFontSize" property lives,
public partial class NameChangeSetupMainWindow : Window
{
private int m_fontSize = 14;
public int MyFontSize
{
get { return m_fontSize; }
set
{
if (m_fontSize != value))
{
m_fontSize = (value > 0) ? value : 10;
}
}
}
...
... rest of the class...
...
}
and the VM is called NameChangeSetupViewModel and that's where the "real" data lives and the DataContext points ala:
<Window.DataContext>
<local:NameChangeSetupViewModel/>
</Window.DataContext>
what is the syntax in XAML to bind just those UI items (tooltips related to the UI, font sizes, etc) to variables in the CodeBehind instead of housing them in the VM?
Thanks in advance for any guidance you can supply.
You can use RelativeSource AncestorType to bind to properties of the view itself:
<TextBlock FontSize="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=MyFontSize}" />
Using ElementName should work as well:
<Window x:Name="window">
<TextBlock FontSize="{Binding ElementName=window,Path=MyFontSize}" />
</Window>
Edit
Here is an example that I've confirmed working:
XAML
<Window x:Class="WpfAbc.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
ToolTip="{Binding RelativeSource={RelativeSource Self},Path=MyToolTip}"
>
<Grid>
<TextBlock Text="hello world" FontSize="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=MyFontSize}" />
</Grid>
</Window>
Code Behind
public partial class MainWindow : Window
{
private int m_fontSize = 20;
public int MyFontSize
{
get { return m_fontSize; }
set
{
if (m_fontSize != value)
{
m_fontSize = (value > 0) ? value : 10;
}
}
}
public string MyToolTip
{
get { return "hello world"; }
}
public MainWindow()
{
InitializeComponent();
}
}
Articles on this topic:
The RelativeSource markup extension
XAML binding declarations
Related background:
"Namescopes" in XAML (when binding to a source using "ElementName", the source element must be in the same namescope)
Visual tree vs logical tree in XAML (elements not in the visual tree, like Popup and ContextMenu, do not inherit DataContext. Binding from these elements requires a workaround like the "data context spy" technique.)

Categories

Resources