I want to bind my custom TreeViewItem to IsExpanded.
The normal way without a custom TreeView Item would look like this.
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded}"></Setter>
</Style>
</TreeView.ItemContainerStyle>
But I want to bind it to my own TreeViewItem for example named CoolTreeItemModel.
CoolTreeItemModel could look like this:
public class CoolTreeItemModel : XY
{
public LocalTreeItemModel()
{
TreeViewItems = new List<CoolTreeItemModel>();
}
public List<CoolTreeItemModel> TreeViewItems { get; set; }
public SomeType IsValid { get; set; }
public bool IsExpanded { get; set; }
}
How CoolTreeItemModel is bound:
<TreeView HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding TreeRoots}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="models:CoolTreeItemModel"
ItemsSource="{Binding TreeViewItems}"></HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
How can I bind to the IsExpanded property of CoolTreeItemModel ?
Thanks for your help.
The "normal way" is applicable in this case. Each CoolTreeItemModel will be implicitly wrapped in a TreeViewItem container so you should be able to bind to your IsExpanded property. You may want to set the Mode of the binding to TwoWay though:
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"></Setter>
</Style>
</TreeView.ItemContainerStyle>
Binding requires the use of dependency properties. Dependency Properties On MSDN You would need to define a dependency property like so:
public ClassName
{
public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register("IsExpanded", typeof(bool), typeof(ClassName));
public bool IsExpanded
{
get { return (bool)GetValue(IsExpandedProperty); }
set { SetValue(IsExpandedProperty, value); }
}
}
From there you can then include the namespace of your class in the top of your xaml and then bind to IsExpanded like normal
<MyNamespace:ClassName IsExpanded="{Binding Value}" />
I Would like to raise one concern of my though. When developers new to XAML/WPF start developing their own controls, the distinction between view data and business data gets muddled. If this is your own control to be consumed by others, there is an entire style template that may need to be created and used. All of this binding should be taking place in this style template, following the example set forth by Microsoft. Also a custom control should have no view model or defined data context as it is expected to be provided by the consumer, meaning a lot of your logic is going to be in the code behind, which is okay as a custom control is only view related and shouldn't have any business logic in it.
Related
I have a WPF TreeView for which I've implemented a small model class behind the scenes. I bind a list of them to the TreeView's ItemsSource when creating the control. (I've pared the code here down a bit for the sake of simplicity, but it should be reproducable.)
public class TreeViewItemModel
{
public ObservableCollection<TreeViewItemModel> Children { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
public TreeViewItemModel()
{
Children = new ObservableCollection<TreeViewItemModel>();
IsSelected = false;
}
}
public partial class MainWindow : Window
{
public ObservableCollection<TreeViewItemModel> MyTree { get; set; }
public MainWindow()
{
InitializeComponent();
// Add some dummy values
List<TreeViewItemModel> items = new List<TreeViewItemModel>();
for (int i = 0; i < 10; i++) items.Add(new TreeViewItemModel() { Name = ("Node" + i) });
MyTree = new ObservableCollection<TreeViewItemModel>(items);
DataContext = this;
}
}
My TreeViewItems themselves contain checkboxes. Now, what I'd like to do is to bind IsSelected to the checkbox so that at the end of the day I (hopefully) have a list of TreeViewItemModel classes with IsSelected set to whether or not the checkbox is checked.
To that end, I have this style:
<Style x:Key="{x:Type TreeViewItem}" TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
and this TreeView declaration:
<TreeView ItemsSource="{Binding MyTree}" >
<TreeView.Resources>
<DataTemplate DataType="{x:Type UI:TreeViewItemModel}">
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected}" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
This almost works. I can create a list of items programmatically and they get bound to the TreeView, check off items in my TreeView, and when I check them in C# IsSelected is set appropriately.
Except for one thing: my TreeViewItems all act like RadioButtons. I click one, and it sets IsSelected to true. I rejoice! But then I click on another... and it deselects the first TreeViewItem! I can never have more than one selected at a time.
But... why?! I don't understand at all. They're all bound to different items on the backend, so why would setting IsSelected change the state of another item?
:'(
In your Style for TreeViewItem you bind TreeViewItem.IsSelected to IsSelected property of your view model which basically means that CheckBox will be checked if TreeViewItem is selected. It happens so because WPF TreeView does not support multi selection.
You can easily add multi selection by changing TreeViewItem content into CheckBox or ToggleButton, exactly what you're trying to achieve, but then you cannot bind TreeViewItem.IsSelected to your view model.
What currently happens is
you click to select one item
previous TreeViewItem.IsSelected is set to false
this is passed to your view model by IsSelected
which is then passed back to CheckBox.IsChecked
new TreeViewItem.IsSelected is set to true
and so on
Remove Style for TreeViewItem and leave only CheckBox.IsChecked to IsSelected binding
On a side note you don't need StackPanel when you want to show just one element like CheckBox
You try removing your style? You should then see multiple selections
I am trying to "throw" several objects on a canvas.
I have 5 types of objects with several properties (like text, position, bitmap, and more)
Each type should be rendered differently (one type will be rendered as textblock, one as bitmapImage, ect.)
I have 5 observableCollections that holds all the objects of same type.
I can bind one of them (the one that represents text for example) to the canvas and using a data template with a textblock to bind each property to the right parameter (like visibility and localtion).
now my 2nd type should be bind to a bitmap.
How can I do that ? How can I bind 5 different types to the canvas and have each type converted to the right element ?
One possible way is to aggregate all collections to a single one... but then it tries to convert everything to the first type...
There`s a great answer to somehow related question that we can extend to make it work with your problem.
Say we have two types that can be dropped to Canvas:
public class TextClass
{
public string Text { get; set; }
}
public class RectangleClass
{
public Brush FillBrush { get; set; }
}
To facilitate the use of collection to bind to we can use the code from answer I mentioned but change ItemTemplate for our custom DataTemplateSelector:
<ItemsControl Name="icMain">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Left}" />
<Setter Property="Canvas.Top" Value="{Binding Top}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplateSelector>
<TestWPF:CustomTemplateSelector>
<TestWPF:CustomTemplateSelector.TextTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" />
</DataTemplate>
</TestWPF:CustomTemplateSelector.TextTemplate>
<TestWPF:CustomTemplateSelector.RectangleTemplate>
<DataTemplate>
<Rectangle Height="25" Width="25" Fill="{Binding FillBrush}" />
</DataTemplate>
</TestWPF:CustomTemplateSelector.RectangleTemplate>
</TestWPF:CustomTemplateSelector>
</ItemsControl.ItemTemplateSelector>
</ItemsControl>
And that`s the template selector I used:
public class CustomTemplateSelector: DataTemplateSelector
{
public DataTemplate TextTemplate { get; set; }
public DataTemplate RectangleTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is TextClass)
return TextTemplate;
else if (item is RectangleClass)
return RectangleTemplate;
else return base.SelectTemplate(item, container);
}
}
Well, all that`s left is to bind our collection. I used simple List in code behind just for test:
List<object> aggregation = new List<object>()
{
new TextClass() { Text = "Some test text" },
new RectangleClass() { FillBrush = new SolidColorBrush(Colors.Tomato)}
};
icMain.ItemsSource = aggregation;
This code shows some test text and yummy tomato rectangle. These sample objects do not have any positioning logic, but I figured you have that already.
I am using Caliburn Micro in my Project and i have many UserControls and thier viewmodel inherited from PropertyChangedBase, i want this UserControl to be added to a Canvas in my ShellView. I dont want to use IWindowManager from showing Windows instead i want them to get added in a Canvas.
Please help. How can i do that.
If you use ContentControl within your ShellView you can hook into the View-ViewModel binding process of Caliburn.Micro.
I assume that in your ShellViewModel you have a bunch of properties exposed that are types of ViewModel. If you place a ContentControl in your ShellView (this could be on/as a child of Canvas if that is the container you wish to use to layout your Shell), and then name that control with the name of the property in your ShellViewModel you wish it to be bound to, then Caliburn's ViewModelBinder will do the rest for you.
As an example say you have a VM called FizzViewModel and a matching View called FizzView (which is just a UserControl) and you want FizzView to appear on your ShellView you could do something like the following...
A stripped back ShellViewModel
public class ShellViewModel : Screen, IShell
{
public ShellViewModel(FizzViewModel theFizz)
{
TheFizz = theFizz;
}
public FizzViewModel TheFizz { get; set; }
}
And its matching ShellView
<UserControl x:Class="ANamespace.ShellView">
<Canvas>
<ContentControl x:Name="TheFizz"></ContentControl>
</Canvas>
</UserControl>
Here because the ContentControl is named TheFizz, it will be bound by Caliburn to the property with that name on your VM (the one of type FizzViewModel)
Doing this means you don't have to laydown your UserControl's using their true types on your ShellView, you let Caliburn do the work for you via conventions (which all so means its easy to swap out the type TheFizz if you just add a little more interface indirection).
UPDATE
From the extra information you have provided in the comments, I can now see you are actually looking at a problem that requires an ItemsControl.
The default DataTemplate Caliburn uses looks like the following
<DataTemplate xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro">
<ContentControl cal:View.Model="{Binding}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch" />
</DataTemplate>
You will notice that it uses a ContentControl, which has some advantages as I have discussed above. Basically what this will do is allow Caliburn to provide DataTemplateSelector like behaviour to the items in your ItemsControl. So you can add VMs of different types to the collection your ItemsControl is bound to and this default DataTemplate will resolve the type of View to use to display it. The following demos a very simple example of how you can achieve what you want.
First the ShellViewModel, take note of the BindableCollection named Items
[Export(typeof(IShell))]
public class ShellViewModel : IShell
{
public ShellViewModel()
{
Items = new BindableCollection<Screen>();
_rand = new Random();
}
public BindableCollection<Screen> Items { get; set; }
private Random _rand;
public void AddItem()
{
var next = _rand.Next(3);
var mPosition = System.Windows.Input.Mouse.GetPosition(App.Current.MainWindow);
switch (next)
{
case 0:
{
Items.Add(new BlueViewModel
{
X = mPosition.X,
Y = mPosition.Y,
});
break;
}
case 1:
{
Items.Add(new RedViewModel
{
X = mPosition.X,
Y = mPosition.Y,
});
break;
}
case 2:
{
Items.Add(new GreenViewModel
{
X = mPosition.X,
Y = mPosition.Y,
});
break;
}
default:
break;
}
}
}
And then a few dummy VM types that you want to display in your Shell. These could be/do anything you like:
public abstract class SquareViewModel : Screen
{
public double X { get; set; }
public double Y { get; set; }
}
public class BlueViewModel : SquareViewModel
{
}
public class RedViewModel : SquareViewModel
{
}
public class GreenViewModel : SquareViewModel
{
}
Now a ShellView, note the ItemsControl which binds to the Items property on your ShellViewModel
<Window x:Class="WpfApplication2.ShellView"
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">
<Grid >
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<ItemsControl x:Name="Items"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas cal:Message.Attach="[Event MouseLeftButtonUp] = [Action AddItem()]"
Background="Transparent"></Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
</Window>
And an example of a UserControl that will be used to display the GreenViewModel, create 2 more of these, changing the names to RedView and BlueView and set the backgrounds appropriately to get the demo to work.
<UserControl x:Class="WpfApplication2.GreenView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="30"
Height="30">
<Grid Background="Green"></Grid>
</UserControl>
What this example does when put together is creates colored squares on the Canvas of your shell based on the location of the mouse click. I think you should be able to take this and extend it to your needs.
Consider the scenario shown below
class MyViewModel
{
public bool IsSelected {get;set;}
}
class SomeClass
{
public bool IsSelected {get;set;}
public object Data {get;}
}
<DataTemplate x:Key="ItemTemplate>
<Image ... />
</DataTemplate>
<SomeControl ItemsSource={Binding MyViewModels}"
ItemTemplate={StaticResource ItemTemplate}" />
The classes SomeControl and SomeClass are third party classes which I cannot modify. Internally, SomeControl creates instances of SomeClass and assigns my view model to its Data property. So, the data context of my ItemTemplate is SomeClass
I want to bind the IsSelected property on SomeClass to the IsSelected property of MyViewModel. How do I do that?
First, I would consider ditching a third-party control that does something so at odds with how the ItemsControl normally works. Your data context should be your view model. Period.
That said, you should be able to work around it if the item container is a SomeClass like this:
<SomeControl ...>
<SomeControl.ItemContainerStyle>
<Style TargetType="SomeClass">
<Setter Property="IsSelected" Value="{Binding Data.IsSelected, Mode=TwoWay}"/>
</Style>
</SomeControl.ItemContainerStyle>
</SomeControl>
How do I use a DelegateCommand in a TreeView to get the Expanded event?
Should I be using the DelegateCommand or is there another way?
Thanks
Since you are mentioning Prism, I assume you have a controller or ViewModel attached to the view containing your TreeView...
That being the case, expose a boolean property IsExpanded
private bool _isExpanded;
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (value != _isExpanded)
{
_isExpanded = value;
RaisePropertyChanged("IsExpanded");
// Apply custom logic here...
}
}
}
Now to hook this property up to the TreeView, you need to apply the following style in the TreeView's resources (or further up the Visual tree as appropriate)
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding Path=IsExpanded, Mode=TwoWay}" />
</Style>
NB: You can also use a similar technique to hook up the IsSelected property - also very useful!!