I've got a content control and its content template shall change triggered by an data trigger, but the trigger is not firing. What am I doing wrong? The content controls' and Data templates' datacontext is "MainViewModel". Here is the code in question:
Content controls' resources:
<DataTemplate x:Key="TopologyConfigurationInputTemplate">
<ContentControl>
<ContentControl.Resources>
<conv:ObjectToStringConverter x:Key="ObjectToStringConverter"/>
</ContentControl.Resources>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource CraneTemplate123}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=TopologyConfigViewModel.TemplateSelection}"
Value="{x:Static vm:TopologyConfigViewModel+TemplateSelectionEnum.CRANE}">
<Setter Property="ContentTemplate" Value="{StaticResource CraneTemplate123}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=TopologyConfigViewModel.TemplateSelection}"
Value="{x:Static vm:TopologyConfigViewModel+TemplateSelectionEnum.EQUIPMENT}">
<Setter Property="ContentTemplate" Value="{StaticResource EquipmentTemplate123}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=TopologyConfigViewModel.TemplateSelection}"
Value="{x:Static vm:TopologyConfigViewModel+TemplateSelectionEnum.TERMINAL}">
<Setter Property="ContentTemplate" Value="{StaticResource TerminalTemplate123}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Content Control:
<ContentControl ContentTemplate="{StaticResource TopologyConfigurationInputTemplate}" MinHeight="250"/>
This is my "MainViewModel":
public class MainViewModel : BaseViewModel
{
private TopologyViewModel _topologyViewModel;
private TopologyConfigViewModel _topologyConfigViewModel;
protected Dictionary<string, List<string>> _validationErrors;
public MainViewModel()
{
_topologyViewModel = new TopologyViewModel(GetTopology());
_topologyConfigViewModel = new TopologyConfigViewModel(_topologyViewModel);
_validationErrors = new Dictionary<string, List<string>>();
}
public TopologyViewModel TopologyViewModel
{
get { return _topologyViewModel; }
set
{
if (_topologyViewModel != value)
{
_topologyViewModel = value;
NotifyPropertyChanged("TopologyViewModel");
}
}
}
public TopologyConfigViewModel TopologyConfigViewModel
{
get { return _topologyConfigViewModel; }
set
{
if (_topologyConfigViewModel != value)
{
_topologyConfigViewModel = value;
NotifyPropertyChanged("TopologyConfigViewModel");
}
}
}
}
This is the nested ViewModel "TopologyConfigViewModel", where the property for the data trigger is implemented:
public class TopologyConfigViewModel : BaseViewModel
{
public enum TemplateSelectionEnum { CRANE, EQUIPMENT, TERMINAL }
private TemplateSelectionEnum _templateSelection;
private TopologyViewModel _topologyViewModel;
protected Dictionary<string, List<string>> _validationErrors;
private ICommand _craneConfigSelectCmd;
private ICommand _equipmentConfigCmd;
private ICommand _terminalConfigCmd;
public TopologyConfigViewModel(TopologyViewModel pTopologyViewModel)
{
_topologyViewModel = pTopologyViewModel;
_validationErrors = new Dictionary<string, List<string>>();
_craneConfigSelectCmd = new DelegateCommand(param => ChangeConfigTemplate(param));
_equipmentConfigCmd = new DelegateCommand(param => ChangeConfigTemplate(param));
_terminalConfigCmd = new DelegateCommand(param => ChangeConfigTemplate(param));
}
public TemplateSelectionEnum TemplateSelection
{
get { return _templateSelection; }
set
{
if (_templateSelection != value)
{
_templateSelection = value;
NotifyPropertyChanged("TemplateSelection");
}
}
}
public ICommand CraneConfigSelectCmd
{
get { return _craneConfigSelectCmd; }
}
public ICommand EquipmentConfigSelectCmd
{
get { return _equipmentConfigCmd; }
}
public ICommand TerminalConfigSelectCmd
{
get { return _terminalConfigCmd; }
}
public void ChangeConfigTemplate(object param)
{
try
{
if (param == null)
return;
if (((string)param) == "Crane")
TemplateSelection = TemplateSelectionEnum.CRANE;
else if (((string)param) == "Equipment")
TemplateSelection = TemplateSelectionEnum.EQUIPMENT;
else
TemplateSelection = TemplateSelectionEnum.TERMINAL;
//CraneConfigSelected = true;
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
}
Following is working correctly: Commands to change enums value, Property changed notifications
Thanks in advance!
It should work if you bind the Content property of the ContentControl to the view model:
<ContentControl ContentTemplate="{StaticResource TopologyConfigurationInputTemplate}" Content="{Binding}" MinHeight="250"/>
Related
i'm implementet a dynamic menu service in my WPF Application. Every Command can or should have different CommandParameters.
The Problem:
With my solution to set the CommandParamter binding in xaml the CanExecute property from the command doesent update anymore.
What i have so far
I'm using mvvm-light and fody-propertychanged.
Here the menu class:
public class MyMenu : INotifyPropertyChanged
{
private ObservableCollection<MyMenu> myChildren;
public MyMenu()
{
myChildren = new ObservableCollection<MyMenu>();
}
public string Header { get; set; }
public string Image { get; set; }
public string CommandName { get; set; } //used to set the CommandParameter binding
public ICommand Command { get; set; }
public ObservableCollection<MyMenu> Children {
get
{
return myChildren;
}
private set
{
myChildren = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
This class is used by the MenuService:
public sealed class MenuService : INotifyPropertyChanged
{
private static readonly Lazy<MenuService> lazy = new Lazy<MenuService>(() => new MenuService());
public static MenuService Instance { get { return lazy.Value; } }
private ObservableCollection<MyMenu> myMainMenu;
public event PropertyChangedEventHandler PropertyChanged;
private MenuService()
{
myMainMenu = new ObservableCollection<MyMenu>();
}
public ObservableCollection<MyMenu> MainMenu
{
get
{
return myMainMenu;
}
private set
{
myMainMenu = value;
}
}
}
In the constructor of the viewmodel i get the instance of the MenuService and add some items:
private void AddMenuItems()
{
MyMenu OpenUserLoginMenuItem = new MyMenu
{
Header = "_Login",
Image = "./Icons/IconLogin.png",
Command = OpenSelectTestprocedureWindowCommand,
CommandName = "OpenUserLoginDialogCommand"
};
MyMenu OpenSelectTestprocedureMenuItem = new MyMenu
{
Header = "_Select Testprocedure",
Image = "./Icons/IconSelectTestprocedure.png",
Command = OpenSelectTestprocedureWindowCommand,
CommandName = "OpenSelectTestprocedureWindowCommand"
};
MainMenu.Add(OpenUserLoginMenuItem);
MainMenu.Add(OpenSelectTestprocedureMenuItem);
}
Then i have a bindable property in the viewmodel:
public ObservableCollection<MyMenu> MainMenu
{
get
{
return myMenuService.MainMenu;
}
}
Here the command implementation as RelayCommand:
//in the constructor
OpenSelectTestprocedureWindowCommand = new RelayCommand<ShowTestschrittViewParameter>(OpenSelectTestablaufWindow, CanOpenSelectTestablaufWindow);
OpenUserLoginDialogCommand = new RelayCommand<Type>(OpenUserLoginDialog);
private void OpenUserLoginDialog(Type aWindowType)
{
myNavigationService.ShowWindowModal(aWindowType);
}
private bool CanOpenSelectTestablaufWindow(ShowTestschrittViewParameter showTestschrittViewParameter)
{
if (myDataService.CurrentTestProcedure != null)
{
if (myDataService.CurrentTestProcedure.TestProcedureState == Logic.Model.GlobalTypes.TestProcedureState.Running) return false;
}
return new ViewModelLocator().UserLoginDialogViewModel.User.NameIsValid;
}
private void OpenSelectTestablaufWindow(ShowTestschrittViewParameter showTestschrittViewParameter)
{
myNavigationService.ShowTestschrittView(showTestschrittViewParameter);
}
Then in the MainView i have the following xaml:
<Menu Grid.Row="2" ItemsSource="{Binding MainMenu}" Name="DynamicMenu">
<!--<Menu.ItemTemplate>
<DataTemplate DataType="{x:Type luih:MyMenu}">
<StackPanel>
<Label Content="{Binding Header}"/>
<Image Source="{Binding Image}"/>
</StackPanel>
</DataTemplate>
</Menu.ItemTemplate>-->
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="ItemsSource" Value="{Binding Children}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding CommandName}" Value="OpenUserLoginDialogCommand">
<Setter Property="CommandParameter" Value="{x:Type local:UserLoginDialog}"/>
</DataTrigger>
<DataTrigger Binding="{Binding CommandName}" Value="OpenSelectTestprocedureWindowCommand">
<Setter Property="CommandParameter" Value="{x:Type local:UserLoginDialog}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Menu.ItemContainerStyle>
</Menu>
Attention. The CommandParameter binding type in the xaml is currently not correct. This is another problem, i will solve by my self. But for testing purposes it should work. It will give me an exception because of wrong type.
But when i do the CommandParameter binding in the Style.Trigger with the DataTrigger, the CanExecute property doesent update anymore at runtime. When i'm comment this section out, everything works fine. But then i have no CommandParameters.
Any help and suggestions are welcome.
i'm found the problem.
RelayCommand from mvvm light evaluetes the type of the parameter for the CanExecute function. This must be the correct declared type or null.
So for testing purposes i had to set the binding like so:
<Style TargetType="{x:Type MenuItem}">
<Setter Property="CommandParameter" Value="{x:Null}"/>
</Style>
I have the following model:
public class Tag : ObservableObject
{
private int _id = -1;
public int Id
{
get { return _id; }
set
{
if (value != _id)
{
_id = value;
OnPropertyChanged("Id");
}
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
private string _freeText;
public string FreeText
{
get { return _freeText; }
set
{
if (value != _freeText)
{
_freeText = value;
OnPropertyChanged("FreeText");
}
}
}
}
public class Item : ObservableObject
{
// ...
private Tag _tag;
public Tag Tag
{
get { return _tag; }
set
{
if (value != _tag)
{
_tag= value;
OnPropertyChanged("Tag");
}
}
}
}
The following View Model:
public class AppViewModel : ViewModelBase
{
private Item _item;
public Item Item
{
get { return _item; }
set
{
if (_item != value)
{
_item = value;
OnPropertyChanged("Item");
}
}
}
private List<Tag> _tags;
public List<Tag> Tags
{
get { return _tags; }
set
{
if (_tags != value)
{
_tags = value;
OnPropertyChanged("Tags");
}
}
}
//...
}
Where my Tags list is populated with the following data:
Tags = new List<Tag>()
{
new Tag()
{
Id = 0,
Name = "Free Text"
},
new Tag()
{
Id = 1,
Name = "Tag 1"
},
new Tag()
{
Id = 2,
Name = "Tag 2"
},
new Tag()
{
Id = 3,
Name = "Tag 3"
}
}
And the following View:
<ComboBox x:Name="cmbTags"
ItemsSource="{Binding Tags}"
DisplayMemberPath="Name"
SelectedItem="{Binding Item.Tag}"/>
<TextBox x:Name="txtTagFreeText" Text="{Binding ElementName=cmbTags, Path=SelectedItem.FreeText}">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=cmbTags, Path=SelectedItem.Id}" Value="0">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
What I want to do is to have the ComboBox Items Source binded to my Tags list, and the SelectedItem binded to my Item. I managed to set that up.
Then, if the Tag with the Id 0 is selected, I want to show the TextBox, that is also set up.
What I am having trouble with, is to bind the Text of the TextBox, with the Property FreeText of my Item.Tag.
The binding is kinda working, but it's being applied to all my Items, so whenever I change my Item, the TextBox will allways show my last input.
I have also tried to change the TextBox Text binding to:
{Binding Item.Tag.FreeText}
But the I have the same problem.
Anyone knows what I am doing wrong?
Do I need to post more code?
Any help or feedback is appreciated.
Thank you.
You are binding to a null instance the Item Property of the AppViewModel should Initialize with an instance, but this will not solve the problem you should bind the ComboBox with an other Property in the ViewModel lets name it SelectedTag in the setter of, handle the logic that you want.
public class AppViewModel : ObservableObject
{
private Item _item = new Item();
public Item Item
{
get { return _item; }
set
{
if (_item != value)
{
_item = value;
OnPropertyChanged("Item");
}
}
}
private Tag _SelectedTag;
public Tag SelectedTag
{
get { return _SelectedTag; }
set
{
if (_SelectedTag != value)
{
_SelectedTag = value;
OnPropertyChanged("SelectedTag");
Item.Tag = _SelectedTag;
if (_SelectedTag.Id == 0)
{
_SelectedTag.FreeText = "";
}
}
}
}
private List<Tag> _tags;
public List<Tag> Tags
{
get { return _tags; }
set
{
if (_tags != value)
{
_tags = value;
OnPropertyChanged("Tags");
}
}
}
// ...
}
and the XAML Code:
<ComboBox x:Name="cmbTags"
ItemsSource="{Binding Tags}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedTag,Mode=TwoWay}"/>
<TextBox Text="{Binding SelectedTag.FreeText,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedTag.Id}" Value="0">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
UPDATE
An other way you could bind directly by listening on the Item PropertyChanged Event when the Tag changed Raise Tag Property on the AppViewModel and do you logic.
public class AppViewModel : ObservableObject
{
private Item _item;
public Item Item
{
get { return _item; }
set
{
if (_item != value)
{
_item = value;
_item.PropertyChanged -= _item_PropertyChanged;
_item.PropertyChanged += _item_PropertyChanged;
OnPropertyChanged("Item");
}
}
}
private List<Tag> _tags;
public List<Tag> Tags
{
get { return _tags; }
set
{
if (_tags != value)
{
_tags = value;
OnPropertyChanged("Tags");
}
}
}
public AppViewModel()
{
Item = new Item();
Tags = new List<Tag>()
{
new Tag()
{
Id = 0,
Name = "Free Text"
},
new Tag()
{
Id = 1,
Name = "Tag 1"
},
new Tag()
{
Id = 2,
Name = "Tag 2"
},
new Tag()
{
Id = 3,
Name = "Tag 3"
}
};
}
private void _item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Item.Tag))
{
if (Item.Tag.Id == 0)
{
Item.Tag.FreeText = string.Empty;
}
OnPropertyChanged(nameof(Item));
}
}
}
And bind the ComboBox with The Item Tag directly
<TextBox x:Name="txtTagFreeText" Text="{Binding Item.Tag.FreeText,Mode=TwoWay}">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding Item.Tag.Id}" Value="0">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
I seem to have a problem with adding rows to a DataGridthrough the interface itself.
Here is a screenshot of the UI:
As you can see, 0 rows were found in the database so nothing shows up in the DataGrid on the right side.
But id like there to be one empty row there, for manually adding rows.
The DataGrid.CanUserAddRows is set to True but has no effect.
Here is the xaml for the DataGrid, I have taken the liberty of removing some of the code to make it smaller.
PrivilegeDetailsView.xaml
<UserControl ...
d:DataContext="{d:DesignInstance impl:PrivilegeDetailsViewModel}">
<DataGrid ...
ItemsSource="{Binding RolesHasPrivilegesOnObjects}"
AutoGenerateColumns="False"
CanUserAddRows="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Type" CanUserSort="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type int:IRoleHasPrivilegeOnObjectListItemViewModel}">
<Image Source="{Binding Icon}" ToolTip="{Binding ToolTip}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Width="*" Header="Name" Binding="{Binding Name}"/>
<DataGridCheckBoxColumn Header="Select" Binding="{Binding HasSelect, UpdateSourceTrigger=PropertyChanged}">
<DataGridCheckBoxColumn.ElementStyle>
<Style TargetType="CheckBox">
<Style.Triggers>
<DataTrigger Binding="{Binding CanHaveSelect}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</DataTrigger>
<DataTrigger Binding="{Binding CanHaveSelect}" Value="False">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridCheckBoxColumn.ElementStyle>
</DataGridCheckBoxColumn>
...
</DataGrid.Columns>
</DataGrid>
</UserControl>
PrivilegeDetailsView.xaml.cs
public partial class PrivilegeDetailsView : IPrivilegeDetailsView
{
public PrivilegeDetailsView() { InitializeComponent(); }
public DataGrid PrivilegesOnObjectsDataGrid { get { return PrivilegeDataGrid; } }
public IViewModel ViewModel
{
get { return (IViewModel)DataContext; }
set { DataContext = value; }
}
}
Here is the ViewModel (VM) for the xaml View above:
PrivilegeDetailsViewModel.cs
public class PrivilegeDetailsViewModel : ViewModelBase, IPrivilegeDetailsViewModel
{
private readonly IEventAggregator _eventAggregator;
private readonly IPrivilegeViewModel _privilegeViewModel;
private readonly IRoleHasPrivilegeOnObjectViewModelAdapterRepository _roleHasPrivilegeOnObjectViewModelAdapterRepository;
private ObservableCollection<IRoleHasPrivilegeOnObjectListItemViewModel> _rolesHasPrivilegesOnObjects;
public PrivilegeDetailsViewModel(IPrivilegeDetailsView view,
IRoleHasPrivilegeOnObjectViewModelAdapterRepository roleHasPrivilegeOnObjectViewModelAdapterRepository,
IPrivilegeViewModel privilegeViewModel,
IEventAggregator eventAggregator) : base(view)
{
_roleHasPrivilegeOnObjectViewModelAdapterRepository = roleHasPrivilegeOnObjectViewModelAdapterRepository;
_privilegeViewModel = privilegeViewModel;
_eventAggregator = eventAggregator;
Initialize();
}
protected override sealed void Initialize()
{
_privilegeViewModel.PropertyChanged += PrivilegeViewModelOnPropertyChanged;
_eventAggregator.GetEvent<ToggleSelectPrivilegeEvent>().Subscribe(ToggleSelectPrivilege);
...
}
public new IPrivilegeDetailsView View
{
get { return (IPrivilegeDetailsView)base.View; }
}
public ObservableCollection<IRoleHasPrivilegeOnObjectListItemViewModel> RolesHasPrivilegesOnObjects
{
get { return _rolesHasPrivilegesOnObjects; }
set
{
_rolesHasPrivilegesOnObjects = value;
OnPropertyChanged();
}
}
public void Save()
{
if(RolesHasPrivilegesOnObjects == null) return;
_roleHasPrivilegeOnObjectViewModelAdapterRepository.SaveChanges(RolesHasPrivilegesOnObjects);
}
private void ToggleExecutePrivilege(object obj)
{
var toggle = !View.PrivilegesOnObjectsDataGrid.SelectedItems.Cast<IRoleHasPrivilegeOnObjectListItemViewModel>()
.All(x => x.HasExecute);
foreach(var selectedItem in View.PrivilegesOnObjectsDataGrid
.SelectedItems
.Cast<IRoleHasPrivilegeOnObjectListItemViewModel>()
.Where(selectedItem => selectedItem.Object
.CanHavePrivilege("EXECUTE"))) {
selectedItem.HasExecute = toggle;
}
}
...
private void PrivilegeViewModelOnPropertyChanged(object s, PropertyChangedEventArgs e)
{
switch(e.PropertyName)
{
//When the SelectedSchema changes in the parent VM, I get the new rows to be shown in the DataGrid.
case "SelectedSchema":
RolesHasPrivilegesOnObjects = _roleHasPrivilegeOnObjectViewModelAdapterRepository
.GetPrivilegesOnObjectsAssociatedWith((IRoleEntityViewModel)_privilegeViewModel.SelectedRole,
(IContainerEntityViewModel)_privilegeViewModel.SelectedSchema);
break;
}
}
}
This is the VM for each row in the DataGrid
RoleHasPrivilegeOnObjectEntityViewModel.cs
public class RoleHasPrivilegeOnObjectEntityViewModel : EntityViewModelBase<RoleHasPrivilegeOnObjectEntityViewModel,
RoleHasPrivilegesOnObject>,
IRoleHasPrivilegeOnObjectListItemViewModel
{
private readonly RoleHasPrivilegesOnObject _roleHasPrivilegesOnObject;
public RoleHasPrivilegeOnObjectEntityViewModel(RoleHasPrivilegesOnObject roleHasPrivilegesOnObject)
{
_roleHasPrivilegesOnObject = roleHasPrivilegesOnObject;
Role = new RoleEntityViewModel(_roleHasPrivilegesOnObject.Role);
Object = new ObjectEntityViewModel(_roleHasPrivilegesOnObject.Object);
}
public override EntityType EntityType { get { return EntityType.NONE; } }
public override RoleHasPrivilegesOnObject OriginalEntity { get { return _roleHasPrivilegesOnObject; } }
public IRoleEntityViewModel Role { get; set; }
public IObjectEntityViewModel Object { get; set; }
public string ToolTip { get { return _roleHasPrivilegesOnObject.ToolTip; } }
public bool HasExecute
{
get { return _roleHasPrivilegesOnObject.HasExecute; }
set
{
_roleHasPrivilegesOnObject.HasExecute = value;
OnPropertyChanged();
}
}
public bool CanHaveExecute { get { return _roleHasPrivilegesOnObject.CanHaveExecute; } }
public override string Icon { get { return Object != null ? Object.Icon : string.Empty; } }
public override string NAME
{
get { return _roleHasPrivilegesOnObject.NAME; }
set
{
_roleHasPrivilegesOnObject.NAME = value;
OnPropertyChanged();
}
}
...
}
I know this is a lot of code, I have stripped away a lot and put in place a few dots (...) to show that more code exist. NOTE: Im using EF5 and PRISM
How can I make the DataGrid accept new rows through the GUI?
I believe your problem is using ObservableCollection<IRoleHasPrivilegeOnObjectListItemViewModel> as ItemsSource. In order for DataGrid to be able to create a new row, there has to be a type that can be constructed with an empty constructor.
If you changed it to say ObservableCollection<RoleHasPrivilegeOnObjectEntityViewModel> instead, i'm pretty sure your rows will start getting added.
What I ended up doing was partially/Mostly what Maverik suggested.
I changed ObservableCollection<IRoleHasPrivilegeOnObjectListItemViewModel> to be ObservableCollection<RoleHasPrivilegeOnObjectEntityViewModel> and created a default constructor, which it didn't previously have.
The issue then was that the RoleHasPrivilegeOnObjectEntityViewModel needs some fields and properties set in order to function, so I created a public Initialize function to provided the necessary parameters.
I added an event handler to the DataGrid's InitializingNewItem event, where i called the Initialize function.
private void PrivilegesOnObjectsDataGridOnInitializingNewItem(object s, InitializingNewItemEventArgs e)
{
var newItem = e.NewItem as RoleHasPrivilegeOnObjectEntityViewModel;
if (newItem == null) return;
var role = _privilegeViewModel.SelectedRole;
var schema = _privilegeViewModel.SelectedSchema;
newItem.Initialize(role.OriginalEntity, schema.OriginalEntity);
}
When trying to adda new row, clicking the ComboBox didn't fire off the InitializeNewItem event. But clicking any other column fired off the InitializeNewItem event, and since at first each Row's VM had it's own AvailableObjectTypes property, the ComboBox ItemSource was not set if the ComboBox was selected before any other column, thus making it empty.
That was not an acceptable behaviour so moving AvailableObjectTypes to the PrivilegeDetailsViewModel and changing the ComboBox's ItemSource binding to this helped
ItemsSource="{Binding DataContext.AvailableObjectTypes, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
I have the following code creating a AutoCompleteBox (using WPFToolkit) that combines Companies and Employees into a single search feature. I am forced to use an ObservableCollection as the AutoCompleteBox does not know how to handle CompositeCollections (at least according to what I could find on the subject).
I would like to style the dropdown and textbox such that a Company's text is red and an Employee's text is green. I can set the text color for the entire dropdown as shown in the ResourceDictionary below but cannot find a way to use the DataTrigger (what I have below does not work).
I am not sure how to Bind the DataTrigger to the ModelType property of each object in SearchCollection.
EDIT:
After looking at the code again I realize that there is not a Property called ModelType exposed in the VM, the ModelType property is on each of the objects in SearchCollection. How can I bind to the ModelType of each object in the collection?
In XAML:
<toolkit:AutoCompleteBox Name="OmniSearchTextBox"
ItemsSource="{Binding SearchCollection}"
SelectedItem="{Binding Selection, Mode=TwoWay, UpdateSourceTrigger=Explicit}"
KeyUp="OmniSearch_KeyUp"
MouseLeftButtonUp="OmniSearch_MouseLeftButtonUp"
IsTextCompletionEnabled="False"
FilterMode="Contains"
VerticalAlignment="Top" Margin="10,0" >
In VM:
public sealed class SearchViewModel : ViewModelBase
{
private ViewModelLocator _vmLocator;
private ObservableCollection<Company> _companyList;
private ObservableCollection<Employee> _employeeList;
/// <summary>
/// Initializes a new instance of the SearchViewModel class.
/// </summary>
public SearchViewModel()
{
try
{
_vmLocator = new ViewModelLocator();
_searchCompCollection.Add(companies);
_searchCompCollection.Add(employees);
foreach (Company co in CompanyList)
{
_searchCollection.Add(co);
}
foreach (Employee emp in EmployeeList)
{
_searchCollection.Add(emp);
}
}
catch (Exception ex)
{
}
}
private const string CompanyListPropertyName = "CompanyList";
public ObservableCollection<Company> CompanyList
{
get
{
return (_vmLocator.CompanyVM).CompanyList;
}
set
{
if (_companyList == value)
{
return;
}
_companyList = value;
RaisePropertyChanged(CompanyListPropertyName);
}
}
private const string EmployeeListPropertyName = "EmployeeList";
public ObservableCollection<Employee> EmployeeList
{
get
{
return (_vmLocator.EmployeeVM).EmployeeList;
}
set
{
if (_employeeList == value)
{
return;
}
_employeeList = value;
RaisePropertyChanged(EmployeeListPropertyName);
}
}
private ObservableCollection<ModelBase> _searchCollection = new ObservableCollection<ModelBase>();
public ObservableCollection<ModelBase> SearchCollection
{
get { return _searchCollection; }
}
private string _selection = null;
private string _origSelection = null;
public string Selection
{
get
{
return _selection;
}
set
{
if (_selection != value)
{
_origSelection = _selection;
_selection = value;
try
{
var item = _searchCollection.Single<Object>(x => x.ToString() == _selection);
this.SelectedObject = item;
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
}
private object _selectedObject = null;
private object _origSelectedObject = null;
public object SelectedObject
{
get {
return _selectedObject;
}
set
{
if (_selectedObject != value)
{
_origSelectedObject = _selectedObject;
_selectedObject = value;
switch(_selectedObject.GetType().BaseType.Name)
{
case "Company":
RaisePropertyChanged("SelectedObject",
(Company)_origSelectedObject,
(Company)_selectedObject,
true);
break;
case "Employee":
RaisePropertyChanged("SelectedObject",
(Employee)_origSelectedObject,
(Employee)_selectedObject,
true);
break;
default:
break;
}
RaisePropertyChanged("SelectedObject", _origSelectedObject, _selectedObject, true);
}
}
}
}
In ResourceDictionary (ModelType is a property available in each Model that simply returns the class type, i.e., Company or Employee):
<Style TargetType="{x:Type toolkit:AutoCompleteBox}">
<Setter Property="Foreground" Value="DarkCyan" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SearchCollection.ModelType}"
Value="Company">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=SearchCollection.ModelType}"
Value="Employee">
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
I am trying to dynamically populate a WPF tree by using a ViewModel, however, for some reason it's not working. Either the bindings either aren't properly or I am messing up somewhere in code behind.
Here's a sample of what I have.
In XAML I define my TreeView like so...
<TreeView DockPanel.Dock="Left" Width="200" DataContext="{Binding MessageTree}" ItemsSource="{Binding MessageTree}">
<TreeView.ItemContainerStyle>
<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.ItemTemplate>
<HierarchicalDataTemplate DataType="viewModel:Mail" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Subject}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
In Code Behing i have...
private Mail MessageTree { get; set; }
And
using (var mail = new MailParser())
{
int count = mail.GetMessageCount(DateTime.Today.AddDays(-10), DateTime.Today.AddDays(1));
MessageTree = new Mail();
for (int i = count - 1; i >= 0; i--)
{
MailMessage msg = mail.RetrieveMessage(i);
if (msg != null)
{
MessageTree.Add(msg);
}
if (backgroundWorker != null)
{
decimal perc = (100.0m - (((i + 1.0m)*100.0m)/count));
backgroundWorker.ReportProgress((int) perc, "Recebendo mensagens... " + perc.ToString("N2") + "%");
if (backgroundWorker.CancellationPending)
{
e.Cancel = true;
break;
}
}
}
}
Mail is defined as
public sealed class Mail : INotifyPropertyChanged
{
private readonly ObservableCollection<Mail> _children;
private readonly MailMessage _msg;
private readonly Mail _parent;
private bool _isExpanded;
private bool _isSelected;
public Mail()
{
_msg = new MailMessage {Subject = "Empty"};
_parent = null;
_children = new ObservableCollection<Mail>();
}
public Mail(MailMessage msg, Mail parent = null)
{
_msg = msg;
_parent = parent;
_children = new ObservableCollection<Mail>();
}
public IEnumerable<Mail> Children
{
get { return _children; }
}
public string Subject
{
get { return _msg.Subject; }
}
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (value != _isExpanded)
{
_isExpanded = value;
OnPropertyChanged();
}
if (_isExpanded && _parent != null)
_parent.IsExpanded = true;
}
}
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void Add(MailMessage msg)
{
_children.Add(new Mail(msg, this));
OnPropertyChanged("Children");
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I can't find anything in it so different from examples found online that it wouldn't work. The Add method is incomplete, I still need some logic to decide whether to add them to the collection or to the collection of one of the collection members, but as is all my Mail objecys are beeing added to the collection but not showing up in the TreeView.
What totally obvious thing am i missing? Shouldn't the TreeView automaticly update as I add items to the collection?
What I want is for the TreeView to show The children of the MessageTree property, and those children's children.
EDIT: Couldn't see the whole thing on my phone - amended answer based on ability to actually see everything. :)
MOREEDIT: updated based on comments, let's start from scratch!
First off, if you're set on using the window/whatever as the datacontext, let's make it `INotifyPropertyChange...next, let's make "MessageTree" a collection of mails, not just a single one (it'll make binding semantics easier, trust me)
public class WhateverContainsTheTree : Window, INotifyPropertyChanged
{
public WhateverContainsTheTree()
{
this.Loaded += OnLoaded;
this._messageTree = new ObservableCollection<Mail>();
this.DataContext = this;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
_worker = new BackgroundWorker();
_worker.DoWork += WorkerWorkin;
_worker.RunWorkerAsync();
}
private BackgroundWorker _worker;
private ObservableCollection<Mail> _messageTree;
public ObservableCollection<Mail> MessageTree
{
get { return _messageTree; }
set { _messageTree = value; RaisePropertyChanged("MessageTree"); }
}
public event PropertyChangedEventHandler PropertyChanged = delegate {};
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private void WorkerWorkin(object sender, DoWorkEventArgs e)
{
// obviously, change this to your stuff; I added a ctor so I could pass a string
Thread.Sleep(3000);
Console.WriteLine("Ok, setting message tree");
Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Normal,
(Action)(() =>
{
var mail1 = new Mail("Mail#1:Changed from background thread");
var mail2 = new Mail("Mail#2:Submail of mail #1");
var mail3 = new Mail("Mail#3:Submail of mail #2");
var mail4 = new Mail("Mail#4:Submail of mail #1");
var mail5 = new Mail("Mail#5:Submail of mail #4");
mail1.Children.Add(mail2);
mail1.Children.Add(mail4);
mail2.Children.Add(mail3);
mail4.Children.Add(mail5);
MessageTree.Add(mail1);
})
);
}
}
Also, like I'd said in the original response, let's slightly tweak Mail.Children:
public ObservableCollection<Mail> Children
{
get { return _children; }
}
And here's what I used for the treeview xaml:
<TreeView DockPanel.Dock="Left" Width="200" ItemsSource="{{Binding MessageTree}}">
<TreeView.ItemContainerStyle>
<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.ItemTemplate>
<HierarchicalDataTemplate DataType="viewModel:Mail" ItemsSource="{{Binding Children}}">
<TextBlock Text="{{Binding Subject}}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
If this STILL doesn't work, I'll just paste in the whole LINQPad blob I put together to test this.
Without seeing the entire setup, I'm not positive but my guess would be that since MessageTree is a plain CLR property (rather than something that raises PropertyChanged or a DependencyProperty or something, that the binding is occurring before your MessageTree = new Mail(); call. When you set it to a new instance, the binding system isn't getting notified since it is a plain property.
Another potential issue is that you say that code is in the code-behind. Just using that Binding syntax won't pick up a property from the code-behind. It's possible that you're setting that up somewhere else in the code that you didn't show us. But generally you aren't going to be binding from the View to the code-behind, you'd be binding to a ViewModel that was used as the DataContext for the view itself.
Had to give a name to the TreeView (Tree) and then after
MessageTree = new Mail();
insert
Tree.ItemSource = MessageTree.Children;
I find this ugly but at least it works now. Thank you all for trying to help.