I have an Entity Framework model and TreeView which has a binding with model.
in constructor:
Context.TestCategory.Load();
my TreeView:
<TreeView x:Name="DbTree" ItemsSource="{Binding Context.TestCategory.Local}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Tests}" >
<TextBlock Text="{Binding Name}" ContextMenuOpening="ContextMenu_ContextMenuOpening">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Добавить тест" Click="TestAdd" CommandParameter="{Binding
RelativeSource={RelativeSource
AncestorType={x:Type ContextMenu}}}"></MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
........................................
other items
........................................
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
After updating items in ItemsSource I need to update the related TreeView branch, what is the best way to do this?
I'm not sure binding your ItemsSource directly to a context property is a great thing to do.
What you're supposed to do with binding is bind to a property that can do RaisePropertyChanged when it has changed, and then the UI elements will update if needed. I use MVVM so my binding is all from a ViewModel which is the datacontext for the View, and which implements the INotifyPropertyChanged interface. Then within that my property setter can look like this:
private List<TestCategories>_myItemSourceList;
public List<TestCategories> MyItemSourceList
{
get { return _myItemSourceList; }
set
{
if (value != _myItemSourceList)
{
_myItemSourceList= value;
RaisePropertyChanged(() => MyItemSourceList);
}
}
}
And the XAML would look like ...
<TreeView x:Name="DbTree" ItemsSource="{Binding MyItemSourceList}">
Obviously because you're not using MVVM your code will be slightly different, but the principle remains: bind your ItemSource to a property that can RaisePropertyChanged.
EDIT: My RaisePropertyChanged is within the Prism framework, so is using a lambda, but the 'standard' was is with a string like this RaisePropertyChanged("MyItemSourceList")
Related
I have UserControl containing a procedurally generated ItemsControl. Each item in the ItemsControl contains a ListBox and there is no consistent number of how many items will be generated. The selected item in the listbox is bound to am object (SelectedClass) in the ViewModel. The initial value of the SelectedClass object is null.
The scenario I am running into is this:
User selects ListBoxItemA from ItemsControlItemA, PropertyChanged fires, SelectedClass object is set to the proper value.
User then selects ListBoxItemA from ItemsControlItemB, PropertyChanged fires, SelectedClass object is set to the proper value.
User then selects ListBoxItemA from ItemsControlItemA, but since the selection in that list is still considered to be the same item from step 1, PropertyChanged does not fire, and the SelectedClass object remainsListBoxItemA from ItemsControlItemB.
So my question is, how do i get the UpdateSourceTrigger event to fire OnClick rather than on PropertyChanged, and is that even the best way to approach it? I'm using the MVVM Light framework.
Thanks
<ItemsControl ItemsSource="{Binding AllUpcomingClasses}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding classDescription}" />
<ListBox Name="availableClasses"
ItemsSource="{Binding ClassInstances}"
SelectedItem="{Binding
DataContext.SelectedClass,
Mode=TwoWay}
RelativeSource={RelativeSource
FindAncestor,
AncestorType={x:Type UserControl}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid>
<TextBlock Text="{Binding ClassDate}" />
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Edit: Cleaned up the example a bit for readability.
You could handle the PreviewMouseLeftButtonDown event of the ListBoxItem container and "manually" set the SelectedItem property of your view model if the clicked item is the one that is already selected:
<ListBox SelectedItem="{Binding SelectedItem}" xmlns:s="clr-namespace:System;assembly=mscorlib">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="OnMouseLeftButtonDown"/>
</Style>
</ListBox.ItemContainerStyle>
<s:String>A</s:String>
<s:String>B</s:String>
<s:String>C</s:String>
</ListBox>
private void OnMouseLeftButtonDown(object sender, MouseEventArgs e)
{
ListBoxItem lbi = sender as ListBoxItem;
if (lbi != null)
{
YourViewModel vm = DataContext as YourViewModel;
if (vm != null)
{
var selectedItem = lbi.DataContext as YourObjectType;
if (vm.SelectedItem == selectedItem)
{
vm.SelectedItem = selectedItem;
e.Handled = false;
}
}
}
}
If you don't want to handle this in the code-behind of the view you could wrap the same functionality in an attached behaviour: https://www.codeproject.com/articles/28959/introduction-to-attached-behaviors-in-wpf. The former approach doesn't really break the MVVM pattern though since you are just kind of "extending" the ListBox control functionality to be able to set the same view model source property that the ListBox control sets for you when you select a new item. This functionality belongs to the view or the control.
This one is a bit complicated. I'm trying to create a usercontrol which has a treeview and a few other controls, to create a reusable control that will be useful for other implementations.
The problem that I'm having is that I cannot figure out how to insert a HierarchicalDataTemplate defined outside the control into the treeview that is inside the control.
Outside the control here is my WPF
<Grid>
<Grid.Resources>
<HierarchicalDataTemplate x:Key="HierarchicalDataTemplate" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
</Grid.Resources>
<masterTreeUserControl:MasterTreeUserControl
HierarchicalDataTemplate="{StaticResource HierarchicalDataTemplate}"
ItemsSource="{Binding Path=SelectiveListViewModel.Items, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding Path=SelectiveListViewModel.SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
EnableAdd="False"
ItemType="{x:Type viewmodels:LocationItem}"
/>
</Grid>
The MasterTreeUserControl has a HierarchicalDataTemplate DependencyProperty
HierarchicalDataTemplateProperty = DependencyProperty.Register("HierarchicalDataTemplate",typeof(HierarchicalDataTemplate),typeof(MasterTreeUserControl));
private static readonly DependencyProperty HierarchicalDataTemplateProperty;
public HierarchicalDataTemplate HierarchicalDataTemplate
{
get
{
return (HierarchicalDataTemplate)GetValue(HierarchicalDataTemplateProperty);
}
set
{
SetValue(HierarchicalDataTemplateProperty, value);
}
}
And so far the Treeview inside the control looks like this.
<TreeView Name="ItemListView"
Grid.Row="2"
Margin="0,5,0,0"
ItemsSource="{Binding Source={StaticResource ItemsCvs}}">
In other implementations of a treeview what I would normally do is HierarchicalDataTemplate in a fashion similar to this.
<TreeView.Resources>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="Hello"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
However, as the HierarchicalDataTemplate is inside a dependency property, I need to somehow bind it into the Treeview. I've had a look a round the internet (and will continue doing so) but can't having found anything relevent.
How can you inject a dependency property containing the HierarchicalDataTemplate into the treeview?
Following should work:
Add PropertyChangedCallback to your HierarchicalDataTemplateProperty.
In the handler, add template to control resources:
private static void OnTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var This = d as MasterTreeUserControl;
var template = e.NewValue as HierarchicalDataTemplate;
if(template != null)
{
This.ItemListView.Resources[new DataTemplateKey(template.DataType)] = template;
}
}
The only issue is that your HierarchicalDataTemplate has to have DataType set to your templated type. You should enforce that in some way (validation etc..)
I have a DataTemplate for templating my ItemsControl's items which are TimeSheet's Details.
I have couple of TextBox representing certain values of my TimeSheet's Details but their IsEnabled property depends on the TimeSheet itself, not the details.
<ItemsControl
ItemsSource="{Binding Path=TimeSheet.TimeSheetDetails}"
ItemTemplate="{StaticResource TimeSheetDetail}"
/>
<DataTemplate x:Key="TimeSheetDetail">
<TextBox
Text="{Binding Houre}"
IsEnabled="Binding ??????">
</DataTemplate>
Since the IsEnabled property cant be found in the TimeSheetDetails but can be found in my ViewModel, i would like to bind directly to my ViewModel's Property but when i try binding, to my ViewModel from my DataTemplate, it only seems to look in my TimeSheetDetail.
How can i access my ViewModel's public property directly?
You can bind to your parent's DataContext:
{Binding DataContext.IsEnabled,
RelativeSource={RelativeSource FindAncestor, AncestorType=ItemsControl}}
Given the following Xaml:
<Window.Resources>
<System:String x:Key="StringValue"></System:String>
</Window.Resources>
<Grid>
<ComboBox Margin="137,101,169,183" ItemsSource="{Binding collection}" SnapsToDevicePixels="True" IsHitTestVisible="true">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Command="{Binding CheckCommand}" IsChecked="{Binding IsChecked}" Content="{Binding Name}"/>
<TextBlock Text="{StaticResource StringValue}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
What I want is for the Textblock Text to be bound to a static resource, that is databound to a value on the ViewModel. The issue is System.String appears to not allow databinding. ANybody know of a way to do this? For context, the TextBlock needs a different itemssource than that of its parent combobox.
Thanks.
String doesnt allow binding because it is not a DependencyObject (and doesnt implement INotifyPropertyChanged)
but why dont you just bind directly to the Value in the ViewModel?
if you cannot bind to a ViewModel (think about RelativeSource with searching Parent type) you can implement a wrapper (which implements INotifyPropertyChanged to get the changes in the object)
Example wrapper class:
public class BindWrapper<T> : INotifyPropertyChanged
{
private T _Content;
public T Content
{
get
{
return _Content;
}
set
{
_Content = value;
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
how to instantiate and bind in XAML:
<Window.Resources>
<local:BindWrapper x:Key="wrapper" x:TypeArguments="System:String">
<local:BindWrapper.Content>
<System:String>huuu</System:String>
</local:BindWrapper.Content>
</local:BindWrapper>
</Window.Resources>
<TextBlock Text="{Binding Source={StaticResource wrapper}, Path=Content}" />
To clarify, A System.String has no dependency properties so you can't bind it anything. I think you need a convertor so your TextBlock can bind to the View Model. What type of ObservableCollection do you have on the View Model?
EDIT If you just want to bind a simple string to the text property this is the wrong answer. If you want to bind to formatted text, read on.
I was having this problem before. I wanted to bind my TextBlock to a string resource in my properties. I ended up subclassing TextBlock to BindableTextBlock and making and a Convertor for string to an Inline list.
Question and Answers here.
It may seem a little involved, there ought to be an easier way. However I've resused the control several times whenever I've needed to bind to some formatted text and it works. Hopefully you can benefit from my work, and perhaps improve.
I'm having an application using MVC. It has a canvas and property grid. When an item is selected in the canvas. The property grid should display its details.
So I made an event listener and when item is selected in the canvas it raises an event to the controller which pass the selected item to the property grid to display the details.
Model :
Item object containing name, description
Controller :
protected Controller(object model, FrameworkElement view)
{
this._model = model;
this._view = view;
}
public virtual void Initialize()
{
View.DataContext = Model;
}
View :
<TextBlock>Status</TextBlock>
<ComboBox ItemsSource="?????"/>
Where view is the property grid and model is the selected item.
The problem is in the property grid there is a dropdown list containing lookup values how can I get the dropdown values given that the datacontext of the property grid has already been set to the selected item which doesn't contain reference to these lookup items.
I know that it's easy to use custom code to do that. But I don't want to violate the MVC aproach.
Bind to a source rather than DataContext, sources are provided by ElementName, RelativeSource & Source, so you can name the View for example and use ElementName to get it as source then the Path could be DataContext.LookupValues or whatever your property in the model (- the DataContext of the View is your model -) is called.
e.g.
<Window ...
Name="Window">
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<ComboBox ItemsSource="{Binding ElementName=Window, Path=DataContext.Occupations}"
SelectedItem="{Binding Occupation}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- ... --->
Edit: Your problem seems to be that you do not pass the information you need, consider a design which still grants you access to more than just the SelectedItem of some list, e.g.
<Window ...
Name="Window">
<ListBox Name="listBox" ItemsSource="{Binding Data}" />
<ContentControl DataContext="{Binding ElementName=listBox, Path=SelectedItem}">
<ComboBox ItemsSource="{Binding ElementName=Window, Path=DataContext.Occupations}"
SelectedItem="{Binding Occupation}" />
</ContentControl>
<!-- ... --->
The DataContext of the ContentControl may be the SelectedItem of the ListBox but the ComboBox inside can still reference the DataContext of the Window which should provide the necessary information.
This is similar to my first example in that the DataContext inside the DataTemplate is always an item of the collection but you can access external DataContexts using sources in your bindings.