WPF DataContext. Taking from another class - wpf

having general DataContext taken from class SearchByID there's a need to take a separate DataContext from another class, say, testClass.
XAML example:
<Window.DataContext>
<model:SearchById />
</Window.DataContext>
<Grid>
<TextBlock Text="{Binding Description}">
<Texblock.DataContext>
<model: testClass/>
</TextBlock.DataContext>
</TextBlock>
</Grid>
There are no fails, IntelliSens sees all the Properties.
But the TextBlock is blank.
any ideas please.

One idea, is the Description a property or a field?
A Property will work:
public class TestClass
{
public string Description { get; set; }
public TestClass()
{
Description = "Test";
}
}
A field won't:
public class TestClass
{
public string Description;
public TestClass()
{
Description = "Test";
}
}
MainWindow.xaml
<TextBlock Text="{Binding Description}"
Background="Yellow"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock.DataContext>
<local:TestClass />
</TextBlock.DataContext>
</TextBlock>
Result:

Related

XAML Binding to child collection

In a Windows UWP project I'm trying to bind to the following properties in this class
using System;
using System.Collections.ObjectModel;
namespace IAmOkShared.Models
{
public class Client
{
public Guid clientId { get; set; }
public string lastname { get; set; }
public DateTime timestamp { get; set; }
//- List af addresses of this client
public ObservableCollection<Address> clientaddresses;
public Client ()
{
clientId = Guid.Empty;
lastname = string.Empty;
timestamp = DateTime.Today;
clientaddresses = new ObservableCollection<Address>();
}
}
}
Binding to clientId and lastname is no problem, but can't get it right to bind to one or more of the properties of clientaddresses (e.g city, country)
My XAML:
<DataTemplate x:Name="DetailTemplate" x:DataType="models:Client">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="LastNameTextBlock" Text="{Binding lastname}" />
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock x:Name="AddressTextBlock" Text="{Binding clientaddresses[0].city}" />
</StackPanel>
</StackPanel>
</DataTemplate>
Any idea how to solve this?
Steven
You are binding to a field instead of a property.
public ObservableCollection<Address> clientaddresses;
Change this to
public ObservableCollection<Address> Clientaddresses { get; private set; }
So it cannot be instantiated outside the viewmodel then the binding should work.
Also you could create additional data template for the Address and just use the entire collection in your datatemplate of the Client, because then you would not get possible Index out of bounds exception if your ClientAddresses collection is empty.
<DataTemplate x:DataType="models:Address">
<TextBlock x:Name="AddressTextBlock" Text="{Binding city}" />
<DataTemplate>
<DataTemplate x:Name="DetailTemplate" x:DataType="models:Client">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="LastNameTextBlock" Text="{Binding lastname}" />
</StackPanel>
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding ClientAddresses}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
Also note your model is not implementing the INotifyPropertyChanged so your UI will not be updated when the model properties change.
Also the convention for back-end private fields is to start with lower case character and for the properties that utilize the INotifyPropertyChanged you should start the property with upper case.
private int myProperty;
public int MyProperty { get { ... } set { ... }}
that's why we create ViewModel and additional property in it
public Address ClientFirstAddress
{
get {return clientaddresses[0].city;}
}
and then Bind it to View,
remember to call NofityPropertyChanged for this property when you set clientaddresses collection

View is not binding correctly to ViewModel

