I've created a DockingManager from AvalonDock content in my project and my request is quite simple: when I add a document to my LayoutDocumentPaneGroup I want it to be Active, Selected, and not only add at the end of the LayoutDocumentPaneGroup still activate on the first document.
I tried to implement an IsActive property to my documentView class but it doesn't work.
My dockingmanager in xaml file is defined as below:
<dock:DockingManager DataContext="{Binding DockManagerViewModel}" DocumentsSource="{Binding Documents}" AnchorablesSource="{Binding Anchorables}">
<dock:DockingManager.Resources>
<!-- add views for specific ViewModels -->
<DataTemplate DataType="{x:Type vmdock:SampleDockWindowViewModel}">
<uscontrol:SampleDockWindowView />
</DataTemplate>
</dock:DockingManager.Resources>
<dock:DockingManager.LayoutItemContainerStyle>
<Style TargetType="{x:Type dockctrl:LayoutItem}">
<Setter Property="Title" Value="{Binding Model.Title}" />
<Setter Property="CloseCommand" Value="{Binding Model.CloseCommand}" />
<Setter Property="CanClose" Value="{Binding Model.CanClose}" />
</Style>
</dock:DockingManager.LayoutItemContainerStyle>
<dock:LayoutRoot>
<dock:LayoutPanel Orientation="Vertical">
<dock:LayoutDocumentPaneGroup>
<dock:LayoutDocumentPane />
</dock:LayoutDocumentPaneGroup>
<dock:LayoutAnchorablePaneGroup>
<dock:LayoutAnchorablePane />
</dock:LayoutAnchorablePaneGroup>
</dock:LayoutPanel>
</dock:LayoutRoot>
</dock:DockingManager>
My documentView is defined with the class as below:
public abstract class DockWindowViewModel : BaseViewModel
{
#region Properties
#region CloseCommand
private ICommand _CloseCommand;
public ICommand CloseCommand
{
get
{
if (_CloseCommand == null)
_CloseCommand = new RelayCommand(call => Close());
return _CloseCommand;
}
}
#endregion
#region IsClosed
private bool _IsClosed;
public bool IsClosed
{
get { return _IsClosed; }
set
{
if (_IsClosed != value)
{
_IsClosed = value;
OnPropertyChanged(nameof(IsClosed));
}
}
}
#endregion
#region CanClose
private bool _CanClose;
public bool CanClose
{
get { return _CanClose; }
set
{
if (_CanClose != value)
{
_CanClose = value;
OnPropertyChanged(nameof(CanClose));
}
}
}
#endregion
#region Title
private string _Title;
public string Title
{
get { return _Title; }
set
{
if (_Title != value)
{
_Title = value;
OnPropertyChanged(nameof(Title));
}
}
}
#endregion
#endregion
public DockWindowViewModel()
{
CanClose = true;
IsClosed = false;
}
public void Close()
{
IsClosed = true;
}
Finally found ! I post the result because I think I'll not be alone...
First, I add a new property to my document view definition:
#region IsSelected
private bool _isSelected = false;
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged(nameof(IsSelected));
}
}
}
#endregion
But I had also to implement it as a property in my XAML code, as below:
<dock:DockingManager.LayoutItemContainerStyle>
<Style TargetType="{x:Type dockctrl:LayoutItem}">
<Setter Property="Title" Value="{Binding Model.Title}" />
<Setter Property="CloseCommand" Value="{Binding Model.CloseCommand}" />
<Setter Property="CanClose" Value="{Binding Model.CanClose}" />
**<Setter Property="IsSelected" Value="{Binding Model.IsSelected}" />**
</Style>
</dock:DockingManager.LayoutItemContainerStyle>
Supposed it works the same for all properties of LayoutContent defined there: LayoutDocument defined by AvalonDock
EDIT :
Need to add also "Mode=TwoWay" to programmatically update when the selected content changed, like this:
<Setter Property="IsSelected" Value="{Binding Model.IsSelected, Mode=TwoWay}" />
As I already mentioned in my comment on the accepted answer, this didn't work for me, so I moved on trying. My case may differ, though. I'm using Stylet and my DockManager.DocumentsSource is bound to the Items property of a Conductor<MyViewModelBase>.Collection.OneActive Documents in the ViewModel. In this case I needed to do two things to activate the added document:
Bind DockingManager.ActiveContent to Documents.ActiveItem
After Documents.Items.Add(an_instance_of_my_viewmodel); activate the new ViewModel with Documents.ActivateItem(vm);
Related
I'm having problems to set the ColumnWidth of a DataGrid inside a trigger in his style.
I have this:
<DataGrid ItemsSource="{Binding Data}">
<DataGrid.Style>
<Style TargetType="DataGrid">
<Style.Triggers>
<DataTrigger Binding="{Binding Data.Count}" Value="2">
<Setter Property="Background" Value="LightGreen" />
<Setter Property="ColumnWidth" Value="400" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
</DataGrid>
In case of 2 rows I want to fill the background with green and to make wider columns, but I can't only achieve the green background.. Why the ColumnWidth setting is not working?
[![enter image description here][1]][1]
It works if I put the ColumnWidth setting outside the Trigger.. but I don't want this..
<DataGrid ItemsSource="{Binding Data}">
<DataGrid.Style>
<Style TargetType="DataGrid">
<Setter Property="ColumnWidth" Value="400" />
<Style.Triggers>
<DataTrigger Binding="{Binding Data.Count}" Value="2">
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
</DataGrid>
Thanks!
Solved:
Finally I have established ColumnWidt with a Binding to my data and a converter:
<DataGrid ColumnWidth="{Binding Data, Converter={StaticResource DataToColumnWidthConverter}}" ItemsSource="{Binding Data}" IsReadOnly="True" MaxHeight="300" >
Converter:
[ValueConversion(typeof(DataTable), typeof(DataGridLength))]
public class DataToColumnWidthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
DataTable dt = value as DataTable;
if (dt != null && dt.Rows.Count == 2)
{
return new DataGridLength(400);
}
return new DataGridLength();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
This is OK for me because my data won't change number of rows in execution time, so ColumnWidth only needs to be calculated one time at beginning.
Thanks all
Remove ItemsSource from your DataGrid, and move it into DataTrigger.
To set ColumnWidth after initial binding, re-binding is needed. You cannot set ColumnWidth from code, it won't have any effect. For ColumnWidth to have any effect, you need to first remove DataGrid's DataContext/ItemsSource (setting to null) and then re-assign it.
So, if you change your collection somewhere, you have to first set the DataContext of DataGrid to null, and then re-assign it. See what I have done in Button click below.
Code below is self explanatory. I have written a converter and MarkupExtension for cases when Data.Count will not be 4.
<DataGrid x:Name="Dgrid" Margin="0,58,0,0">
<DataGrid.Style>
<Style TargetType="DataGrid">
<Style.Triggers>
<DataTrigger Binding="{Binding Data.Count}" Value="4">
<Setter Property="ColumnWidth" Value="200" />
<Setter Property="ItemsSource" Value="{Binding Data}" />
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
<DataTrigger Binding="{Binding Data.Count, Converter={local:CountToBool}}" Value="true">
<Setter Property="ColumnWidth" Value="100" />
<Setter Property="ItemsSource" Value="{Binding Data}" />
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
</DataGrid>
Converter :
public class CountToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((int)value != 4)
return true;
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class CountToBoolExtension : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return new CountToBoolConverter();
}
}
CodeBehind :
ViewModel vm = new ViewModel();
// removing 2 items and reassigning DataContext to viewmodel.
private void Button_Click(object sender, RoutedEventArgs e)
{
Dgrid.DataContext = null;
vm.Students.RemoveAt(1);
vm.Students.RemoveAt(2);
Dgrid.DataContext = vm;
}
Above code will change ColumnWidth depending upon the Data.Count value and works correctly if we change number of records in Collection at runtime.
please try the next solution. As you can see I've used a proxy object to pass a main data context to each data grid cell. In addition there is a DataTrigger which works when a Visibility of a hidden column is changed and there is an attached property that helps to control the actual column width.
Here is the code:
Xaml Code
<Window x:Class="DataGridSoHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dataGridSoHelpAttempt="clr-namespace:DataGridSoHelpAttempt"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Title="MainWindow" Height="350" Width="525" x:Name="This">
<Window.DataContext>
<dataGridSoHelpAttempt:MainViewModel/>
</Window.DataContext>
<Grid x:Name="MyGrid">
<Grid.Resources>
<dataGridSoHelpAttempt:FreezableProxyClass x:Key="ProxyElement" ProxiedDataContext="{Binding Source={x:Reference This}, Path=DataContext}"/>
</Grid.Resources>
<DataGrid x:Name="MyDataGrid" ItemsSource="{Binding DataSource}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Description" Binding="{Binding Description}" Visibility="{Binding Source={StaticResource ProxyElement},
Path=ProxiedDataContext.Visibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<DataGridTextColumn Header="Comments" Binding="{Binding Comments}"/>
<DataGridTextColumn Header="Price (click to see total)" Binding="{Binding Price, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataGrid.Columns>
<DataGrid.Resources>
<Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource ProxyElement},
Path=ProxiedDataContext.Visibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="Visible">
<Setter Property="Width" Value="200"></Setter>
<Setter Property="dataGridSoHelpAttempt:DataGridAttached.ColumnActualWidth" Value="200"/>
</DataTrigger>
<DataTrigger Binding="{Binding Source={StaticResource ProxyElement},
Path=ProxiedDataContext.Visibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="Collapsed">
<Setter Property="Width" Value="400"></Setter>
<Setter Property="dataGridSoHelpAttempt:DataGridAttached.ColumnActualWidth" Value="400"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
</DataGrid>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Bottom">
<Button Content="Show Description" Command="{Binding Command}"></Button>
</StackPanel>
</Grid></Window>
Attached Property Code
public class DataGridAttached
{
public static readonly DependencyProperty ColumnActualWidthProperty = DependencyProperty.RegisterAttached(
"ColumnActualWidth", typeof (double), typeof (DataGridAttached), new PropertyMetadata(default(double), ColumnActualWidthPropertyChanged));
private static void ColumnActualWidthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var data = d.FindParent<DataGrid>();
var control = (d as Control);
if(data == null || control == null) return;
data.Columns.ToList().ForEach(column =>
{
var cellWidth = control.Width;
if(double.IsNaN(cellWidth) || double.IsInfinity(cellWidth)) return;
column.Width = cellWidth;
});
}
public static void SetColumnActualWidth(DependencyObject element, double value)
{
element.SetValue(ColumnActualWidthProperty, value);
}
public static double GetColumnActualWidth(DependencyObject element)
{
return (double) element.GetValue(ColumnActualWidthProperty);
}
}
View Model and Model
public class MainViewModel:BaseObservableObject
{
private Visibility _visibility;
private ICommand _command;
private Visibility _totalsVisibility;
private double _totalValue;
private double _columnWidth;
public MainViewModel()
{
Visibility = Visibility.Collapsed;
TotalsVisibility = Visibility.Collapsed;
DataSource = new ObservableCollection<BaseData>(new List<BaseData>
{
new BaseData {Name = "Uncle Vania", Description = "A.Chekhov, play", Comments = "worth reading", Price = 25},
new BaseData {Name = "Anna Karenine", Description = "L.Tolstoy, roman", Comments = "worth reading", Price = 35},
new BaseData {Name = "The Master and Margarita", Description = "M.Bulgakov, novel", Comments = "worth reading", Price = 56},
});
}
public ICommand Command
{
get
{
return _command ?? (_command = new RelayCommand(VisibilityChangingCommand));
}
}
private void VisibilityChangingCommand()
{
Visibility = Visibility == Visibility.Collapsed ? Visibility.Visible : Visibility.Collapsed;
ColumnWidth = Visibility == Visibility.Visible ? 200d : 400d;
}
public ObservableCollection<BaseData> DataSource { get; set; }
public Visibility Visibility
{
get { return _visibility; }
set
{
_visibility = value;
OnPropertyChanged();
}
}
public ObservableCollection<BaseData> ColumnCollection
{
get { return DataSource; }
}
public Visibility TotalsVisibility
{
get { return _totalsVisibility; }
set
{
_totalsVisibility = value;
OnPropertyChanged();
}
}
public double TotalValue
{
get { return ColumnCollection.Sum(x => x.Price); }
}
public double ColumnWidth
{
get { return _columnWidth; }
set
{
_columnWidth = value;
OnPropertyChanged();
}
}
}
public class BaseData:BaseObservableObject
{
private string _name;
private string _description;
private string _comments;
private int _price;
public virtual string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
public virtual object Description
{
get { return _description; }
set
{
_description = (string) value;
OnPropertyChanged();
}
}
public string Comments
{
get { return _comments; }
set
{
_comments = value;
OnPropertyChanged();
}
}
public int Price
{
get { return _price; }
set
{
_price = value;
OnPropertyChanged();
}
}
}
Freezable Helper
public class FreezableProxyClass : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new FreezableProxyClass();
}
public static readonly DependencyProperty ProxiedDataContextProperty = DependencyProperty.Register(
"ProxiedDataContext", typeof (object), typeof (FreezableProxyClass), new PropertyMetadata(default(object)));
public object ProxiedDataContext
{
get { return (object) GetValue(ProxiedDataContextProperty); }
set { SetValue(ProxiedDataContextProperty, value); }
}
}
Helpers
public static class VisualTreeHelperExtensions
{
public static T FindParent<T>(this DependencyObject child) where T : DependencyObject
{
while (true)
{
//get parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
return parent;
child = parentObject;
}
}
}
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
public class RelayCommand : ICommand
{
private readonly Func<bool> _canExecute;
private readonly Action _execute;
public RelayCommand(Action execute)
: this(() => true, execute)
{
}
public RelayCommand(Func<bool> canExecute, Action execute)
{
_canExecute = canExecute;
_execute = execute;
}
public bool CanExecute(object parameter = null)
{
return _canExecute();
}
public void Execute(object parameter = null)
{
_execute();
}
public event EventHandler CanExecuteChanged;
}
public class RelayCommand<T> : ICommand
where T:class
{
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
public RelayCommand(Action<T> execute):this(obj => true, execute)
{
}
public RelayCommand(Predicate<T> canExecute, Action<T> execute)
{
_canExecute = canExecute;
_execute = execute;
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter as T);
}
public void Execute(object parameter)
{
_execute(parameter as T);
}
public event EventHandler CanExecuteChanged;
}
It is complete test solution, you should take just the idea of how this is working. I'll be glad to help if you will have problems with the code.
Regards.
I'm having a TextBox, if the TextBox has the Text.Length >0 then I have to change the HasChar property True otherwise False. Here I can't able to Bind the Property in the Setter.
The XAML Source Code:
<TextBox Text="WPF">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Value="0"
Binding="{Binding Text.Length, RelativeSource={RelativeSource Self}}">
<Setter Property="{Binding HasChar}" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
The View Model C# Source Code :
private bool _hasChar= true;
public bool HasChar
{
get { return _hasChar; }
set
{
_hasChar= value;
OnPropertyChanged();
}
}
You're misusing triggers.
The right way to go:
1) XAML:
<TextBox Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"/>
2) view model. You don't need to add setter to HasChar. If this property is bound to something in view, just raise appropriate PropertyChanged:
public class ViewModel : INotifyPropertyChanged
{
// INPC implementation is omitted
public string Text
{
get { return text; }
set
{
if (text != value)
{
text = value;
OnPropertyChanged();
OnPropertyChanged("HasChar");
}
}
}
private string text;
public bool HasChar
{
get { return !string.IsNullOrEmpty(Text); }
}
}
You can not bind a property in the setter. Style is used to set the UI element properties like Text,Visibility,Foreground etc.
I constructed a treeView WPF MVVM with the help of this very good article
Then I created a contextMenu for some node that allowed me to add children from selected parent.
The problem is if I click on "Add" without expanding manually the selected node(parent), a strange child is created automatically in addition to the node expected to be generated when clicking on "Add".
I tried to detect the problem so I change the code below from:
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
to:
<Setter Property="IsExpanded" Value="True" />
Image 1 below shows the result of this test or Image 2 shows what my treeView must show.
image1
image2
Rq: I used image from the article that I talked about it. Also, I used the same approach described in the article (including the class TreeViewItemViewModel.cs )
Base class for all ViewModel
public class TreeViewItemViewModel : INotifyPropertyChanged
{
#region Data
static readonly TreeViewItemViewModel DummyChild = new TreeViewItemViewModel();
readonly ObservableCollection<TreeViewItemViewModel> _children;
readonly TreeViewItemViewModel _parent;
bool _isExpanded;
bool _isSelected;
#endregion // Data
#region Constructors
protected TreeViewItemViewModel(TreeViewItemViewModel parent, bool lazyLoadChildren)
{
_parent = parent;
_children = new ObservableCollection<TreeViewItemViewModel>();
if (lazyLoadChildren)
_children.Add(DummyChild);
}
// This is used to create the DummyChild instance.
private TreeViewItemViewModel()
{
}
#endregion // Constructors
#region Presentation Members
#region Children
/// <summary>
/// Returns the logical child items of this object.
/// </summary>
public ObservableCollection<TreeViewItemViewModel> Children
{
get { return _children; }
}
#endregion // Children
#region HasLoadedChildren
/// <summary>
/// Returns true if this object's Children have not yet been populated.
/// </summary>
public bool HasDummyChild
{
get { return this.Children.Count == 1 && this.Children[0] == DummyChild; }
}
#endregion // HasLoadedChildren
#region IsExpanded
/// <summary>
/// Gets/sets whether the TreeViewItem
/// associated with this object is expanded.
/// </summary>
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (value != _isExpanded)
{
_isExpanded = value;
this.OnPropertyChanged("IsExpanded");
}
// Expand all the way up to the root.
if (_isExpanded && _parent != null)
_parent.IsExpanded = true;
// Lazy load the child items, if necessary.
if (this.HasDummyChild)
{
this.Children.Remove(DummyChild);
this.LoadChildren();
}
}
}
#endregion // IsExpanded
#region IsSelected
/// <summary>
/// Gets/sets whether the TreeViewItem
/// associated with this object is selected.
/// </summary>
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
this.OnPropertyChanged("IsSelected");
}
}
}
#endregion // IsSelected
#region LoadChildren
/// <summary>
/// Invoked when the child items need to be loaded on demand.
/// Subclasses can override this to populate the Children collection.
/// </summary>
protected virtual void LoadChildren()
{
}
#endregion // LoadChildren
#region Parent
public TreeViewItemViewModel Parent
{
get { return _parent; }
}
#endregion // Parent
#endregion // Presentation Members
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion // INotifyPropertyChanged Members
}
Myxml:
<TreeView ItemsSource="{Binding Regions}" IsEnabled="{Binding EnableTree}" >
<TreeView.ItemContainerStyle>
<!--
This Style binds a TreeViewItem to a TreeViewItemViewModel.
-->
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<ContextMenu x:Key="AddCity" ItemsSource="{Binding AddCityItems}"/>
<HierarchicalDataTemplate
DataType="{x:Type local:StateViewModel}"
ItemsSource="{Binding Children}"
>
<StackPanel Orientation="Horizontal" ContextMenu="{StaticResource AddCity}">
<Image Width="16" Height="16" Margin="3,0" Source="Images\Region.png" />
<TextBlock Text="{Binding RegionName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
RegionViewModel:
`public class StateViewModel : TreeViewItemViewModel
{
readonly State _state;
public ICommand AddCityCommand { get; private set; }
public List<MenuItem> AddCityItems { get; set; }
public StateViewModel(State state, RegionViewModel parentRegion)
: base(parentRegion, true)
{
_state = state;
AddCityItems = new List<MenuItem>();
AddCityCommand = new DelegateCommand<CancelEventArgs>(OnAddCityCommandExecute, OnAddCityCommandCanExecute);
AddCityItems.Add(new MenuItem() { Header = "Add City", Command = AddCityCommand });
}
public string StateName
{
get { return _state.StateName; }
}
protected override void LoadChildren()
{
foreach (City city in Database.GetCities(_state))
base.Children.Add(new CityViewModel(city, this));
}
bool OnAddCityCommandCanExecute(CancelEventArgs parameter)
{
return true;
}
public void OnAddCityCommandExecute(CancelEventArgs parameter)
{
var myNewCity = new city();
Children.Add(new CityViewModel(myNewCity, this));
}
}`
BTW, if I expand my parent node then I click into add City, I have the result as expected but if I don't expand parent node and I click on contextMenu I have another child created in addition to the child I want to create
EDIT
I add the statemnt below to my add() method and I don't have any problem now:
public void OnAddCityCommandExecute(CancelEventArgs parameter)
{
var myNewCity = new city();
Children.Add(new CityViewModel(myNewCity, this));
//the modif
this.Children.Remove(DummyChild);
}
I can see the bug in your code.
Here's the steps to reproduce:
At state node (never expand it first)
Without Expanding the Child upfront, Your StateViewModel's Children contain a DummyChild.
Added 1 new City into the list which cause the HasDummyChild won't work as the count is now 2 in Children's list
Then when you try to expand the node to check the result. Your treelist will have the DummyChild which is a base class that screwed up everything
So, basically that's why "Expand" first is the key of your problem as at that time HasDummyChild still working as it compares .Count == 1. The tree won't remove the DummyChild out from your Children list if you add an extra child to the list that makes .Count == 2.
ADDITIONAL INFO as requested
Just change the HasDummyChild as the following
public bool HasDummyChild
{
//get { return this.Children.Count == 1 && this.Children[0] == DummyChild; }
get { return Children.Any() && Children.Contains(DummyChild); }
}
In my View I have a Menu Control which is binded to my ViewModel's Property becauase I want to populate it dynamically.I created a seperated class for my Menu .
Here is my My menu class:
public class MenuItemViewModel : ViewModelBase
{
internal MenuItemViewModel()
{
}
private string _menuText;
public string MenuText
{
get { return _menuText; }
set
{
if (_menuText == value)
return;
_menuText = value;
RaisePropertyChanged("MenuText");
}
}
private ObservableCollection<MenuItemViewModel> _children;
public ObservableCollection<MenuItemViewModel> Children
{
get { return _children; }
set
{
_children = value;
RaisePropertyChanged("Children");
}
}
}
and In my MainViewModel I created an Collection property of my MenuItemViewModel
here is my MainViewModel:
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
LoadMainMenu();
}
#region Menu
private ObservableCollection<MenuItemViewModel> _topMenuItems;
public ObservableCollection<MenuItemViewModel> TopMenuItems
{
get { return _topMenuItems; }
set
{
if (_topMenuItems == value)
return;
_topMenuItems = value;
RaisePropertyChanged("TopMenuItems");
}
}
public void LoadMainMenu()
{
IList<MenuItemViewModel> fileMenuItems = PopulateFileMenuEntries();
_topMenuItems.Add(new MenuItemViewModel() { MenuText = "_File", Children = new ObservableCollection<MenuItemViewModel>(fileMenuItems) });
}
private IList<MenuItemViewModel> PopulateFileMenuEntries()
{
List<MenuItemViewModel> fileMenuItems = new List<MenuItemViewModel>();
fileMenuItems.Add(new MenuItemViewModel() { MenuText = "Open _Recent" });
return fileMenuItems;
}
}
here is my XAML:
<Window.Resources>
<WpfApplication3_ViewModel:MainViewModel x:Key="MainViewModelDataSource"
d:IsDataSource="True" />
</Window.Resources>
<Grid DataContext="{StaticResource MainViewModelDataSource}">
<Menu
ItemsSource="{Binding TopMenuItems}"
Margin="12,0,50,237">
<Menu.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header"
Value="{Binding MenuText}" />
<Setter Property="ItemsSource"
Value="{Binding Children}" />
<Style.Triggers>
<DataTrigger Binding="{Binding }"
Value="{x:Null}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Separator Style="{StaticResource {x:Static MenuItem.SeparatorStyleKey}}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Menu.Resources>
</Menu>
</Grid>
When the application run it throw an exception "Exception has been thrown by the target of an invocation."
What is wrong with my code
I have had similar problem and I tracked it down to an uninitialised variable. Your _topMenuItems in your Constructor should be
new ObservableCollection<MenuItemViewModel>()
or
ObservableCollection<MenuItemViewModel> _topMenuItems = new ObservableCollection<MenuItemViewModel>();
I have an application where a number of custom buttons are dynamically generated within a WrapPanel. All works fine and I am able to assign border thickness, ImageSource, Content etc. as I generate the buttons in code. The customer now has a requirement to allow them to choose border colours for individual buttons and try as I might I cannot figure out the correct binding scenario. I'm on a steep WPF learning curve here so it may be that my initial design is somewhat off kilter.
In my Generic.XAML I have the button specified thus:
<Style TargetType="{x:Type local:LauncherButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:LauncherButton}">
<Border Name="LauncherButtonBorder" BorderThickness="{TemplateBinding BThickness}"
CornerRadius="10" Background="White" >
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="BorderBrush" Value="SteelBlue" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="PaleGoldenrod" />
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
<DockPanel LastChildFill="True" Background="White" Margin="3">
<TextBlock Text="{TemplateBinding Content}" HorizontalAlignment="Center"
Foreground="{DynamicResource TaskButtonTextBrush}" FontWeight="Bold"
Margin="5,0,0,0" VerticalAlignment="Center" FontSize="10"
Background="Transparent" DockPanel.Dock="Bottom" TextWrapping="Wrap" />
<Image Source="{TemplateBinding ImageSource}" Stretch="Uniform" />
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I want to dynamically change in c# the border colours that are currently set to static SteelBlue and PaleGoldenrod.
The button class is defined thus:
public class LauncherButton : ButtonBase
{
static LauncherButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(LauncherButton), new FrameworkPropertyMetadata(typeof(LauncherButton)));
}
public ImageSource ImageSource
{
get { return (ImageSource)GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
public Thickness BThickness
{
get { return (Thickness) GetValue(BThicknessProperty); }
set { SetValue(BThicknessProperty,value);}
}
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(LauncherButton), new UIPropertyMetadata(null));
public static readonly DependencyProperty BThicknessProperty =
DependencyProperty.Register("BThickness", typeof(Thickness), typeof(LauncherButton), new UIPropertyMetadata(null));
}
and I'm binding some of the properties to an instance of the following class:
public class CustomButton:INotifyPropertyChanged
{
private string _type;
private string _buttonId;
private string _name;
private string _image;
private string _link;
private string _parent;
private List<CustomButton> _children;
private bool _isExpanded;
private bool _isSelected;
public string ButtonId
{
get { return _buttonId; }
set
{
if (value == _buttonId) return;
_buttonId = value;
OnPropertyChanged("ButtonId");
}
}
public string Type
{
get { return _type; }
set
{
if (value == _type) return;
_type = value;
OnPropertyChanged("Type");
}
}
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged("Name");
}
}
public string Image
{
get { return _image; }
set
{
if (value == _image) return;
_image = value;
OnPropertyChanged("Image");
}
}
public string Link
{
get { return _link; }
set
{
if (value == _link) return;
_link = value;
OnPropertyChanged("Link");
}
}
public string Parent
{
get { return _parent; }
set
{
if (value == _parent) return;
_parent = value;
OnPropertyChanged("Parent");
}
}
public List<CustomButton> Children
{
get { return _children; }
set
{
if (Equals(value, _children)) return;
_children = value;
OnPropertyChanged("Children");
}
}
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (value.Equals(_isExpanded)) return;
_isExpanded = value;
OnPropertyChanged("IsExpanded");
}
}
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value.Equals(_isSelected)) return;
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Are you trying to make the two brushes used for Border.BorderBrush dynamic?
If so you can address it in a few ways.
Add two dependency properties to LauncherButton for say NormalBorderBrush and MouseOverBorderBrush and then set it as you wish when you use the Button. Now to get the Border to use this, within it's Style where you set SteelBlue or PaleGoldenRod, apply a RelativeSource FindAncestor binding with AncestorType as local:LauncherButton and point it to the corresponding Brush(NormalBorderBrush or MouseOverBorderBrush)
Example:
public class LauncherButton : ButtonBase {
...
public static readonly DependencyProperty NormalBorderBrushProperty =
DependencyProperty.Register("NormalBorderBrush", typeof(Brush), typeof(LauncherButton),
new UIPropertyMetadata(Brushes.Blue));
public static readonly DependencyProperty MouseOverBorderBrushProperty =
DependencyProperty.Register("MouseOverBorderBrush", typeof(Brush), typeof(LauncherButton),
new UIPropertyMetadata(Brushes.Red));
public Brush NormalBorderBrush
{
get { return (Brush)GetValue(NormalBorderBrushProperty); }
set { SetValue(NormalBorderBrushProperty, value); }
}
public Brush MouseOverBorderBrush
{
get { return (Brush)GetValue(MouseOverBorderBrushProperty); }
set { SetValue(MouseOverBorderBrushProperty, value); }
}
}
in xaml:
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="BorderBrush"
Value="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type local:LauncherButton}},
Path=NormalBorderBrush}" />
<Style.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter Property="BorderBrush"
Value="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type local:LauncherButton}},
Path=MouseOverBorderBrush}" />
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
and usage:
<local:LauncherButton BThickness="5"
Content="Hellooooo"
MouseOverBorderBrush="Green"
NormalBorderBrush="Aqua" />
Sample Download - This does not contain the converter for Brush usage, that should be easy enough to implement.
OR You could have two brushes defined as dynamic resources and override their color's from your code when you need to.
OR You can use the Button's BorderBrush property which it already has and apply this to the Border with a TemplateBinding BorderBrush. Now this would mean you need to switch the BorderBrush accordingly when your IsMouseOver state change occurs.
OR you could even go to extents of retrieving the button's Style and getting a reference to the Border element by finding it via it's Name and then tweaking it at run-time.
Personally I'd opt for option 1. Finally use a converter or likewise in the Binding to make it MVVM friendly.