Using data binding on value which is a FrameworkElement - wpf

One of my data sources produces a collection of values which are typed to the following interface
public interface IData
{
string Name { get; }
FrameworkElement VisualElement { get; }
}
I'd like to use data binding in WPF to display a collection of IData instances in a TabControl where the Name value becomes the header of the tab and the VisualElement value is displayed as the content of the corresponding tab.
Binding the header is straight forward. I'm stuck though on how to define a template which allows me to display the VisualElement value. I've tried a number of solutions with little success. My best attempt is as follows.
<TabControl ItemsSource="{Binding}">
<TabControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
How do I display VisualElement here?
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
I'm still very new to WPF so I could be missing the obvious here.

ContentPresenters were made for this. The content template becomes:
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding VisualElement}" />
</DataTemplate>
</TabControl.ContentTemplate>
I tested it with a TextBlock and a TextBox.

Related

DataTemplate Binding depending on property type and with working property Binding

I check those articles about doing DataTemplate :
WPF DataTemplate Binding
WPF DataTemplate and Binding
WPF DataTemplate Textblock binding
and thoses about DataTemplate depending on property type :
WPF DataTemplate Binding depending on the type of a property
Dynamically display a control depending on bound property using WPF
I'm trying to display a property with different controls depending of the property value. I have this Xaml that is partialy working. I have 2 problems :
The property is displaying with the right control, but when I set the value it doesn't go back to the property. Means the "set" of My property is not call (but was before I creates the DataTemplate). I detect that the problem about setting the property is about the ="{Binding Path=.}" but I cannot find the solution to set it otherwise.
Also To be able to make it work, I had to "isolate" the Value into a single ViewModel so that the DataTemplate doesn't affect all the other control.
Can you help me find betters solutions to resolves those 2 problems?
Here is the xaml code of my View linked with MyContainerViewModel that has a "ChangingDataType" :
<UserControl >
<UserControl.Resources>
<!-- DataTemplate for strings -->
<DataTemplate DataType="{x:Type sys:String}">
<TextBox Text="{Binding Path=.}" HorizontalAlignment="Stretch"/>
</DataTemplate>
<!-- DataTemplate for bool -->
<DataTemplate DataType="{x:Type sys:Boolean}">
<CheckBox IsChecked="{Binding Path=.}" />
</DataTemplate>
<!-- DataTemplate for Int32 -->
<DataTemplate DataType="{x:Type sys:Int32}">
<dxe:TextEdit Text="{Binding Path=.}" MinWidth="50" Mask="d" MaskType="Numeric" HorizontalAlignment="Stretch"/>
<!--<Slider Maximum="100" Minimum="0" Value="{Binding Path=.}" Width="100" />-->
</DataTemplate>
<!-- DataTemplate for decimals -->
<DataTemplate DataType="{x:Type sys:Decimal}">
<!-- <TextBox Text="{Binding Path=.}" MinWidth="50" HorizontalAlignment="Stretch" />-->
<dxe:TextEdit Text="{Binding Path=.}" MinWidth="50" Mask="f" MaskType="Numeric" HorizontalAlignment="Stretch" />
</DataTemplate>
<!-- DataTemplate for DateTimes -->
<DataTemplate DataType="{x:Type sys:DateTime}">
<DataTemplate.Resources>
<DataTemplate DataType="{x:Type sys:String}">
<TextBlock Text="{Binding Path=.}"/>
</DataTemplate>
</DataTemplate.Resources>
<DatePicker SelectedDate="{Binding Path=.}" HorizontalAlignment="Stretch"/>
</DataTemplate>
</UserControl.Resources>
<ContentPresenter Content="{Binding MyChangingPropery}"/>
</UserControl>
More informations about 2 :
I wanted to have in a view a label and a property that changes depending of the object. Something like this :
<UserControl>
<UserControl.Resources>
<!-- ...DataTemplate here... -->
</UserControl.Resources>
<StackPanel>
<Label Content="Allo"/>
<ContentPresenter Content="{Binding MyChangingPropery}"/>
</StackPanel>
</UserControl>
But if I put the DataTemplate on this UserControl resources, it will also affect the Label "allo". So I had to create another view that contains the DataTemplate and MyChangingProperty so that the label Allo would not be affected. But the extra View created just for one property is kind of ugly to me, I'm sure there is a better way to isolate the DataTemplate so it can apply only to one UIControl.
<UserControl >
<StackPanel>
<Label Content="Allo"/>
<ContentPresenter Content="{Binding MyContainerViewModel}"/>
</StackPanel>
</UserControl>
Note : MyContainerViewModel here is linked with the first view described.
Thanks in advance!
One possible solution would be to use a DataTemplateSelector. You cannot bind primitive types using two way bindings because that would have to be somehow by reference via the DataTemplate which I think is not supported by WPF.
The DataTemplateSelector now selects the right DataTemplate based on the property type and searches for the right DataTemplate in the resources by name. This also solves your problem that your DataTemplates interacted with the Label.
So first you need to define a DataTemplateSelector that changes the DataTemplate based on the type of the property:
public class MyDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var fe = (FrameworkElement)container;
var prop = (item as MyViewModelType)?.MyChangingProperty;
if (prop is string)
return fe.FindResource("MyStringDT") as DataTemplate;
else if (prop is bool)
return fe.FindResource("MyBoolDT") as DataTemplate;
// More types...
return base.SelectTemplate(item, container);
}
}
Then you need to change the UserControl like this:
<UserControl>
<UserControl.Resources>
<local:MyDataTemplateSelector x:Key="MyDTSelector" />
<!-- DataTemplate for strings -->
<DataTemplate x:Key="MyStringDT">
<TextBox Text="{Binding MyChangingProperty, Mode=TwoWay}"
HorizontalAlignment="Stretch"/>
</DataTemplate>
<!-- DataTemplate for bool -->
<DataTemplate x:Key="MyBoolDT">
<CheckBox IsChecked="{Binding MyChangingProperty, Mode=TwoWay}" />
<!-- More DataTemplates... -->
</DataTemplate>
</UserControl.Resources>
<StackPanel>
<Label Content="Allo"/>
<ContentPresenter Content="{Binding MyContainerViewModel}"
ContentTemplateSelector="{StaticResource MyDTSelector}" />
</StackPanel>
</UserControl>
You can find a bit more information regarding the DataTemplateSelector here.
You can of course also set a DataType on this new DataTemplates but it isn't required because the x:Key makes them unique anyway. But if you want then it has to look like this:
<DataTemplate x:Key="MyStringDT" DataType="{x:Type local:MyViewModelType}">
In my opinion, the previously posted answer is overkill. While a DateTemplateSelector is a useful thing to know about, it seems unnecessary to me in this scenario.
But if I put the DataTemplate on this UserControl resources, it will also affect the Label "allo".
The reason it affects the Label object is that the Label object is a ContentControl, and so does the same template-matching behavior for content types as your own ContentPresenter element does. And you've set the content of the Label object to a string value. But you can put anything you want as the content for it.
The way to fix the undesired effect is to intercept that behavior by changing the content from a string object to an explicit TextBlock (the control in the template that a string object normally gets assigned). For example:
<UserControl>
<UserControl.Resources>
<!-- ...DataTemplate here... -->
</UserControl.Resources>
<StackPanel>
<Label>
<TextBlock Text="Allo"/>
</Label>
<ContentPresenter Content="{Binding MyChangingPropery}"/>
</StackPanel>
</UserControl>
In that way, you bypass the template-finding behavior (since TextBlock doesn't map to any template and can be used directly), and the content for the Label will just be the TextBlock with the text you want.
This seems like a lot simpler way to fix the issue, than either to create a whole new view or to add a DataTemplateSelector.

WPF binding UserControl collection to ItemsControl. DataTemplate issue

http://www.filedropper.com/wpfapplication9
that link has a project in VS2013 with a sample issue.
my problem is, how to set DataTemplate to UserControl, in ItemsControl.
<ItemsControl ItemsSource="{Binding Collection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
my Collections is a
public IEnumerable<MyUC> Collection {get;}
my MyUC is a
public partial class MyUC : UserControl
{
public string Title { get; set; }
}
When i try to show that collection im getting
MyUC.Content
insted
MyUC.Title
when i change ItemsSouce to ListBox, datatemplate starts working.
but i need to show collection without ListBox addons.
If I understand you correctly, you just want to display the Title property value of your UserControls from your collection. All that you have to do is to declare an appropriate DataTemplate for them in the Resources section:
<DataTemplate DataType="x:Type={YourPrefix:MyUC}">
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
To be honest though, you seem to be going about this in the wrong way. In WPF, we generally don't put UI elements into collections, instead preferring to work with custom data classes that have DataTempates to define what they should look like. In your case, it would look something like this:
<DataTemplate x:Key="TitleTemplate" DataType="x:Type={YourPrefix:MyDataClass}">
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
<DataTemplate x:Key="ControlTemplate" DataType="x:Type={YourPrefix:MyDataClass}">
<YourControlPrefix:MyUC />
</DataTemplate>
You'd then use the TitleTemplate when you want to see just the Title property values and the ControlTemplate when you want to see the whole UserControl.

Binding one ElementUI in XAML to 2 source

<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Textblock Text={Binding Path=Content} Foreground={Binding Path=TextColor}/>
</DataTemplate>
<ListBox.ItemTemplate>
</ListBox>
Hi, I'm developing a book reader application in WP8. I have a stoy with a list of paragraph which I use ListBox to display. Each paragraph content is binding to a textblock as you can see in my code. In Paragraph class, I define a field call TextColor to bind the foreground color of textblock to it. Now, each time user change the color, I have to loop through all paragraph in story and change the value of TextColor. Is there any way to separately bind 2 different property (ie. the Foreground and the Text) of a ListboxItem to different source> So I'll only have to change the Foreground one time. Thank
KooKiz solution is pretty good. However, if you don't want to include any properties to manage foreground colors, or any visual property for that matter, at all you can simply make it a static resource and bind to that instead where you can modify them independently of your model.
For example, you could define a ForegroundResouces class and add in different types of foregrounds your app needs.
In App.xaml
<Application.Resources>
<local:ForegroundResouces xmlns:local="clr-namespace:YOUR-NAMESPACE" x:Key="ForegroundResouces" />
</Application.Resources>
Then define your class
public class ForegroundResouces {
public static Brush TitleForeground { get; set; }
public static Brush ContentForeground { get; set; }
// ...
}
Then define your binding
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Textblock
Text={Binding Path=Content}
Foreground={Binding Path=ContentForeground, Source={StaticResource ForegroundResouces} />
</DataTemplate>
<ListBox.ItemTemplate>
</ListBox>
Then you can simply change the foreground by modifying your static properties
ForegroundResources.ContentForeground = new SolidBrush(Colors.Red);
You could sort of make different set of themes but this solution is probably only worth it if you have more than one visual property to manage.
There is ways to specify a different source for your bindings. For instance, you can use ElementName to point at your listbox and retrieve its datacontext:
<ListBox x:Name="MyList" ItemsSource="{Binding Path=Paragraphs}">
<ListBox.ItemTemplate>
<DataTemplate>
<Textblock Text="{Binding Path=Content}" Foreground="{Binding Path=DataContext.TextColor, ElementName=MyList}"/>
</DataTemplate>
<ListBox.ItemTemplate>
</ListBox>
But in your case, it's probably easier to just set the Foreground property on the parent list. It will be automatically applies to all child controls:
<ListBox x:Name="MyList" ItemsSource="{Binding Path=Paragraphs}" Foreground="{Binding Path=TextColor}">
<ListBox.ItemTemplate>
<DataTemplate>
<Textblock Text="{Binding Path=Content}" />
</DataTemplate>
<ListBox.ItemTemplate>
</ListBox>

How can I bind a control into a WPF listview column

I'm new to WPF so forgive me if I've missed something obvious
I have the following list view control (non relevent details removed for brevity)
<ListView>
<ListView.View>
<GridView>
<GridViewColumn Header="Type" DisplayMemberBinding="{Binding Type}"/>
<GridViewColumn Header="Details" DisplayMemberBinding="{Binding Details}"/>
</GridView>
</ListView.View>
</ListView>
I add items to this list view in code behind
public void Add(LogEntry entry)
{
ListViewItem item = new ListViewItem();
item.Content = entry;
listView.Items.Add(item);
}
where LogEntry has public string properties for "Type" and "Details"
So far, so good, all works lovely but..
I want my details property to be an element itself (TextBox or DockPanel containing various types of content)
How can I bind this Element to the list column?
Using the code above, Changing "Details" to an element I simply get the class name in the list box (e.g. System.Windows.Control.TextBox) as by default the cell displays the ToString() value of the property.
I have googled examples which use a DataTemplate but I can't find an example of binding an element to the content of a panel.
The control cannot be defined in xaml as its structure and contents are not known until runtime
The binding is one way (I only need to display the list, not update the underlying data structure)
To make my problem clearer, I want to bind the following list item
class LogEntry
{
public string Type {get;}
public DockPanel Details {get;} // This dock panel was created by code and contains
// various elements not predictable at compile time
}
Your Model, the LogEntry class, should not reference a UI control. Instead it should contain the data needed by the UI, and the XAML should define a DataTemplate that uses that data. For example,
public class LogEntry
{
public string Type {get;}
public ObservableCollection<IDetail> Details {get;}
}
<DataGridTemplateColumn Header="Details">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DockPanel>
<ItemsControl ItemsSource="{Binding Details}" />
</DockPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
You mentioned that the DockPanel in the LogEntry created items that were not known at runtime. Can you give an example of that? In most cases, you should have some kind of pattern in the data, and you can use DataTemplates to define how to draw each Detail piece.
<DataTemplate DataType="{x:Type local:LoginDetail}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding CreatedDate}" />
<TextBlock Text="{Binding UserName}" />
<TextBlock Text="{Binding MachineLoggedIn}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:LogoutDetail}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding CreatedDate}" />
<TextBlock Text="{Binding UserName}" />
<TextBlock Text="{Binding LoggedInTime}" />
</StackPanel>
</DataTemplate>
If you REALLY need to store a control in the Model, then you can use a ContentControl in your DataGridTemplateColumn and set it's Content equal to the Details
<ContentControl Content="{Binding Details}" />
Sorry I cant give you an exact answer as I dont have VS where I am right now here but a few pointers.
First instead of using your method to add Listview items you want to create an ObservableCollection with your data. Then you can bind the itemssource of your listview to the observableCollection.
Next you can create an itemtemplate for the listview containing the control you want, something quite simple would be like a stack panel with horizontal orientation and two textboxes.
Once you have done that because you have set the itemsource of the listview to the ObservableCollection you can just bind the textbox to the String property within your collection.
Note that ObservableCollection is better to bind to than List as ObservableCollection supports NotifyPropertyChanged().
I have copied your code into a new project and created the same list view here is the code. And this one worked and displayed to data correctly
XAML:
<ListView x:Name="MyList" ItemsSource="{Binding MyListDetails}">
<ListView.View>
<GridView>
<GridViewColumn Header="Type" DisplayMemberBinding="{Binding Firstname}"/>
<GridViewColumn Header="Details" DisplayMemberBinding="{Binding Lastname}"/>
</GridView>
</ListView.View>
ViewModel:
private List<Contact> _details= new List<Contact>();;
public List<Contact> MyListDetails
{
get
{
return _details;
}
set
{
_details = value;
this.RaisePropertyChanged("MyListDetails");
}
}
public void AddEntry(LogEntry entry)
{
MyListDetails.Add(entry);
}

WPF Combobox DisplayMemberPath

Ok, I looked at other questions and didn't seem to get my answer so hopefully someone here can.
Very simple question why does the DisplayMemberPath property not bind to the item?
<ComboBox Grid.Row="1" Grid.Column="2" ItemsSource="{Binding PromptList}" DisplayMemberPath="{Binding Name}" SelectedItem="{Binding Prompt}"/>
The trace output shows that it is trying to bind to the class holding the IEnumerable not the actual item in the IEnumerable. I'm confused as to a simple way to fill a combobox without adding a bunch a lines in xaml.
It simply calls the ToString() for the object in itemssource. I have a work around which is this:
<ComboBox Grid.Row="1" Grid.Column="2" ItemsSource="{Binding PromptList}" SelectedItem="{Binding Prompt}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
But in my opinion it's too much for such a simple task. Can I use a relativesource binding?
DisplayMemberPath specifies the path to the display string property for each item. In your case, you'd set it to "Name", not "{Binding Name}".
You are not binding to the data in the class, you are telling it to get it's data from the class member that is named by the member "name" so, if your instance has item.Name == "steve" it is trying to get the data from item.steve.
For this to work, you should remove the binding from the MemberPath. Change it to MemberPath = "Name" this tells it to get the data from the member "Name". That way it will call item.Name, not item.steve.
You should change the MemberPath="{Binding Name}" to MemberPath="Name". Then it will work.
You could remove DisplayMemberPath and then set the path in the TextBlock.
The DisplayMemberPath is really for when you have no ItemTemplate.
Or you could remove your ItemTemplate and use DisplayMemberPath - in which case it basically creates a TextBlock for you.
Not recomended you do both.
<TextBlock text="{Binding Path=Name, Mode=OneWay}"
Alternatively you don't need to set the DisplayMemberPath. you can just include an override ToString() in your object that is in your PromptList. like this:
class Prompt {
public string Name = "";
public string Value = "";
public override string ToString() {
return Name;
}
}
The ToString() will automatically be called and display the Name parameter from your class. this works for ComboBoxes, ListBoxes, etc.
Trying this :
<ComboBox Grid.Row="1" Grid.Column="2" ItemsSource="{Binding PromptList}" SelectedItem="{Binding Prompt}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Content}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
from what i can figure,
"DisplayMemberPath" uses reflection to get the property name in the data context class, if it cant find it nothing will be displayed.
if class
class some_class{
string xxx{ get; }
}
DisplayMemberPath=xxx, will show whatever value "xxx" is
if you want to concatenate properties from the datacontext you need to create an item template, which will show up in the header and the drop down list.
<ComboBox.ItemTemplate>
<DataTemplate DataType="employee">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding first_name}" />
<TextBlock Text="" />
<TextBlock Text="{Binding last_name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
you cannot have "DisplayMemberPath" and "ComboBox.ItemTemplate" set at the same time.

Resources