Select different bindings in DataContext based on another binding - c#

I have a fairly dynamic ObservableCollection of view models that is used by two different ListBox elements in XAML. Each view model contains properties for two different model objects of type Card called Primary and Secondary, as well as other properties. In one ListBox I'd like to display properties from Primary and in the other I'd like to display properties from Secondary. I'd like to use the same XAML UserControl file when displaying the ListBoxItems for both.
My first thought was to create an entry in UserControl.Resources that gives a name to the "right" card based on a RelativeSource reference from the parent view model which indicates Primary or Secondary, but I've not created an entry like that before. Is this the right approach? If so, what would the entry look like?
I've made up some XAML to help illustrate (may have typos). First, the Primary ListBox control:
<UserControl x:Class="Project.Cards.ListPrimary" d:DataContext="{Binding Main.Cards.Primary, Source={StaticResource Locator}}">
<UserControl.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type vms:CardViewModel}">
<views:Card />
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<ListBox x:Name="CardListBox"
SelectedItem="{Binding SelectedCard}"
ItemsSource="{Binding Cards}" />
</Grid>
</UserControl>
And the secondary:
<UserControl x:Class="Project.Cards.ListSecondary" d:DataContext="{Binding Main.Cards.Secondary, Source={StaticResource Locator}}">
... (same) ...
</UserControl>
And the card view (where I need to replace "Primary.Direction" with something that lets me select Primary/Secondary):
<UserControl x:Class="Project.Cards.Card">
<UserControl.Resources>
... perhaps something here ...
</UserControl.Resources>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Description}" />
<TextBlock Text="{Binding Primary.Direction}" />
</StackPanel>
</UserControl>

If you want two instances of the same UserControl that differ in one respect, you figure out how to parameterize that. There are a couple of ways, but the simplest I thought of that fits your case was to just bind the differing value to a property of the View. This moves the specification of the different value to the owner.
We'll do that by defining a dependency property on the UserControl. It's a string, though it could be an object, and in the future you might want to make it one. Since we're using the view in a DataTemplate, we can bind a property of the DataContext to it there.
public partial class Card : UserControl
{
public Card()
{
InitializeComponent();
}
public String Direction
{
get { return (String)GetValue(DirectionProperty); }
set { SetValue(DirectionProperty, value); }
}
public static readonly DependencyProperty DirectionProperty = DependencyProperty.Register("Direction",
typeof(String), typeof(Card), new PropertyMetadata(null));
}
...and we'll use that in the UserControl like this:
<Grid>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Description}" />
<TextBlock
Text="{Binding Direction, RelativeSource={RelativeSource AncestorType=UserControl}}"
/>
</StackPanel>
</Grid>
The RelativeSource stuff tells the Binding to look for that Direction property on the UserControl object itself, rather than on the DataContext as it would otherwise do by default.
If Card.Direction were object instead of string, you'd make that TextBox a ContentControl and bind to its Content property. Then you could put anything in there -- XAML, a whole other viewmodel, literally anything that XAML can figure out how to display.
And here's how it looks in the wild:
<DataTemplate DataType="{x:Type vms:CardViewModel}">
<views:Card Direction="{Binding Primary.Direction}" />
</DataTemplate>
And here's my whole mainwindow content from my test code. I didn't bother creating user controls for the listboxes; the above template is an exact match for the way you're doing it.
<Window.Resources>
<DataTemplate x:Key="PrimaryItemTemplate" DataType="{x:Type vms:CardViewModel}">
<views:Card Direction="{Binding Primary.Direction}" />
</DataTemplate>
<DataTemplate x:Key="SecondaryItemTemplate" DataType="{x:Type vms:CardViewModel}">
<views:Card Direction="{Binding Secondary.Direction}" />
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel Orientation="Horizontal">
<ListBox
SelectedItem="{Binding SelectedCard}"
ItemsSource="{Binding Cards}"
ItemTemplate="{StaticResource PrimaryItemTemplate}"
/>
<ListBox
SelectedItem="{Binding SelectedCard}"
ItemsSource="{Binding Cards}"
ItemTemplate="{StaticResource SecondaryItemTemplate}"
/>
</StackPanel>
</Grid>
I originally thought of a more elaborate scheme where you give the view a DataTemplate instead, and it worked, but this is simpler. On the other hand, that was more powerful. I actually used that in the first version of the answer, before I came to my senses; it's in the edit history.
Thanks for a fun little projectlet.

Related

How to access data from ViewModel in ItemsSource tag