I cannot get the View to bind correctly to the ViewModel. When it displays, it only shows the string version of the ViewModel.
I have seen: Setting Window.Content to ViewModel - simple data template not working. But the link is no longer available.
I'm trying to use https://msdn.microsoft.com/en-us/magazine/dd419663.aspx, as a template.
MainWindow.xaml
<Window x:Class="DemoApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:DemoApp.ViewModel"
xmlns:vw="clr-namespace:DemoApp.View">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:TestViewModel}">
<vw:TestView/>
</DataTemplate>
<DataTemplate x:Key="ClosableTabItemTemplate">
<DockPanel Width="120">
<Button
Command="{Binding Path=CloseCommand}"
Content="X"
Cursor="Hand"
DockPanel.Dock="Right"
VerticalContentAlignment="Bottom"
Width="16" Height="16"/>
<ContentPresenter
Content="{Binding Path=DisplayName}"
VerticalAlignment="Center"/>
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4" />
</DataTemplate>
</Window.Resources>
<DockPanel>
<Border
Grid.Column="2"
Style="{StaticResource MainBorderStyle}">
<HeaderedContentControl
Content="{Binding Path=Workspaces}"
ContentTemplate="{StaticResource WorkspacesTemplate}"
Header="Workspaces"
Style="{StaticResource MainHCCStyle}" />
</Border>
</DockPanel>
</Window>
MainWindowViewModel.cs
// ommitted for clarity. This is directing to the view model correctly. It's the binding between View and ViewModel that is not
TestView.xaml
public class TestViewModel : WorkspaceViewModel, INotifyPropertyChanged,
{
public Model.Test _test;
public string DisplayName {get; set;}
public class TestViewModel(Model.Test t)
{
DisplayName = "Test Display Name";
_model = t;
}
// INofifyPropertyChanged Members removed for clarity
}
Test.cs
public class Test
{
public string FirstName {get; set;}
public string LastName {get; set;}
public static DisplayTest()
{
return new Test();
}
}
Displays:
DemoApp.ViewModel.TestViewModel;
However, when I go to the MainWindow.xaml and actually type in into a DockPanel, it will display correctly...
Thank you!!
UPDATE:
MainWindowViewModel.cs Properties
public ReadOnlyCollection<CommandViewModel> Commands
{
get
{
if (_commands == null)
{
List<CommandViewModel> cmds = this.CreateCommands();
_commands = new ReadOnlyCollection<CommandViewModel>(cmds);
}
return _commands;
}
}
public ObservableCollection<WorkspaceViewModel> Workspaces
{
get
{
if (_workspaces == null)
{
_workspaces = new ObservableCollection<WorkspaceViewModel>();
_workspaces.CollectionChanged += this.OnWorkspacesChanged;
}
return _workspaces;
}
}
In the View there was a Data Context Declared. This was confusing the binding it looks like. Once the Data Context in the View was removed and the MainWindowResourses kept the data context, the view is displayed as it should.

Wpf adding stack panels from view

I'm not sure my title is clear (poor wpf skills).
What i'm trying to do is to create a smart data entry form. My goal is to have a hard coded data that the user should enter, and on demand (a plus button) he can enter another set of data, every time the user will click the plus button another set will appear in the window (endless)
Edit:
For more details, for a very simple example of what i'm trying to achieve, lets say that this is the window:
And after the user will click the plus button the window will look like this:
And the plus button will always let the user adding more peoples.
Seems like all you need is a List and a ItemControl:
Your Model:
public class User
{
public String Name { get; set; }
public int Age { get; set; }
}
In your ViewModel:
public List<User> Users { get; set; }
//In your constructor
Users = new List<User>();
In your View:
<ItemsControl ItemsSource={Binding Users}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="Name:" />
<TextBox Text="{Binding Name}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="Age:" />
<TextBox Text="{Binding Age}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And then below this wire up your add button to a command to point to a method that would do someething like:
private void AddUser()
{
Users.Add(new User());
NotifyPropertyChange("Users");
}
Use an ItemsControl with its ItemsSource property bound to a ReadOnlyObservableCollection<Person>, where Person is a class holding the name and age as strings.
(1) Create Person
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
(2) Create PeopleViewModel, holding your collection.
public class PeopleViewModel
{
private ObservableCollection<Person> _people;
public ReadOnlyObservableCollection<Person> People { get; private set; }
public PeopleViewModel()
{
_people = new ObservableCollection<Person>();
People = new ReadOnlyObservableCollection<Person>(_people);
addPerson(); // adding the 1st person
}
// You also need to hook this up to the button press somehow
private void addPerson()
{
_people.Add(new Person());
}
}
(3) Set the DataContext of your window to be a PersonViewModel in the code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new PeopleViewModel();
}
}
(4) Create an ItemsControl along with a DataTemplate for Person
<ItemsControl ItemsSource="{Binding People}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="name:" />
<TextBox Text="{Binding Name}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="age:" />
<TextBox Text="{Binding Age}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Don't forget to hook up your button either through a Command or through the Button.Click event.

