Im using DevExpress and Prism in my application which Im new to both. Im trying to access "MyText" in the content item I create in my viewmodel. Currently when this item is created in CreatedSelectionEventReceived() via the viewmodel I don't see this property show up in my parameter list while debugging. I would like to know how to access this once its been created so I can update the property when a property is updated NumGaugeValue via the viewmodel. Right now I can hard code a value for "MyText" but unable to bind to it.
ResourceDictionary.xaml
<ResourceDictionary
x:Class="MyProject.WPF.Resources.CustomShapes"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
xmlns:dxc="http://schemas.devexpress.com/winfx/2008/xaml/charts"
xmlns:dxdiag="http://schemas.devexpress.com/winfx/2008/xaml/diagram"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
xmlns:dxga="http://schemas.devexpress.com/winfx/2008/xaml/gauges"
xmlns:ctrl="clr-namespace:MyProject.WPF.Controls"
xmlns:vm="clr-namespace:MyProject.WPF.ViewModels">
<Style x:Key="NumGaugeContentItem" TargetType="dxdiag:DiagramContentItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ctrl:NumberGauge MyText="22"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
MyView.xaml
<dxdiag:DiagramControl
x:Name="Diagram"
Width="{Binding ElementName=Dashboard, Path=ActualWidth}"
Height="{Binding ElementName=Dashboard, Path=ActualHeight}"
AllowAddRemoveItems="True"
AllowMoveItems="True"
AllowResizeItems="True"
CanvasSizeMode="Fill"
GridSize="25,25"
MaxZoomFactor="1"
MinZoomFactor="1"
ScrollMode="Content"
SelectedStencils="BasicShapes"
SelectionMode="Single"
ShowRulers="False"
ShowSelectionRectangle="False"
SnapToGrid="True"
SnapToItems="False"
ZoomFactor="1" />
<i:Interaction.Triggers>
<i:EventTrigger EventName="Drop">
<prism:InvokeCommandAction Command="{Binding DiagramControlDropCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type dxdiag:DiagramControl}}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
MyViewModel.cs
public class MyViewModel: BindableBase
{
public string NumGaugeValue
{
get { return _numGaugeValue; }
set { SetProperty(ref _numGaugeValue, value); }
}
private DiagramControl DiagramCtrl { get; set; }
private Point CtrlPlacementCoordinates { get; set; }
public DelegateCommand<string> UpdateCommand { get; set; }
public DelegateCommand<DragEventArgs> DiagramControlDropCommand { get; set; }
public MyViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
UpdateCommand = new DelegateCommand<string>(Execute).ObservesProperty(() => NumGaugeValue);
DiagramControlDropCommand = new DelegateCommand<DragEventArgs>(HandleDropOnDxDiagram);
ItemSelectionCommand = new DelegateCommand<object>(ProcessSelectionCommand);
_eventAggregator.GetEvent<ControlSelectionEvent>().Subscribe(ControlSelectionEventReceived);
}
public MyViewModel() { }
private DiagramContentItem CreateDiagramItem(string styleId, Point position)
{
var ctrlItem = new DiagramContentItem() {CustomStyleId = styleId, Position = position};
return ctrlItem;
}
private void HandleDropOnDxDiagram(DragEventArgs sender)
{
DiagramCtrl = (DiagramControl)sender.Source;
CtrlPlacementCoordinates = sender.GetPosition(DiagramCtrl);
}
private void DoDragDrop(object sender)
{
// Grab the control from the accordion item and store as the control that was dropped
var item = (AccordionItem)sender;
var controlDropped = (ControlType)item.Items[0];
if (controlDropped.DataContext != null)
{
DragDrop.DoDragDrop(controlDropped, controlDropped.DataContext, DragDropEffects.Move);
}
}
private void ControlSelectionEventReceived(ControlSelectionMessageInfo controlName)
{
// Create the control that was selected dynamically
switch (controlName.SelectedControlName)
{
case "NumberGauge":
var numGauge = CreateDiagramItem("NumGaugeContentItem", CtrlPlacementCoordinates);
DiagramCtrl.Items.Add(numGauge);
break;
//other cases here but not relevant
}
}
}
Related
I'm new into WPF, and I'm unfortunately used to work only with WinForms.
I'd like to ask for advice with binding.
I want to fill menu items for languages like you can see in the picture below
I have obtained dictionary with language name as a key and bool as value in my viewModel:
internal Dictionary<string,bool> Languages { get; set; }
#endregion
public MainWindowViewModel()
{
Loc = new Locale(CurrentLanguage);
Languages = Loc.GetAvailableLanguages();
}
I have handled it by adding them in the code behind of the MainWindowView.xaml file:
public partial class MainWindow : Window
{
MainWindowViewModel viewModel;
public MainWindow()
{
viewModel = new MainWindowViewModel();
DataContext = viewModel;
InitializeComponent();
PopulateMILanguages();
}
private void PopulateMILanguages()
{
foreach (var lng in viewModel.Languages)
{
MenuItem mi = new MenuItem();
mi.IsCheckable = true;
mi.Header = lng.Key;
mi.Checked += Mi_Checked;
MI_Lngs.Items.Add(mi);
mi.IsChecked = lng.Value;
if (lng.Key.ToLowerInvariant() ==
Settings.Default.LastSelectedLanguage.ToLowerInvariant())
{
mi.IsChecked = true;
}
}
}
private void Mi_Checked(object sender, RoutedEventArgs e)
{
MenuItem menuItem = sender as MenuItem;
viewModel.CurrentLanguage = menuItem.Header.ToString()
.ToLowerInvariant();
Settings.Default.LastSelectedLanguage=viewModel.CurrentLanguage;
foreach (MenuItem mi in MI_Lngs.Items)
{
if (mi.Header != menuItem.Header)
{
mi.IsChecked = false;
}
}
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
Settings.Default.Save();
}
}
I really don't like this solution, but I was not able to handle it to fill collection in the xaml:
<!-- LANGUAGE -->
<MenuItem x:Name="MI_Lngs" Header="{Binding Loc[Lng]}"
Style="{StaticResource MenuItemToolbar}">
<MenuItem.Icon>
<Image Source="/Assets/languages.png"/>
</MenuItem.Icon>
<MenuItem.ToolTip>
<ToolTip Content="{Binding Loc[Lng_TT]}"/>
</MenuItem.ToolTip>
</MenuItem>
Could anyone please give me an advice how to handle it to fill menu items from the collection and also be able to catch checked events?
Thanks in advance
Jiri
You can use this menu with the below viewmodel to get the desired functionality.
<Menu>
<MenuItem Header="File"/>
<MenuItem Header="Settings">
<MenuItem Header="Application"/>
<MenuItem Header="Language" ItemsSource="{Binding Languages}">
<MenuItem.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.IsCheckable" Value="True"/>
<Setter Property="MenuItem.Header" Value="{Binding Header, Mode=OneWay}"/>
<Setter Property="MenuItem.IsChecked" Value="{Binding IsSelected, Mode=OneWay}"/>
<Setter Property="MenuItem.Command" Value="{Binding DataContext.SelectLanguageCommand, RelativeSource={RelativeSource AncestorType=MenuItem}}"/>
<Setter Property="MenuItem.CommandParameter" Value="{Binding}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</MenuItem>
</Menu>
I used CommunityToolkit.Mvvm library to give you some idea here. You can use another library.
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
namespace WpfApp1.ViewModel
{
public class MainWindowViewModel : ObservableObject
{
private ObservableCollection<Language> _languages = new();
public ObservableCollection<Language> Languages
{
get => _languages;
set => SetProperty(ref _languages, value);
}
public IRelayCommand SelectLanguageCommand { get; }
public MainWindowViewModel()
{
Languages.Add(new Language { Header = "CZ", IsSelected = false });
Languages.Add(new Language { Header = "EN", IsSelected = true });
SelectLanguageCommand = new RelayCommand<Language>(l =>
{
if (l != null)
{
foreach (Language item in Languages)
{
item.IsSelected = false;
}
l.IsSelected = true;
}
});
}
}
public class Language : ObservableObject
{
private string? _header;
public string? Header
{
get => _header;
set => SetProperty(ref _header, value);
}
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set => SetProperty(ref _isSelected, value);
}
}
}
I'm using a HierarchicalDataTemplate to build a menubar that will have MenuItems that are checkable. In my ViewModel, I create an ObservableCollection of a class called MenuItemModel, then bind the ObservableCollection in my View. I can build the menu along with its submenus, but I can't figure out how to tell the ViewModel which MenuItem is checked.
I've tried using the INotifyPropertyChanged in the MenuItemModel but I could not figure out how to send that information to the ViewModel. After much googling, I've come to the conclusion this isn't the proper approach and I only need to use the INotifyPropertyChanged in the ViewModel. I'm a WPF newbie so still learning the do's and don'ts. I've found most of the code below on StackOverflow and have managed to adapt it to to my needs but I'm still trying to wrap my head around how it works. The code below will create a menu "Main Menu" with 3 submenus "SubMenu1", "SubMenu2", and "SubMenu3" where they are all checkable. That being said, here are my questions:
How/where do I implement the OnPropertyChanged event in the ViewModel when a MenuItem is checked/unchecked?
How can I access model properties of the MenuItem that was checked/unchecked?
<Menu DockPanel.Dock="Top" ItemsSource="{Binding MenuItemsObservableCollection}">
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="IsCheckable" Value="{Binding IsCheckable}" />
<Setter Property="StaysOpenOnClick" Value="{Binding IsCheckable}" />
<Setter Property="IsChecked" Value="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Style>
</Menu.ItemContainerStyle>
<Menu.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:ViewModel}" ItemsSource="{Binding Path=SubMenuItemsObservableCollection}">
<TextBlock Text="{Binding Header}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
AddMenuItems();
}
private void AddMenuItems()
{
var subMenu = new ObservableCollection<MenuItemModel>
{
new MenuItemModel { Header = "SubMenu1" },
new MenuItemModel { Header = "SubMenu2"},
new MenuItemModel { Header = "SubMenu3"}
};
MenuItemsObservableCollection = new ObservableCollection<MenuItemModel>
{
new MenuItemModel { Header = "Main Menu", SubMenuItemsObservableCollection = subMenu }
};
}
private void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<MenuItemModel> MenuItemsObservableCollection { get; set; }
}
class MenuItemModel
{
public MenuItemModel()
{
}
public string Header { get; set; }
public string Key { get; set; }
public bool IsCheckable { get; set; } = true
public bool IsChecked { get; set; } = false;
public ObservableCollection<MenuItemModel> SubMenuItemsObservableCollection { get; set; }
}
I'm working in MVVM, WPF and I have a popup; inside this popup is a listbox and inside the listbox I have a checkbox. The problem is: if I uncheck an item from the list box and click outside, popup disappears; if a I click again the checkbox is reseted at its initial value (all the items become checked).
So, how can I maintain the state of the popup set and stop its resetting while the app is running ? Can I do this through XAML ?
here is the code:
public class CheckedListItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked = false;
private T item;
public CheckedListItem()
{ }
public CheckedListItem(T item, bool isChecked)
{
this.item = item;
this.isChecked = isChecked;
}
public T Item
{
get { return item; }
set
{
item = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
}
the viewModel:
private void OnApplyFiltersCommandRaised(object obj)
{
if (FilterElement.Contains("ClassView"))
{
switch (FilterElement)
{
case "buttonClassViewClassFilter":
FilteredClassViewItems.Clear();
FilteredFieldViewItems.Clear();
foreach (var filterItem in FilterItems)
{
if (filterItem.IsChecked == true)
{
FilteredClassViewItems.Add(classViewItems.First(c => c.ClassName == filterItem.Item));
FilteredFieldViewItems.Add(fieldViewItems.First(c => c.ClassName == filterItem.Item));
}
}
break;
...
public ObservableCollection<CheckedListItem<string>> FilterItems
{
get
{
return filterItems;
}
set
{
filterItems = value;
SetPropertyChanged("FilterItems");
}
}
the XAML part:
<ListBox x:Name="listBoxPopupContent"
Height="250"
ItemsSource="{Binding FilterItems}"
BorderThickness="0"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="FontSize" Value="8" />
<Setter Property="IsSelected" Value="{Binding IsChecked, Mode=TwoWay}" />
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked}"
Content="{Binding Item}"
Command="{Binding DataContext.ApplyFiltersCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBox}}}"
CommandParameter="{Binding IsChecked,
RelativeSource={RelativeSource Self},
Mode=OneWay}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Thanks in advance !
If you want to keep the state, you can just create a new view that will contain your listbox. Then your popup will be
<Popup>
<views:MyListBoxview>
</Popup>
where views is the path where wpf can find MyListBoxview.
This is an example of how you can do MyListBoxView. First of all, add a new usercontrol to your project. Then you create:
<ListBox ItemSource = {Binding MyCollectionOfItem}>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked = {Binding IsItemChecked} Content = {Binding Name}/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You will need to assign to this view a viewmodel that will of course implement INotifyPropertyChanged and that will have these this class defined inside it (also this class will implement INotifyPropertyChanged)
public class MyItem : INotifyPropertyChanged
{
public void SetPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
private bool isItemChecked = false;
public bool IsItemChecked
{
get { return isItemChecked; }
set
{
isItemChecked = value;
SetPropertyChanged("IsItemChecked");
}
}
private string name ;
public string Name
{
get { return Name; }
set
{
name = value;
SetPropertyChanged("Name");
}
}
}
finally, the viewmodel that will represent the state of the popup will have inside this property
private ObservableCollection<MyItem> myCollectionOfItem = new ObservableCollection<MyItem>();
public ObservableCollection<MyItem> MyCollectionOfItem
{
get { return myCollectionOfItem; }
set
{
myCollectionOfItem = value;
SetPropertyChanged("MyCollectionOfItem");
}
}
I usually handle this kind of problem modelling properly the object that i need to bind to my controls in WPF
There is a collection of categories with products.
Each category is represented in the interface by the AvalonDock tab, which has a DataGrid with products.
Now when switching from tab to tab, DataGrid updates the collection every time. If you select a pair of rows in the table on the first tab, switch to the second tab and return to the first one, the selection disappears.
What could be the problem?
XAML:
<xcad:DockingManager DocumentsSource="{Binding Examples}">
<xcad:DockingManager.LayoutItemTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Content.Items}"
SelectionMode="Extended" />
</DataTemplate>
</xcad:DockingManager.LayoutItemTemplate>
<xcad:LayoutRoot />
</xcad:DockingManager>>
Code-behind:
public partial class MainWindow : Window
{
public class Example
{
public List<int> Items { get; } = new List<int>();
public Example()
{
for (var i = 0; i < 10; i++)
{
Items.Add(i);
}
}
}
public List<Example> Examples { get; } = new List<Example>();
public MainWindow()
{
InitializeComponent();
DataContext = this;
Examples.Add(new Example());
Examples.Add(new Example());
}
}
As #nobody suggested, switching between tabs seems to update the layout, and the selection state is lost. If UI can't persist the selection state, then you can use the next layer i.e. presentation or view-model to do the same.
In this case, adding a IsSelected property to view-model item and a binding to ListViewItem should do the trick.
XAML:
<Grid>
<xcad:DockingManager DocumentsSource="{Binding Examples}">
<xcad:DockingManager.DocumentHeaderTemplate>
<DataTemplate>
<TextBlock Text="Doc" />
</DataTemplate>
</xcad:DockingManager.DocumentHeaderTemplate>
<xcad:DockingManager.LayoutItemTemplate>
<DataTemplate>
<ListBox
DisplayMemberPath="Value"
ItemsSource="{Binding Content.Items}"
SelectionMode="Extended">
<ListBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="LightBlue" />
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.Resources>
</ListBox>
</DataTemplate>
</xcad:DockingManager.LayoutItemTemplate>
<xcad:LayoutRoot />
</xcad:DockingManager>
</Grid>
Code-behind:
public partial class MainWindow : Window
{
public class ExampleItem
{
public int Value { get; set; }
public bool IsSelected { get; set; }
}
public class Example
{
public List<ExampleItem> Items { get; } = new List<ExampleItem>();
public Example()
{
for (var i = 0; i < 10; i++)
{
Items.Add(new ExampleItem { Value = i });
}
}
}
public List<Example> Examples { get; } = new List<Example>();
public MainWindow()
{
InitializeComponent();
DataContext = this;
Examples.Add(new Example());
Examples.Add(new Example());
}
}
I have a WPF TreeView populated by an observable collection using a hiarchialdatabinding
I need to access the item in my observable collection or the database that was used to populate it.
An example use case is that the user right clicks a treeview item to add a subgroup. I obviously need to access its parent to add the child.
Any suggestions? Im so lost..
I cant just edit the treeview item itself cause then the changes wont reflect back to my database
Database Code:
[Serializable]
public class LoginGroup
{
public string Name { get; set; }
public Guid ID { get; set; }
public List<Login> LoginItems = new List<Login>();
public List<LoginGroup> Children { get; set; }
}
public static ObservableCollection<LoginGroup> _GroupCollection = new ObservableCollection<LoginGroup>();
public ObservableCollection<LoginGroup> GroupCollection
{
get { return _GroupCollection; }
}
TreeView:
<TreeView x:Name="groupView" Width="211" TreeViewItem.Selected="OnTreeItemSelected" DockPanel.Dock="Left" Height="Auto" ItemsSource="{Binding GroupCollection}" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
You can just cast the SelectedItem to LoginGroup:
LoginGroup selectedGroup = (LoginGroup)groupView.SelectedItem;
You can't reflect back changed of your properties because they don't have way to "notice" back that they are edited. You need inherit LoginGroup from DependencyObject or implement INotifyPropertyChanged
You should use TreeView's ItemContainer style.
Here's sample TreeNode view model:
public class TreeNode : ViewModel
{
public TreeNode()
{
this.children = new ObservableCollection<TreeNode>();
// the magic goes here
this.addChildCommand = new RelayCommand(obj => AddNewChild());
}
private void AddNewChild()
{
// create new child instance
var child = new TreeNode
{
Name = "This is a new child node.",
IsSelected = true // new child should be selected
};
// add it to collection
children.Add(child);
// expand this node, we want to look at the new child node
IsExpanded = true;
}
public String Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
OnPropertyChanged("Name");
}
}
}
private String name;
public Boolean IsSelected
{
get { return isSelected; }
set
{
if (isSelected != value)
{
isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
private Boolean isSelected;
public Boolean IsExpanded
{
get { return isExpanded; }
set
{
if (isExpanded != value)
{
isExpanded = value;
OnPropertyChanged("IsExpanded");
}
}
}
private Boolean isExpanded;
public ObservableCollection<TreeNode> Children
{
get { return children; }
}
private ObservableCollection<TreeNode> children;
public ICommand AddChildCommand
{
get { return addChildCommand; }
}
private RelayCommand addChildCommand;
}
Some comments:
ViewModel is any base implementation of INotifyPropertyChanged
interface.
RelayCommand (a.k.a. DelegateCommand) is ICommand implementation for use in MVVM approach.
Here's the view:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<TreeView ItemsSource="{Binding}">
<TreeView.ItemContainerStyle>
<!-- Let's glue our view models with the view! -->
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<!-- Here's menu item, which is responsible for adding new child node -->
<MenuItem Header="Add child..." Command="{Binding AddChildCommand}" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Window>
... and sample data context initialization:
public MainWindow()
{
InitializeComponent();
DataContext = new ObservableCollection<TreeNode>
{
new TreeNode { Name = "Root", IsSelected = true }
};
}
Hope this helps.
Upd.
Of course, you have to expose child nodes as the ObservableCollection too. Otherwise, changes made to nodes collection won't be reflected.