I'm making TabControl that can change dynamically using ItemsSource tag.
I want to know the way to access ViewModel data in ItemsSource tag.
I searched through the Internet. but I couldn't find the answer.
CODE
public class ViewModel
{
// this will be used in ItemsSource
private ObservableCollection<ActiveButton> _allExecuteButtonInfos = new ObservableCollection<ActiveButton>();
public ObservableCollection<ActiveButton> AllExecuteButtonInfos
{
get { return _allExecuteButtonInfos; }
set {
_allExecuteButtonInfos = value;
OnPropertyChanged();
}
}
// I want to get this data in ItemsSource
private List<string> _boardNameList = new List<string>();
public string BoardNameList
{
get { return _boardNameList; }
set {
_boardNameList = value;
OnPropertyChanged();
}
}
}
XAML
<Grid>
<TabControl Background="#FF292929" ItemsSource="{Binding AllExecuteButtonInfos}">
<TabControl.ContentTemplate>
<DataTemplate>
<ScrollViewer Grid.Row="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalScrollBarVisibility="Auto">
<Grid VerticalAlignment="Stretch" Margin="0,0,0,0" >
<ComboBox Width="334" Margin="0,0,0,0" Grid.Column="1" Grid.Row="1" Height="22" VerticalAlignment="Top"
<!-- I want to get data from ViewModel not in ItemsSource(AllExecuteButtonInfos) -->
<!-- eg) VM:BoardNameList, ViewModel.BoardNameList etc -->
ItemsSource="{Binding BoardNameList, Mode=TwoWay , UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedBoard, Mode=TwoWay}"/>
</Grid>
</ScrollViewer>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
I hope I can find the answer.
Thank you.
You could bind to the DataContext, i.e. the view model, of the parent TabControl using a RelativeSource:
<ComboBox ...
ItemsSource="{Binding DataContext.BoardNameList, RelativeSource={RelativeSource AncestorType=TabControl}}" />
Note that it's pointless to set the Mode of an ItemsSource binding to TwoWay since the control never sets the property. It's also meaningless to set the UpdateSourceTrigger to PropertyChanged in this case for the same reason.
I am not sure where you've defined the data context but I suppose that it's somewhere above the first 'Grid' markup. Something like this?
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
Then you have to somehow refer to the Datacontext of the window. You can do it this way
<ComboBox
ItemsSource="{Binding DataContext.BoardNameList, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" />
if the name of your view is not 'MainWindow', you have to change it to the view name where you have that code.
One of the best ways is to create a UserControl for each model and then put data templates in TabControl.Resources with DataType specified for all types you could put in ItemsSource - you get full customization of the view with nice seperation of XAML files.
<Grid>
<TabControl Background="#FF292929" ItemsSource="{Binding AllExecuteButtonInfos}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type MyViewModel1}">
<MyViewModel1_View ViewModel="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type MyViewModel2}">
<MyViewModel2_View ViewModel="{Binding}"/>
</DataTemplate>
</TabControl.Resources>
</TabControl>
</Grid>
I'm going from memory, so the binding may be done differently, but that's the basic idea.
That, or you use some kind of ViewResolver as the only item in the TabControl (something like this)
Basically, go even more MVVM :)
Provided that the DataContext of your view is set correctly to your ViewModel and AllExecuteButtonInfos is indeed available in your view, you can use a RelativeBinding to access properties which are not in the DataContext of your current scope.
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.BoardNameList}" />
With that, you are leaving the implicit DataContext of the DataTemplate, which is ActiveButton and access the object of the specified type via AncestorType. From there you can set a Path to the DataContext of the UserControl, which is, in your case, an object of the class ViewModel.
Imaging you are climbing up a ladder. From the ComboBox object up to your UserControl, from where you can access all underlying properties.

Use Style in TreeView HierarchicalDataTemplate