WPF TabControl ContentTemplate List

Inexperienced with WPF, so I need a little help. Appreciate the help in advance.
I have the following class:
public class TabDefn
{
public TabDefn() { }
public TabDefn(string inFolderName, List<FilesFolder> inFilesFolders)
{
folderName = inFolderName;
FFs = inFilesFolders;
}
public string folderName { get; set; }
public List<FilesFolder> FFs {get; set;}
}
public class FilesFolder
{
public FilesFolder() {}
//public Image image { get; set; }
public string ffName { get; set; }
//public Image arrow { get; set; }
}
The TabControl.ItemContent is working fine. I can't get anything to show up for the TabControl.ContentTemplate. I've tried many things, but this is where the WPF is right now:
<TabControl Grid.Column="1" Grid.Row="1" Visibility="Hidden" Name="Actions">
<!-- This displays the tab heading perfectly.-->
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding folderName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<!-- This is the content of the tab that I can't get anything to show up in.-->
<TabControl.ContentTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding FF}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding ffName}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
I don't care if the content changes, so I don't need the INotifyPropertyChanged or ObservableCollection. However, if I have to put all that code in, so be it.
You declare FF as field which is invalid binding source. You need to convert it into property
public List<FilesFolders> FF { get; set; }
and initialize it for example in TabDefn constructor. You can find more as to what is a valid binding source on MSDN

WPF Show data from multiple DataContexts in ToolTip of ItemsControl

