I have model objects set up like so:
public class Model
{
public ObservableCollection<Model> Children{get;set;}
public string Name{get;set;}
}
This gives me a tree of model objects of arbitrary size and depth. I display these in a WPF TreeView using a hierarchical datatemplate. Because of the ObservableCollection, I can add model objects anywhere in the model tree and the UI will update accordingly.
The annoyance comes because even though a new model object might be added at the third level, for example, it might not be immediately visible.
When a new model object is added anywhere in the tree, I would like the TreeView to automatically select the new node, and expand its parents to that it is immediately visible to the user. This doesn't seem to be immediately obvious.
What I don't want to do is pollute the Model objects with properties which only make sense in a particular WPF control. Nor do I want to add a parent property to the Models.
It seems that this must have been tackled before by someone. Does anyone have an idea on how to tackle this?
Create 2 properties in your model as below
IsExpanded and IsSelected
and in your treeview ItemContainerStyle add the below setters
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded}"/>
Noe everytime you set these 2 properties in your viewmodels, the corresponding treeviewitem will be selected/expanded
Related
We are using a TreeView control in an MVVM C# WPF application.
I have successfully added Expand/Collapse All functionality to the View - iterating through all the Items in the TreeView, setting IsExpanded and then calling Focus on the TreeView to refresh the changes.
I have also successfully added functionality to clear the currently selected Item in the TreeView when clicking the empty space in the TreeView.
My current task and problem is to automatically select any newly added Items in the TreeView. The IsSelected and IsExpanded properties of the TreeViewItems are bound to matching properties in the ViewModel and setting IsSelected on the ViewModel works as long as the parent node is expanded.
I have tried setting IsExpanded on the parent ViewModel but as far as I am aware I need to then call Focus on the TreeView to force it to update and expand its representation of the tree. Since I am in the ViewModel, I have no connection to the TreeView without breaking MVVM.
How can I make this work?
Edit: Source added for pushpraj
The tree items are stored like this:
private ObservableCollection<NodeViewModel> _children = new ObservableCollection<NodeViewModel>();
public ObservableCollection<NodeViewModel> Children
{
get { return _children; }
set
{
_children = value;
OnPropertyChanged("Children", this);
}
}
The Children property of our root node is bound to the ItemsSource property of the TreeView. The child nodes are added programmatically to the Children property of the root node and each other.
The source I am working with is not my own and I am not overly experienced with WPF so please forgive me if I am giving you the wrong information. I have also noticed that we are using some Drag/Drop functionality in our TreeView from Gong Solutions. Could this be the cause of our problem? Setting IsExpanded to true in our ViewModel does not expand the entry in the tree view.
add the following style in the resources
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="FocusManager.FocusedElement"
Value="{Binding RelativeSource={RelativeSource Self}}" />
</Style>
this will enable every new TreeViewItem to focus itself when created
try it and see if this is what you are looking for
I am an intermediate WPF developer, with working knowledge on how to implement dependency properties as well as simple custom controls. I do not yet understand how I can add a DataTemplate dependency property to a custom control, and use it to define the element tree for each datum in a collection of data.
The full story is that I have been working on creating a WPF map control that displays many different points and geometric shapes on the map, over map tiles. These shapes will translate with the rest of the map when user "drags" the map around.
I have accomplished this, insofar that I have created the map control, and can add child elements to it in Xaml that have map coordinates. I would like to take this farther, and add properties for collections of data, i.e. points, areas, etc. To better understand what I'm looking for, I would like to re-create two properties from ListBox: ItemsSource and ItemTemplate.
I have added two dependency properties to my Map control - PointsSource and PointsTemplate. PointsSource is of type IEnumberable and represents the collection of data to display on the map. PointsTemplate represents what each of those datum should look like. Simply throwing these properties into my control is obviously not enough, but I am unsure of how to coordinate them with one another. If anyone has working knowledge of creating a custom data control with it's own DataTemplate properties for changing the UI tree for each data element, I would really appreciate it.
I have found what I am looking for in the DataTemplate itself. The DataTemplate provides a function for code behind called LoadContent(). LoadContent produces a dependency object that represents the tree of content for a given datum. From what I have found elsewhere, the common use for LoadContent might look like the following:
foreach (object point in PointsSource)
{
FrameworkElement pointElement = _PointsTemplate.LoadContent() as FrameworkElement;
pointElement.DataContext = point;
this.Children.Add(pointElement);
}
The above code will add a content tree for every single element of data, and we give it the datum to bind its DataContext to.
If anyone has working knowledge of creating a custom data control with it's own DataTemplate properties for changing the UI tree for each data element, I would really appreciate it.
Basically, you will want to use an ItemsControl inside your control template, and bind its ItemsSource and ItemTemplate properties to your custom Dependency Properties. Ie,
<Style TargetType="{x:Type local:CustomControl}">
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type local:CustomControl}">
<ItemsControl ItemsSource="{TemplateBinding PointsSource}"
ItemTemplate="{TemplateBinding PointsTemplate}"
/>
</ControlTemplate>
</Setter>
</Style>
(assuming the DPs IEnumerable - "PointsSource" and DataTemplate - "PointsTemplate")
I have in my ParentView(DashboardConsultants) a gridview which shows a custom tooltip when the user's mousepointer is hovered over a cell. The tooltip show a View (AgreementDetails_View) which shows information of the Agreement binded to that cell. I will show the code I have now so you can better understand my question:
DataGrid Cell in ParentView:
<DataGridTextColumn Header="Okt" Width="*" x:Name="test">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Tag" Value="{Binding Months[9].AgreementID}"/>
<Setter Property="Background" Value="White" />
<Setter Property="DataGridCell.ToolTip" >
<Setter.Value>
<v:UC1001_AgreementDetails_View Background="#FFF" Opacity="0.88" />
</Setter.Value>
</Setter>
My ChildView:
public UC1001_DashBoardConsultants_View(UC1001_DashboardConsultantViewModel viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
In the ViewModel, I have following method to get the right Agreement from the database:
private void GetRefData()
{
UC1001_ActiveAgreementArguments args = new UC1001_ActiveAgreementArguments();
args.AgreementID = 3;
DefaultCacheProvider defaultCacheProvider = new DefaultCacheProvider();
if (!defaultCacheProvider.IsSet("AgrDet:" + args.AgreementID))
{
ConsultantServiceClient client = new ConsultantServiceClient();
AgreementDetailsContract = client.GetAgreementDetailsByAgreementID(args);
defaultCacheProvider.Set("AgrDet:" + args.AgreementID, AgreementDetailsContract, 5);
}
else
{
AgreementDetailsContract = (UC1001_ActiveAgreementContract)defaultCacheProvider.Get("AgrDet:" + args.AgreementID);
}
}
As you can see for now, the method always calls the same Agreement, (I did that for testing purposes) but now I want the Agreement which ID is specified in the DataGrid Cell Tag (in this example it's the Months[9].AgreementID).
I can give it to the ViewModel in my Child View's constructor, but I don't think that it's allowed due to the MVVM Pattern (or is it allowed?).
So my question is: How can I pass the AgreementID specified in my ParentView to the ChildView's ViewModel to get the right data for the ChildView?
Ofcourse, more information/code/clarification can be happily provided, just ask :)
Thanks in advance!
Not sure that I got question in righ way but my feelings like you need to use Commands instead of introducing tied coupling by passing back reference to parent itself
Personally, I feel that WPF Views should be nothing more than a pretty reflection of the ViewModel. So the View should not actually be passing any data to the ViewModels - instead it should be reflecting the ViewModel's data.
In your case, I would attach a property to the object that is displayed in each DataGrid Row. For example, if your DataGrid contained Agreement objects, I would ensure that each Agreement object had a property called AgreementDetails which can be viewed from the ToolTip
It's pefectly legal and valid to pass in that ID eitehr via a constructor or through a property. I'm not sure from your code, but if your parent is the one with access to your model, you can also pass teh model into your view model (i.e. via constructor, property, or method).
One thing I often do in this case is to add a property such as the following in the ViewModel of my Parent:
object ActiveItem {get;set;}
I then bind that ActiveItem to the ActiveItem in my grid.
<DataGrid SelectedItem="{Binding ActiveItem}">
</DataGrid>
I have CollectionViewSource in which dates grouped by years and months. Dates are displayed in TreeView (accurately in RadTreeView).
The target is to change selected date without recreating the view (do not call Refresh method).
To do this I implemented IEditableObject in date view model and changed date so:
var selectedDate = SelectedDate;
var editableCollectionView = Dates.View as IEditableCollectionView;
if (null != editableCollectionView && !editableCollectionView.IsEditingItem)
{
editableCollectionView.EditItem(selectedDate);
selectedDate.Date = dt.Date;
editableCollectionView.CommitEdit();
}
But in this case TreeView lost selection and I need to select "selected item" again that leads refreshing data bounded to selected date.
How can I solve this issue? Perfectly using MVVM way.
UPDATE:
If date is alone in group, changing date causes collapsing item which contains it.
UPDATE 2:
May be I shouldn't use SelectedDate property and work only with IsSelected and IsExpanded?
Leverage MVVM for Tree View Item.
Include two writeable properties in your item level class (which serves as the data context to your individual tree view item)
IsExpanded
IsSelected
Have INotifyPropertyChanged implemented and property changed notification raised in the setter of the above two properties.
Now at TreeViewLevel have a Style that binds these properties.
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
....
</...>
This way the expansion and selection is maintained on the tree view as long as it is maintained in the tree view item's data context.
Now remember that expanded states can be true for multiple items but selection state true is applicable for only one item along the entire tree view.
Hope this helps.
I have the following problem: I'm building a chat WPF application on which I want the user to be connected with different accounts to GTalk. I've made a ViewModel that permits handling the communication but I don't know which is the best way to handle DataContext. I think that I need different DataContexts for every connection instance but I don't know if this is the right way and don't know how to define multiple DataContexts in a View.
A DataContext provides the default binding source for an element in the view. It is inherited from parent to child within the visual tree. You can change the DataContext for any element by binding it to some property of its parent DataContext. This is a common way of creating 'islands' that bind to a child view model.
For example, if you have a User view model that has an Address Property, you can render this in an AddressUserControl as follows:
<StackPanel>
... elements bound to properties of User ...
<AddressUserControl DataContext="{Binding Address}"/>
</StackPanel>
If you have a variable number of accounts, you can use the ItemsControl.
The rough idea behind this: You can bind the ItemsControl to a list of "Sub-ViewModels" in your main ViewModel and define a DataTemplate (= a View) for each of them. The DataContext of each DataTemplate is automatically assigned to a corresponding item in your ViewModel's list.