I'm new to this and can't quit get the correct syntax. This works correctly to capture the Left Mouse click on the textbox within the treeview:
<HierarchicalDataTemplate
DataType="{x:Type r:NetworkViewModel}"
ItemsSource="{Binding Children}"
>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding NetworkIP}" Width="110" >
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftClick"
Command="{Binding DataContext.SelectItem, RelativeSource={RelativeSource FindAncestor, AncestorType=TreeView}}"
CommandParameter="{Binding}" />
</TextBlock.InputBindings>
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
How can this be done using a Style block in the Resources?
The goal being to use the same style for all TextBoxes in the TreeView. Something that would sit in the Usercontrol.Resources and be refrenced by the HierarchicalDataTemplate.
If I understand you correctly, you could define a template in the controls or windows resources with a target type (opposed to key x:Key=...) to have it automatically applied to all items in the tree view.
Here is a small example with a definition of a template in the window resources, which contains the InputBindings definition. This template will be automatically applied to all objects of type ItemViewModel if no other template is explicitly defined by the ItemsControl or TreeView. In this example, the items are displayed in a simple ItemsControl but it works for the TreeView just the same.
Note that for this to work, all items in the TreeView need to be of the same type. It is not sufficient if they are derived from the same base type. The template will only be applied, if the type defined in Template.DataType is exactly the same as the type of the ViewModel. If your TreeViews ItemsScources contain mixed type, you would need to specify the template for every type separately.
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type loc:ItemViewModel}">
<TextBlock Text="{Binding Name}" Width="110" >
<TextBlock.InputBindings>
<MouseBinding
MouseAction="LeftClick"
Command="{Binding SelectItem}" />
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Items}" />
</Grid>
</Window>

Best way to implement a "parameter view" like in Visual Studio - TemplateSelector or what?

I am building a tool and want to have a listview with parameters.
i have a base class called Parameter and many different derived classes, for example:
ParameterAddress, ParameterBool, ParameterString, ParameterScreen, ...
Each Parameter looks a bit different..
ParameterBool: Label + Checkbox
ParameterString: Label + Textbox
ParamterAddress: Label + Textbox + Button (for a new Dialog)
...
I have done this in my first try with an DataTemplateSelector.
Was nice, works "well"..
In my window i implemented the events like text changed, button clicked and so on..
but now i want to use this parameter view in another window too and dont want to copy the same text changed, button clicked events in each window again..
So my second try was to build in this into my parameter class.
Each Parameter class will have an Stackpanel and each derived class adds its Controls into it and can so handle whatever events just in one place!
But now in my listview only comes up the text "....Stackpanel" .
I think way 1 was a better direction, way 2 could work anyway ..
But what would be the "best" way?
EDIT
here are my actual datatemplates:
<Window.Resources>
<DataTemplate x:Key="textBoxTemplate">
<TextBox Text="{Binding Path=Value}" Width="100"></TextBox>
</DataTemplate>
<DataTemplate x:Key="checkBoxTemplate">
<CheckBox IsChecked="{Binding Path=Value}" IsThreeState="False"></CheckBox>
</DataTemplate>
<DataTemplate x:Key="screen_template">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Path=Value}" Width="100"></TextBox>
<Button Content="..." Click="btn_screenlist_Click" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="address_template">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Path=Value}" Width="100"></TextBox>
<Button Content="..." Click="btn_address_Click" />
</StackPanel>
</DataTemplate>
<local:ParameterTemplateSelector
x:Key="parameterTemplateSelector"
TextBoxTemplate="{StaticResource textBoxTemplate}"
CheckBoxTemplate="{StaticResource checkBoxTemplate}"
Screen_template="{StaticResource screen_template}"
Address_template="{StaticResource address_template}"
/>
</Window.Resources>
in my opinion its very unusable..
you have to maintain so many peaces of code to work..
create a new template
put it into the datatemplate resource
dont forget to put it into the datatemplateselector class
with having my "controls" directly in my class i have only this one peace of code.. ?!
You should make Datatemplates without keys pointing to your derives parameter class types. Wpf will automatically show the most suitable template for each class.
As for the textchanged or click-events, you should hang your "ontextchanged" or "isCheckedChanged) into the setter of the binding target (e.g. parameter.Text or parameter.IsTrue).
The OnClick of Buttons should be replaced with a proper command.
Whatever you did: don't instanciate controls in code behind ... thats the worst way to do it.
Okay, after some hours playing around, researching, reading and testing ..
Here is my actual solution, which works fine in multiple windows !
Please leave some comments, improvements, whatever .. Thanks.
I am having now a Parameter.cs and Parameter.xaml
Parameter.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:locale="clr-namespace:tests"
>
<DataTemplate DataType="{x:Type locale:ParameterString}">
<TextBox Text="{Binding Value}" MinWidth="100"></TextBox>
</DataTemplate>
<DataTemplate DataType="{x:Type locale:ParameterBool}">
<CheckBox IsChecked="{Binding Value}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type locale:ParameterAddress}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Name}"/>
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" MinWidth="100"/>
<Button
Content="..."
Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}"
Command="{Binding EditAddrCmd}"
/>
</StackPanel>
</DataTemplate>
Snippet of Parameter.cs
public class ParameterAddress : Parameter
{
ControllerList controllers;
#region constructors
public ParameterAddress (ControllerList controllers, String address)
{
this.controllers=controllers;
Name="Address";
Value=address;
EditAddrCmd=new RelayCommand(ex => EditAddrCmdExec(), cex => EditAddrCmdCanExec());
}
// ..
#endregion // constructors
#region commands
public ICommand EditAddrCmd { get; internal set; }
private void EditAddrCmdExec ()
{
// open dialog to edit address and save to <Value>
}
private bool EditAddrCmdCanExec ()
{
return true;
}
#endregion // comannds
public override bool isValid ()
{
return true;
}
}
now i simply add to each window where i want to use these parameters the Parameter.xaml to its Resources:
SomeWindow.xaml
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Parameter.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
#SnowballTwo
did you mean this way of doing it?