I am trying to display a tooltip for an item generated by an ItemsControl that needs to pull data from conceptually unrelated sources. For example, say I have an Item class as follows:
public class Item
{
public string ItemDescription { get; set; }
public string ItemName { get; set; }
}
I can display the Item within an ItemsControl with a tooltip as follows:
<ItemsControl x:Name="itemsControl" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}">
<TextBlock.ToolTip>
<ToolTip>
<TextBlock Text="{Binding ItemDescription}" />
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But say I have another property that can be accessed via the DataContext of the ItemsControl. Is there any way to do this from within the tooltip? E.g.,
<ItemsControl x:Name="itemsControl" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}">
<TextBlock.ToolTip>
<ToolTip>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding ItemDescription}" />
<TextBlock Grid.Row="1" Text="{Bind this to another property of the ItemsControl DataContext}" />
</Grid>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The code for the test Window I used is as follows:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
List<Item> itemList = new List<Item>() {
new Item() { ItemName = "First Item", ItemDescription = "This is the first item." },
new Item() { ItemName = "Second Item", ItemDescription = "This is the second item." }
};
this.Items = itemList;
this.GlobalText = "Something else for the tooltip.";
this.DataContext = this;
}
public string GlobalText { get; private set; }
public List<Item> Items { get; private set; }
}
So in this example I want to show the value of the GlobalText property (in reality this would be another custom object).
To complicate matters, I am actually using DataTemplates and show two different types of objects within the ItemsControl, but any assistance would be greatly appreciated!
After an hour of hair pulling I have come to the conviction that you can't reference another DataContext inside a DataTemplate for a ToolTip. For other Bindings it is perfectly possible as other posters have proven. That's why you can't use the RelativeSource trick either. What you can do is implement a static property on your Item class and reference that:
<Window x:Class="ToolTipSpike.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
Name="Root"
xmlns:ToolTipSpike="clr-namespace:ToolTipSpike">
<Grid>
<ItemsControl x:Name="itemsControl" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}">
<TextBlock.ToolTip>
<ToolTip>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding ItemDescription}" />
<TextBlock Grid.Row="1"
Text="{Binding Source={x:Static ToolTipSpike:Item.GlobalText},
Path=.}"
/>
</Grid>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
using System.Collections.Generic;
using System.Windows;
namespace ToolTipSpike
{
public partial class Window1 : Window
{
public List<Item> Items { get; private set; }
public Window1()
{
InitializeComponent();
var itemList = new List<Item>
{
new Item { ItemName = "First Item", ItemDescription = "This is the first item." },
new Item { ItemName = "Second Item", ItemDescription = "This is the second item." }
};
this.Items = itemList;
this.DataContext = this;
}
}
public class Item
{
static Item()
{
GlobalText = "Additional Text";
}
public static string GlobalText { get; set; }
public string ItemName{ get; set;}
public string ItemDescription{ get; set;}
}
}
Second attempt
Ok, the Relative Source Binding doesn't work in this case. It actually works from a data template, you can find many examples of this on the Internets. But here (you were right, David, in your comment) ToolTip is a special beast that is not placed correctly in the VisualTree (it's a property, not a control per se) and it doesn't have access to the proper name scope to use relative binding.
After some more searching I found this article, which describes this effect in details and proposes an implementation of a BindableToolTip.
It might be an overkill, because you have other options -- like using a static property on a class (as in Dabblernl's response) or adding a new instance property to your Item.
First attempt :)
You should consult with the Relative Source Binding types (in this cheat sheet for example):
So your binding will look somehow similar to this:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}, Path= GlobalText}
Almost correct Yacoder, and guessed way wrong there Dabblernl ;)
Your way of thinking is correct and it is possible to reference the DataContext of your ItemsControl
You are missing the DataContext property in path:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.GlobalText}
Second attempt ;)
http://blogs.msdn.com/tom_mathews/archive/2006/11/06/binding-a-tooltip-in-xaml.aspx
Here is an article with the same problem. They can reference the DataContext of their Parent control by the PlacementTarget property:
<ToolTip DataContext=”{Binding RelativeSource={RelativeSource Self},Path=PlacementTarget.Parent}”>
If you would place the DataContext on a deeper level, you avoid changing your Item DataContext
A second suggestion (Neil and Adam Smith) was that we could use PlacementTarget in the binding. This is nice, as I am actually inheriting the DataContext already from the page that hosts the DataControl, and this would allow the ToolTip to gain access back to the origial control. As Adam noted, though, you have to be aware of the parent/child structure off your markup:
This is a case where I think it's conceptually more appropriate to do this in the view model than it is in the view anyway. Expose the tooltip information to the view as a property of the view model item. That lets the view do what it's good at (presenting properties of the item) and the view model do what it's good at (deciding what information should be presented).
I had a very similar problem and arrived at this question seeking answers. In the end I came up with a different solution that worked in my case and may be useful to others.
In my solution, I added a property to the child item that references the parent model, and populated it when the children were generated. In the XAML for the ToolTip, I then simply referenced the property from the parent model on each element and set the DataContext to the parent model property.
I felt more comfortable with this solution than creating proxy elements in XAML and referencing them.
Using the example code for this question, you would do the following. Note I have not tested this scenario in a compiler, but have done so successfully implemented this solution in the code for my own scenario.
Item:
public class Item
{
public List<Item> Parent { get; set; }
public string ItemDescription { get; set; }
public string ItemName { get; set; }
}
Window:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
List<Item> itemList = new List<Item>();
itemList.Add(new Item() { Parent = this, ItemName = "First Item", ItemDescription = "This is the first item." });
itemList.Add(new Item() { Parent = this, ItemName = "Second Item", ItemDescription = "This is the second item." });
this.Items = itemList;
this.GlobalText = "Something else for the tooltip.";
this.DataContext = this;
}
public string GlobalText { get; private set; }
public List<Item> Items { get; private set; }
}
XAML:
<ItemsControl x:Name="itemsControl" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}">
<TextBlock.ToolTip>
<ToolTip>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding ItemDescription}" />
<TextBlock Grid.Row="1" DataContext={Binding Parent} Text="{Bind this to aproperty of the parent data model}" />
</Grid>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Resources