Changing content according to selected option in WPF

I'm interested in creating an app that displays some buttons and changes a viewport according to the selected button. The viewport in my app is a ContentControl and I thought of changing its content whenever a button is clicked. However, I believe there's a better approach, by perhaps injecting the ViewModels of each of the Views I want to present to the ContentControl and styling them using DataTemplates (Since I want to avoid having a grid with many controls and just setting their Visibility property whenever I want to show a particular view). Which of the approaches seems better to you? Do you have a different approach for this?
The view should be something similar to this:
Thanks!
Usually have a ViewModel behind the window which contains:
ObservableCollection<IViewModel> AvailableViewModels
IViewModel SelectedViewModel
ICommand SetCurrentViewModelCommand
I display the AvailableViewModels using an ItemsControl, which has its ItemTemplate set to a Button. The Button.Command is bound to the SetCurrentViewModelCommand, and it passes the current data item from the AvailableViewModels collection in through the CommandParameter
To display the content area, I use a ContentControl with ContentControl.Content bound to SelectedViewModel, and DataTemplates get used to tell WPF how to render each ViewModel.
The end result is my XAML looks something like this:
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModelA}">
<local:ViewA />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelB}">
<local:ViewB />
</DataTemplate>
</Window.Resources>
<DockPanel>
<Border DockPanel.Dock="Left" BorderBrush="Black" BorderThickness="0,0,1,0">
<ItemsControl ItemsSource="{Binding AvailableViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.SetCurrentViewModelCommand, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
CommandParameter="{Binding }"
Margin="2,5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<ContentControl Content="{Binding SelectedViewModel}" />
</DockPanel>
You can view an example of the full code used for such a setup on my blog

Silverlight DataGrid styling

I have a DataGrid whose ItemsSource is bound to a changing Observable Collection. Inside of this collection is a Business Object. Based on some of the values of the Business Object's properties, I would like to be able to modify the color of the text for each item displayed in my DataGrid once the ItemsSource is created.
Has anyone done this before or ran across something similar? Thanks in advance.
<DataTemplate x:Key="MyTemplate">
<Grid x:Name="LayoutRoot">
<TextBlock Text="{Binding MyText}"
Foreground="{Binding MyStatus, Converter={StaticResource colorConverter}}" />
</Grid>
</DataTemplate>
I added the above code and inserted the TemplateColumn to the grid as below:
<data:DataGridTemplateColumn Header="Testing"
CellTemplate="{StaticResource MyTemplate}"/>
The code works fine and pulls out the correct text but the converter never fires and the Binding of the foreground is never called from the get on it.
Any ideas?
Yes. Use a Value Converter when databinding.
<UserControl.Resources>
<myconverters:BackColor x:Key="BackColor" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="{Binding SomeValue, Converter={StaticResource BackColor}" >
</Grid>
Then have your converter class implement IValueConverter and return a Brush object of some kind. You usually don't have to implement ConvertBack()
Adding to BC's answer:
You can make a DataGridTemplateColumn and specify a data template for cells in a column. In the data template you can bind the text colour.
<swcd:DataGrid ... >
<swcd:DataGrid.Columns>
<swcd:DataGridTemplateColumn Header="MyColumn" CellTemplate="{StaticResource MyColumnDataGridCellTemplate}"/>
...
in resources:
<DataTemplate x:Key="MyColumnDataGridCellTemplate">
<Grid>
<TextBlock Text="{Binding someproperty}" Foreground="{Binding someotherproperty, Converter={StaticResource MyConverter}}"/>
...

Categories

Resources