I have a simple form that as a dynamic set of Parent Labels ( could be 1 -10 labels ) that is updated on a timer, if all is good these labels stay green like:
If however one of the parents has a status change, i am trying to then display the offending child or childs and result in somewthing like this:
then once status returns to normal revert back to the original layout ( as above )
so currently i have a view like this :
<Grid>
<ItemsControl ItemsSource = "{Binding Path = CIs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Label Content = "{Binding Path = NodeName, Mode = OneWay}"
Background = "{Binding Path = NodeStatus, Mode = OneWay}"
Tag="{Binding Path = Nodeid, Mode = OneWay}"
Foreground="White"
FontFamily="Arial Black"
HorizontalContentAlignment="Center"
BorderBrush="Black"
BorderThickness="1,1,1,1"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
a viewmodel populating the view with a simple add:
if (Node.level == 3)
{
CIs.Add(new CI { NodeName = Node.name, NodeStatus = Node.status, Nodeid = Node.id });
}
and the basic model :
public class CIModel {}
public class CI : INotifyPropertyChanged {
private string nodeName;
private string nodeStatus;
private string nodeid;
public string NodeName {
get {
return nodeName;
}
set {
if (nodeName != value) {
nodeName = value;
RaisePropertyChanged("NodeName");
}
}
}
public string Nodeid
{
get
{
return nodeid;
}
set
{
if (nodeid != value)
{
nodeid = value;
RaisePropertyChanged("Nodeid");
}
}
}
public string NodeStatus
{
get
{
return nodeStatus;
}
set
{
if (nodeStatus != value)
{
nodeStatus = value;
RaisePropertyChanged("NodeStatus");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
would creating a child view somehow on the parent status change be a possible way? or can i create all the parent and childs and toggle there visibility when there status changes?
Thanks
Did you mean something like this?
// Put this in MainWindow()
public MainWindow()
{
InitializeComponent();
new Demo(this);
}
// Demo code
public class Demo
{
public Demo(FrameworkElement view)
{
View = view;
View.DataContext = this;
StartDemo();
}
private FrameworkElement View { get; }
public ObservableCollection<Parent> Parents { get; } = new ObservableCollection<Parent>();
public async void StartDemo()
{
var delay = 500;
foreach (var index in Enumerable.Range(0, 5))
{
var item = new Parent { Name = $"Parent {index + 1}" };
Parents.Add(item);
await Task.Delay(delay);
}
// Add errors
for (var i = 0; i < 3; i++)
{
Parents[1].Errors.Add(new Child { Name = $"Child {i + 1}" });
await Task.Delay(delay);
}
// Remove errors
while (Parents[1].Errors.Any())
{
Parents[1].Errors.RemoveAt(Parents[1].Errors.Count - 1);
await Task.Delay(delay);
}
// Remove parents
while (Parents.Any())
{
Parents.RemoveAt(Parents.Count-1);
await Task.Delay(delay);
}
}
}
/// <summary>
/// Child (error item)
/// </summary>
public class Child
{
public string Name { get; set; }
}
/// <summary>
/// Parent
/// </summary>
public class Parent : INotifyPropertyChanged
{
public Parent()
{
System.Collections.Specialized.CollectionChangedEventManager.AddHandler(Errors,
delegate
{
OnPropertyChanged(nameof(Status));
});
}
private string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(); }
}
public string Status { get { return Errors.Any() ? "ERROR" : "OK"; } }
/// <summary>
/// Children/errors
/// </summary>
public ObservableCollection<Child> Errors { get; } = new ObservableCollection<Child>();
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
<!-- Xaml -->
<ItemsControl ItemsSource="{Binding Path=Parents}"
Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:Parent}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="ParentColumn" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!-- Parent label -->
<Label Content="{Binding Path=Name}"
x:Name="Label"/>
<!-- Errors -->
<ItemsControl ItemsSource="{Binding Path=Errors}"
Grid.Column="1">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:Child}">
<Label Content="{Binding Path=Name}"
Background="Red" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
<DataTemplate.Triggers>
<!-- Parent is ok -->
<DataTrigger Binding="{Binding Path=Status}"
Value="OK">
<Setter TargetName="Label" Property="Background" Value="Green" />
</DataTrigger>
<!-- Parent is not ok -->
<DataTrigger Binding="{Binding Path=Status}"
Value="ERROR">
<Setter TargetName="Label" Property="Background" Value="Red" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Related
I have tried a bunch of solutions about this problem on google but none seem to be helpful.
I have a button on every row which when clicked open a new window with textboxes. This window should display the selected row cells data.
I load the datagrid from mysql database.
VIEW
textboxes (XML) for second window
<Label Content="{Binding sFirstName, Mode=OneWay }" /> <Label Content="{Binding sLastName, Mode=OneWay }" />
Datagrid
<DataGrid ItemsSource="{Binding Path=MM}" SelectionMode="Extended" SelectedItem="{Binding SelectedItem}" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=sFirstName}" />
<DataGridTextColumn Binding="{Binding Path=sLastName}" />
</DataGrid.Columns>
MODEL
public class MM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string PropertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName)); }
private string _sFirstName, _sLastName;
public string sFirstName { get { return _sFirstName; } set { if (_sFirstName != value) { _sFirstName = value; OnPropertyChanged("sFirstName"); } } }
public string sLastName { get { return _sLastName; } set { if (_sLastName != value) { _sLastName = value; OnPropertyChanged("sLastName"); } } }
public DataRowView SelectedRow
{
get { return SelectedRow; }
set { SelectedRow = value; OnPropertyChanged("SelectedItem"); }
}
}
VIEW MODEL
Public class MV : INotifyPropertyChanged
{
private ICommand cmdLoad;
public ICommand CmdLoad { get { if (cmdLoad == null) cmdLoad = new RelayCommand(p => OnLoad()); return cmdLoad; } }
private void OnLoad() { Load(); }
public ObservableCollection<FinTuitionM> finTuitionM { get; set; }
public ClrIdVMD()
{
Load();
}
public void Load()
{
}
}
Code behind (cs)
public partial class Home : Window
{
MV mv;
public Home()
{ InitializeComponent();
mv = new MV(); DataContext = mv;
}
}
You seem to be very confused, so I have prepared a small example of what I think you are trying to achieve.
I am guessing that you want to have a main view that is essentially read only, and you intend to use a popup to make changes. On this basis the View Model for the main window does not need to implement INotifyPropertyChanged. So a simple View Model would look like this:
public class MV
{
public ObservableCollection<MM> MMs { get; set; }
private ICommand cmdShowDetails;
public ICommand CmdShowDetails
{
get
{
if (cmdShowDetails == null) cmdShowDetails = new RelayCommand(p => ShowDetails());
return cmdShowDetails;
}
}
public void ShowDetails()
{
var detVM = new DetailsVM(SelectedItem);
var dets = new DetailsWindow(detVM);
dets.ShowDialog();
}
public MV()
{
MMs = new ObservableCollection<MM>
{
new MM{sFirstName = "Mickey", sLastName = "Mouse"},
new MM{sFirstName = "Donald", sLastName = "Duck"},
new MM{sFirstName = "Roger", sLastName = "Rabbit"},
};
}
public MM SelectedItem { get; set; }
}
Notice that for demonstration purposes, I have loaded the ObservableCollection with some dummy data. In your case, this is replaced with data from the database.
The MM class that this refers to then looks something like this:
public class MM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName)
{
if (PropertyChanged != null)
{
var e = new PropertyChangedEventArgs(propertyName);
PropertyChanged(this, e);
}
}
private string firstName;
public string sFirstName
{
get { return firstName; }
set
{
if (firstName == value)
{
return;
}
firstName = value;
RaisePropertyChangedEvent("sFirstName");
}
}
private string lastName;
public string sLastName
{
get { return lastName; }
set
{
if (lastName == value)
{
return;
}
lastName = value;
RaisePropertyChangedEvent("sLastName");
}
}
}
Notice that SelectedItem is in the View Model (MV) and is an object of class MM, so that when the second window is opened, the ShowDetails command can pass the selected details.
This therefore calls for a new very simple view model for the second (details) window:
public class DetailsVM
{
public MM Detail { get; set; }
public DetailsVM(MM detail)
{
Detail = detail;
}
}
The main window grid xaml now looks like this:
<Grid>
<DockPanel>
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal">
<Button Content="Show Details" Command="{Binding CmdShowDetails}"></Button>
</StackPanel>
<DataGrid ItemsSource="{Binding MMs}" SelectedItem="{Binding SelectedItem}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding sFirstName}" />
<DataGridTextColumn Header="Last Name" Binding="{Binding sLastName}" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Grid>
Notice here that I only have one button at the bottom of the window to transfer the details. This is because the details come from the selected item, which is the highlighted row.
The code behind is simply:
public partial class MainWindow : Window
{
private MV _mV;
public MainWindow()
{
InitializeComponent();
_mV = new MV();
DataContext = _mV;
}
}
Finally the xaml for the second (details) window
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40*"/>
<RowDefinition Height="40*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70*"/>
<ColumnDefinition Width="200*"/>
</Grid.ColumnDefinitions>
<Label Content="First Name" Grid.Column="0" Grid.Row="0" HorizontalAlignment="Right" VerticalAlignment="Center"/>
<TextBox Text="{Binding Detail.sFirstName}" Grid.Column="1" Grid.Row="0" Width="150" Height="25" HorizontalAlignment="Left" />
<Label Content="Last Name" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Center"/>
<TextBox Text="{Binding Detail.sLastName}" Grid.Column="1" Grid.Row="1" Width="150" Height="25" HorizontalAlignment="Left" />
</Grid>
Notice here that the binding is to Detail.sFirstName and Detail.sLastName. The DataContext is a DetailsVM object, which has a property Detail of type MM, hence sFirstName and sLastName are sub-properties of Detail.
This window also has a very simple code behind:
public partial class DetailsWindow : Window
{
private DetailsVM _details;
public DetailsWindow(DetailsVM details)
{
_details = details;
DataContext = _details;
InitializeComponent();
}
}
If you now run this, you will find that changes made in the second window are automatically reflected back into the main window. In practice you will probably want Save and Cancel buttons in the second window.
I hope the above is sufficient to point you in the right direction!
I can add texts to the DataGrid and by clicking the button (edit) I can drag the data from the DataGrid back into the text boxes, but my problem is if I change the data after dragging it over, it is not updated in the Datagrid.
<Canvas x:Name="CV_Projekte" Grid.Column="1" Background="White" Visibility="Visible">
<Label Content="Projektnumber:" Canvas.Left="44" Canvas.Top="84" FontSize="14" FontWeight="Bold"/>
<Label Content="Name:" Canvas.Left="43" Canvas.Top="113" FontSize="14" FontWeight="Bold" />
<Label Content="Unterposition:" Canvas.Left="44" Canvas.Top="146" FontSize="14" FontWeight="Bold" />
<Label Content="Describe:" Canvas.Left="44" Canvas.Top="182" FontSize="14" FontWeight="Bold" />
<TextBox Canvas.Left="183" Canvas.Top="89" TextWrapping="Wrap" Width="120" Text="{Binding ProjectNumber}" />
<TextBox Canvas.Left="44" Canvas.Top="216" TextWrapping="Wrap" Width="273" Height="155"/>
<TextBox Canvas.Left="183" Canvas.Top="151" TextWrapping="Wrap" Width="120"/>
<TextBox Canvas.Left="183" Canvas.Top="118" TextWrapping="Wrap" Width="120" Text="{Binding ProjectName}"/>
<Button Content="Add" Canvas.Left="383" Canvas.Top="167" Width="75" Command="{Binding StartCommand}" Opacity="0.2"/>
<Button Content="Edit" Canvas.Left="383" Canvas.Top="201" Width="75" Opacity="0.2" Command="{Binding UpdateCommand}" />
<DataGrid Height="575" Canvas.Left="616" Width="303" ItemsSource="{Binding CareList}" SelectedItem="{Binding SelectedCare}" >
<DataGrid.Columns>
<DataGridTextColumn Header="Number" Binding="{Binding Projektnummer}"/>
<DataGridTextColumn Header="Projektname" Width="184" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Delete"/>
</DataGrid.Columns>
</DataGrid>
</Canvas>
This is the CareViewModel.cs
public class CareViewModel :INotifyPropertyChanged
{
private int _ProtjectNumber;
private string _ProjectName;
private Visibility _TrackingVisibility;
private Visibility _CarreVisibility;
private Care _selectedCare;
public int ProjectNumber
{
get
{
return _ProtjectNumber;
}
set
{
_ProtjectNumber = value;
OnPropertyChanged(nameof(ProjectNumber));
}
}
public string ProjectName
{
get
{
return _ProjectName;
}
set
{
_ProjectName = value;
OnPropertyChanged(nameof(ProjectName));
}
}
public Visibility TrackingVisibility
{
get
{
return _TrackingVisibility;
}
set
{
_TrackingVisibility = value;
OnPropertyChanged(nameof(_TrackingVisibility));
}
}
public Visibility CareVisibility
{
get
{
return _CarreVisibility;
}
set
{
_CarreVisibility = value;
OnPropertyChanged(nameof(CareVisibility));
}
}
public void TrackingView(RoutedEventArgs e)
{
TrackingVisibility = Visibility.Visible;
CareVisibility = Visibility.Collapsed;
}
public void CareView(RoutedEventArgs e)
{
TrackingVisibility = Visibility.Collapsed;
CareVisibility = Visibility.Visible;
}
public ObservableCollection<Care> CareList { get; set; }
public CareViewModel()
{
CareList = new ObservableCollection<Care>(); //List
StartCommand = new OurCommand<RoutedEventArgs>(Setting);
TrackingCommand = new OurCommand<RoutedEventArgs>(TrackingView);
CareCommand = new OurCommand<RoutedEventArgs>(CareView);
UpdateCommand = new OurCommand<RoutedEventArgs>(Bearbeitung);
TrackingVisibility = Visibility.Visible;
CareVisibility = Visibility.Collapsed;
}
public ICommand StartCommand { get; set; }
public ICommand TrackingCommand { get; set; }
public ICommand CareCommand { get; set; }
public ICommand UpdateCommand { get; set; }
public Care SelectedCare
{
get
{
return _selectedCare;
}
set
{
_selectedCare = value;
OnPropertyChanged(nameof(SelectedCare));
}
}
public void Setting(RoutedEventArgs e) // Add the Values to Datagrid
{
var projects = new Care();
projects.Projektnummer = _ProtjectNumber;
projects.Name = _ProjectName;
ProjectNumber = 0;
ProjectName = null;
SelectedCare = projects;
CareList.Add(projects);
}
public void Bearbeitung(RoutedEventArgs e) //Editing the value button
{
ProjectNumber = SelectedCare.Projektnummer;
ProjectName = SelectedCare.Name;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Class Care
public class Care
{
private int _Projektnummer;
private string _Name;
public int Projektnummer
{
get { return _Projektnummer;}
set { _Projektnummer = value;}
}
public string Name
{
get { return _Name;}
set { _Name = value;}
}
}
Thanks to everyone who wants to help me.
I've created some demo project from source you provided.
Selection of the row fills TextBoxes automatically
Add Button creates new row and make it able to edit
Save button applies changes in TextBoxes
X button deletes the row
0. MVVM Helpers
I don't know what is OurCommand but I'm using almost unchanged this one.
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
}
It supports command parameters and everything else related to ICommand.
To be able not to implement INotifyPropertyChanged in every View Model, I moved the implementation to the base class.
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
[CallerMemberName] is a compiler trick that allows to call OnPropertyChanged() without arguments. Compiler will fill it automatically with Caller Member Name :) - Property name.
1. Markup
Use Grid and relative positioning of controls. It will help you to make responsive design where layout looks fine for almost any area size.
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Width="1200" Height="600">
<Window.DataContext>
<local:CareViewModel/>
</Window.DataContext>
<Window.Resources>
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="MinHeight" Value="20"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="MinHeight" Value="20"/>
<Setter Property="Margin" Value="5"/>
<Style.Triggers>
<DataTrigger Binding="{Binding EditingCare}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="Button">
<Setter Property="Width" Value="75"/>
<Setter Property="Margin" Value="5"/>
</Style>
</Grid.Resources>
<StackPanel>
<TextBlock Text="Projektnumber:"/>
<TextBlock Text="Name:"/>
<TextBlock Text="Unterposition:"/>
<TextBlock Text="Describe:"/>
</StackPanel>
<StackPanel Grid.Column="1">
<TextBox Text="{Binding EditingCare.ProjectNumber}"/>
<TextBox Text="{Binding EditingCare.Name}"/>
<TextBox Text="{Binding EditingCare.Subtitle}"/>
</StackPanel>
<TextBox TextWrapping="Wrap" Grid.Row="1" Grid.ColumnSpan="2" Text="{Binding EditingCare.Description}"/>
<DockPanel Margin="5" HorizontalAlignment="Right" Grid.Row="2" Grid.ColumnSpan="2">
<Button Content="Add" Command="{Binding AddCommand}" />
<Button Content="Save" Command="{Binding UpdateCommand}" />
</DockPanel>
</Grid>
<DataGrid Grid.Column="1" ItemsSource="{Binding CareList}" SelectedItem="{Binding SelectedCare}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Number" Binding="{Binding ProjectNumber}"/>
<DataGridTextColumn Header="Projektname" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Unterposition" Binding="{Binding Subtitle}" Width="*"/>
<DataGridTemplateColumn Header="Delete">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="X" Command="{Binding DataContext.DeleteCommand,RelativeSource={RelativeSource AncestorType=DataGrid}}" CommandParameter="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
2. Data
To fix the initial problem I've implemented INotifyPropertyChanged for the Care class.
public class Care : NotifyPropertyChanged
{
private int _projectNumber;
private string _name;
private string _description;
private string _subtitle;
public int ProjectNumber
{
get => _projectNumber;
set
{
_projectNumber = value;
OnPropertyChanged();
}
}
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public string Subtitle
{
get => _subtitle;
set
{
_subtitle = value;
OnPropertyChanged();
}
}
public string Description
{
get => _description;
set
{
_description = value;
OnPropertyChanged();
}
}
public Care() { }
public Care(Care care)
{
ProjectNumber = care.ProjectNumber;
Name = care.Name;
Subtitle = care.Subtitle;
Description = care.Description;
}
}
2. View Model
I played a lot with code and came up with following one.
public class CareViewModel : NotifyPropertyChanged
{
private bool _trackingVisible;
private bool _careVisible;
private Care _selectedCare;
private Care _editingCare;
private ObservableCollection<Care> _careList;
private ICommand _addCommand;
private ICommand _trackingCommand;
private ICommand _careCommand;
private ICommand _updateCommand;
private ICommand _deleteCommand;
public ObservableCollection<Care> CareList
{
get => _careList;
set
{
_careList = value;
OnPropertyChanged();
}
}
public bool TrackingVisibility
{
get => _trackingVisible;
set
{
_trackingVisible = value;
OnPropertyChanged();
}
}
public bool CareVisibility
{
get => _careVisible;
set
{
_careVisible = value;
OnPropertyChanged();
}
}
public Care SelectedCare
{
get => _selectedCare;
set
{
_selectedCare = value;
EditingCare = value is Care care ? new Care(care) : value;
OnPropertyChanged();
}
}
public Care EditingCare
{
get => _editingCare;
set
{
_editingCare = value;
OnPropertyChanged();
}
}
public ICommand TrackingCommand => _trackingCommand ?? (_trackingCommand = new RelayCommand(parameter =>
{
TrackingVisibility = true;
CareVisibility = false;
}));
public ICommand CareCommand => _careCommand ?? (_careCommand = new RelayCommand(parameter =>
{
TrackingVisibility = false;
CareVisibility = true;
}));
// Add the Values to Datagrid
public ICommand AddCommand => _addCommand ?? (_addCommand = new RelayCommand(parameter =>
{
Care newItem = new Care();
CareList.Add(newItem);
SelectedCare = newItem;
}));
//Editing the value button
public ICommand UpdateCommand => _updateCommand ?? (_updateCommand = new RelayCommand(parameter =>
{
int index = CareList.IndexOf(SelectedCare);
CareList[index] = EditingCare;
SelectedCare = CareList[index];
}, parameter => EditingCare != null && SelectedCare != null));
public ICommand DeleteCommand => _deleteCommand ?? (_deleteCommand = new RelayCommand(parameter =>
{
if (parameter is Care care)
{
CareList.Remove(care);
}
}));
public CareViewModel()
{
CareList = new ObservableCollection<Care>();
TrackingVisibility = true;
CareVisibility = false;
}
}
3. Visibility
Among other changes pay attention to former Visibility properties now they are bool. And to attach it to controls here's converter that is already meintoined in Markup.
MVVM doesn't suggest to use UI-related types in View Model or Model unless you need it for special purpose.
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> value is bool v && v ? Visibility.Visible : Visibility.Collapsed;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> null;
}
Usage of converter
<Button Visibility="{Binding TrackingVisibility, Converter={StaticResource BoolToVisibilityConverter}}"/>
I'm trying to filter a list based on what a user types into a textbox. However, nothing is happening as the user types into the box. As I've been debugging, I've placed breakpoints on the setter for this binding, but they don't trigger.
TextBox definition:
<TextBox HorizontalAlignment="Center" Text="{Binding TESTSerialNumbSearchTerm, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" ToolTip="Filter Part Number" Width="180"/>
ViewModel Binding:
public String TESTSerialNumbSearchTerm
{
get
{
return serialNumbSearchTerm;
}
set
{
if (serialNumbSearchTerm != value)
{
serialNumbSearchTerm = value;
VisibleProfiles = FilterList(VisibleProfiles, Tuple.Create("serialNumber", value));
OnPropertyChanged(nameof(VisibleProfiles));
OnPropertyChanged(nameof(TESTSerialNumbSearchTerm));
}
}
}
Grid definition, with ItemSource:
<DataGrid MaxHeight="400" Grid.Row="0" ItemsSource="{Binding VisibleProfiles}" SelectedItem="{Binding SelectedProfile}" SelectionMode="Single" IsReadOnly="True" AutoGenerateColumns="False" VerticalScrollBarVisibility="Visible">
FilterList Method:
public List<DongleProfile> FilterList(List<DongleProfile> inputList, Tuple<string, string> filter)
{
List<DongleProfile> newList = new List<DongleProfile>();
foreach (DongleProfile item in inputList)
{
switch (filter.Item1)
{
case "serialNumber":
if (item.SerialNumberPrefix.Contains(filter.Item2))
{
newList.Add(item);
}
break;
// Similar cases
}
}
return newList;
}
If the TextBox is located in a DataGrid, you could use a RelativeSource to bind to a property of the view model:
<TextBox HorizontalAlignment="Center"
Text="{Binding DataContext.TESTSerialNumbSearchTerm, UpdateSourceTrigger=PropertyChanged,
RelativeSource={RelativeSource AncestorType=DataGrid}}"
ToolTip="Filter Part Number" Width="180"/>
Try the following idea:
Public field for filtering textbox
public string md_FilterString
{
get { return _FilterString; }
set
{
if (_FilterString != value)
{
_FilterString = value;
mf_MakeView();
OnPropertyChanged("md_FilterString");
}
}
}
Public field for datagrid binding:
public ICollectionView md_LogEntriesStoreView { get; private set; }
XAML:
..
<TextBox Grid.Column="1"
Text="{Binding md_FilterString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Height="22"
HorizontalAlignment="Stretch"
Margin="0,0,0,0"
Name="textBoxFilter"
VerticalAlignment="Center"/>
..
<DataGrid ItemsSource="{Binding md_LogEntriesStoreView, UpdateSourceTrigger=PropertyChanged}"
..
</DataGrid>
mf_MakeView func configures the composition of the collection md_LogEntriesStoreView:
private void mf_MakeView()
{
if (d_Items== null) return;
md_LogEntriesStoreView = CollectionViewSource.GetDefaultView(d_Items);
md_LogEntriesStoreView.Filter = mf_UserFilter;
OnPropertyChanged("md_LogEntriesStoreView");
}
Where d_Items - are directly the elements of your observable collection that will be displayed in the control datagrid
The filtering function (mf_UserFilter) is presented in a general way for an object containing text fields. You can replace it for optimization purposes with your own version, adapted to your goals:
private bool mf_UserFilter(object item)
{
string s = md_FilterString;
if (String.IsNullOrWhiteSpace(s))
return true;
else
{
var srcT = item.GetType();
foreach (var f in srcT.GetFields())
{
string str = f.GetValue(item) as string;
if (String.IsNullOrWhiteSpace(str)) continue;
bool b = str.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0;
if (b) return true;
}
foreach (var f in srcT.GetProperties())
{
string str = f.GetValue(item, null) as string;
if (String.IsNullOrWhiteSpace(str)) continue;
bool b = str.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0;
if (b) return true;
}
return false;
}
}
UPDATE:
Full text:
Code part:
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindow_ModelView();
}
}
public class MainWindow_ModelView : NotifyBase
{
private string _FilterString = String.Empty;
public ObservableCollection<ItemClass> d_Items { get; set; }
public ICollectionView md_LogEntriesStoreView { get; private set; }
public string md_FilterString
{
get { return _FilterString; }
set
{
if (_FilterString != value)
{
_FilterString = value;
mf_MakeView();
OnPropertyChanged("md_FilterString");
}
}
}
public MainWindow_ModelView()
{
d_Items = new ObservableCollection<ItemClass>() { new ItemClass() { d_Text1 = "Item1Text1", d_Text2 = "Item1Text2" },
new ItemClass() { d_Text1 = "Item2Text1", d_Text2 = "Item2Text2" },
new ItemClass() { d_Text1 = "Item3Text1", d_Text2 = "Item3Text2" } };
md_LogEntriesStoreView = CollectionViewSource.GetDefaultView(d_Items);
}
private void mf_MakeView()
{
if (d_Items == null) return;
md_LogEntriesStoreView = CollectionViewSource.GetDefaultView(d_Items);
md_LogEntriesStoreView.Filter = mf_UserFilter;
OnPropertyChanged("md_LogEntriesStoreView");
}
private bool mf_UserFilter(object item)
{
string s = _FilterString;
if (String.IsNullOrWhiteSpace(s))
return true;
else
{
var srcT = item.GetType();
foreach (var f in srcT.GetFields())
{
string str = f.GetValue(item) as string;
if (String.IsNullOrWhiteSpace(str)) continue;
bool b = str.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0;
if (b) return true;
}
foreach (var f in srcT.GetProperties())
{
string str = f.GetValue(item, null) as string;
if (String.IsNullOrWhiteSpace(str)) continue;
bool b = str.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0;
if (b) return true;
}
return false;
}
}
}
public class ItemClass : NotifyBase
{
public string d_Text1 { get; set; }
public string d_Text2 { get; set; }
}
public class NotifyBase : INotifyPropertyChanged
{
Guid id = Guid.NewGuid();
[Browsable(false)]
[System.Xml.Serialization.XmlAttribute("ID")]
public Guid ID
{
get { return id; }
set
{
if (id != value)
{
id = value;
OnPropertyChanged("ID");
}
}
}
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
XAML part:
<Window x:Class="WpfApplication2.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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Height="23"
Text="{Binding md_FilterString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Stretch"
Name="textBox1"
Margin="2"
VerticalAlignment="Top"/>
<DataGrid ItemsSource="{Binding md_LogEntriesStoreView}"
AutoGenerateColumns="False"
Grid.Row="1"
Margin="2"
HorizontalAlignment="Stretch"
Name="dataGrid1"
VerticalAlignment="Stretch">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path = d_Text1}"
Width="Auto"
IsReadOnly="True"/>
<DataGridTextColumn Binding="{Binding Path = d_Text2}"
Width="*"
IsReadOnly="True" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Result:
I have a UI which displays a ListView in a ListView:
<ListView
SelectedIndex="{x:Bind ParentViewModel.SelectedParentIndex, Mode=TwoWay}"
ItemsSource="{x:Bind ParentViewModel.ParentViewModels, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewModels:ParentViewModel">
<StackPanel>
<TextBlock Text="{Binding ParentName}" />
<ListView
SelectedIndex="{x:Bind SelectedChildIndex, Mode=TwoWay}"
ItemsSource="{Binding ChildViewModels, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewModels:ChildViewModel">
<TextBlock Text="{Binding ChildName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
When I click on a parent element the SelectedParentIndex gets set and when I click on a child element the SelectedChildIndex gets set.
My problem is that when I click on a child element i don't know to wich parent element it belongs because the SelectedParentIndex is not set. How can I solve this?
And the flow how it should be:
Just add an event in. Here is a compiled working example.
<ListView
ItemsSource="{Binding ParentViewModels, Mode=OneWay}"
SelectedIndex="{Binding SelectedParentIndex, Mode=TwoWay}"
SelectedItem="{Binding SelectedParent,Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate >
<StackPanel>
<TextBlock Text="{Binding ParentName}" />
<ListView
ItemsSource="{Binding ChildViewModels, Mode=OneWay}"
SelectedIndex="{Binding SelectedChildIndex, Mode=TwoWay}"
SelectedItem="{Binding SelectedChild,Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ChildName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here are the cs files. Please pay close attention to the structure.
The MasterViewModel is your DataContext for your View. It handles the SelectedParent, the SelectedParentIndex and your parents collection.
public class MasterViewModel : ViewModelBase
{
private ParentViewModel _SelectedParent;
public ParentViewModel SelectedParent
{
get { return _SelectedParent; }
set
{
_SelectedParent = value;
OnPropertyChanged("SelectedParent");
}
}
private int _SelectedParentIndex;
public int SelectedParentIndex
{
get { return _SelectedParentIndex; }
set
{
_SelectedParentIndex = value;
OnPropertyChanged("SelectedParentIndex");
}
}
public ObservableCollection<ParentViewModel> ParentViewModels
{
get; private set;
}
public MasterViewModel()
{
ParentViewModels = new ObservableCollection<ParentViewModel>();
LoadData();
}
private void LoadData()
{
for(int x = 0; x < 10; x++)
{
ParentViewModel parent = new ParentViewModel();
parent.ChildChangedEvent += Parent_ChildChangedEvent;
for(int y = 0; y < 20; y++)
{
ChildViewModel child = new ChildViewModel()
{ ChildName = "Child " + y };
parent.ChildViewModels.Add(child);
}
ParentViewModels.Add(parent);
}
}
private void Parent_ChildChangedEvent(object sender, EventArgs e)
{
SelectedParent = (ParentViewModel)sender;
}
}
Your ParentViewModel contains your SelectedChildIndex, your SelectedChild and your ChildViewModels collection. It also has a name property
Notice that I added an EventHandler to your ParentViewModel. When the SelectedChild is updated, it fires the event off. Then, we handle this event in the MasterViewModel where we can force the SelectedParent to update.
public class ParentViewModel : ViewModelBase
{
public String ParentName { get; set; }
private int _SelectedChildIndex;
public int SelectedChildIndex
{
get { return _SelectedChildIndex; }
set
{
_SelectedChildIndex = value;
OnPropertyChanged("SelectedChildIndex");
}
}
private ChildViewModel _SelectedChild;
public ChildViewModel SelectedChild
{
get { return _SelectedChild; }
set
{
_SelectedChild = value;
OnPropertyChanged("SelectedChild");
if (ChildChangedEvent != null)
{
ChildChangedEvent(this, new EventArgs());
}
}
}
public ObservableCollection<ChildViewModel> ChildViewModels
{
get; private set;
}
public event EventHandler ChildChangedEvent;
public ParentViewModel()
{
ChildViewModels = new ObservableCollection<ChildViewModel>();
}
}
Your ChildViewModel just has a name property.
public class ChildViewModel : ViewModelBase
{
private string _childName;
public string ChildName
{
get { return _childName; }
set
{
_childName = value;
OnPropertyChanged("ChildName");
}
}
}
The ViewModelBase just updates the UI
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propName));
}
}
}
I am using MVVM and trying to represent my ViewModel data in View.
I have a class called Track containing list of Variations. I want to represent each variation as a TextBlock using data binding.
I am able to represent a single track as:
<Window.Resources>
<src:Track x:Key="trck"/>
...
</Window.Resources>
<StackPanel DataContext="{Binding Source={StaticResource trck}}" Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding Vars}" Height="53" Width="349">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Background="{Binding Path=color}" Height="15" Width="{Binding Path=elapsedtime}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
I also have a class called TrackList containing collection of Tracks.
I tried to use HierarchicalDataTemplate to represent Hierarchical Data of TrackList.
But it's not working..
I am new to WPF, and I have tried the below things so far:
<DockPanel.Resources>
<DataTemplate DataType="{x:Type src:Variation}">
<TextBlock Background="{Binding Path=color}" Height="15" Width="{Binding Path=elapsedtime}"/>
</DataTemplate>
<HierarchicalDataTemplate DataType = "{x:Type src:Track}" ItemsSource = "{Binding Path=Vars}">
<StackPanel/>
</HierarchicalDataTemplate>
</DockPanel.Resources>
public class TrackList : ViewModel
{
private ICollection<Track> tracks;
private Track selectedTrack;
public string Name
{ get; set; }
public TrackList()
{
this.tracks = new List<Track>();
this.tracks.Add(new Track("T1"));
this.tracks.Add(new Track("T2"));
Name = "Track List";
selectedTrack = tracks.ElementAt(1);
}
public ICollection<Track> Tracks
{
get { return this.Tracks; }
set { this.Tracks = value; }
}
public Track SelectedTrack
{
get { return this.selectedTrack; }
set
{
if (this.selectedTrack != value)
{
this.selectedTrack = value;
this.OnPropertyChanged("SelectedTrack");
}
}
}
}
public class Track : ViewModel
{
private ICollection<Variation> vars;
private Variation selectedVar;
public string Name { get; set; }
public Track()
{
Init();
}
public Track(string p)
{
// TODO: Complete member initialization
this.Name = p;
Init();
}
private void Init()
{
this.vars = new List<Variation>();
this.vars.Add(new Variation("var1", 20, Brushes.Red));
this.vars.Add(new Variation("var2", 60, Brushes.Green));
this.vars.Add(new Variation("var3", 40, Brushes.Khaki));
this.vars.Add(new Variation("var4", 120, Brushes.Aqua));
selectedVar = vars.ElementAt(1);
}
public ICollection<Variation> Vars
{
get { return this.vars; }
set { this.vars = value; }
}
public Variation SelectedVar
{
get { return this.selectedVar; }
set
{
if (this.selectedVar != value)
{
this.selectedVar = value;
this.OnPropertyChanged("SelectedVar");
}
}
}
}
public class Variation : ViewModel
{
public int elapsedtime { get; set; }
public string Name { get; set; }
public System.Windows.Media.Brush color { get; set; }
public Variation(string varname)
{
Name = varname;
}
public Variation(string name, int time, System.Windows.Media.Brush br)
{
// TODO: Complete member initialization
this.Name = name;
this.elapsedtime = time;
this.color = br;
}
}
public abstract class ViewModel : INotifyPropertyChanged
{
private readonly Dispatcher _dispatcher;
protected ViewModel()
{
}
public event PropertyChangedEventHandler PropertyChanged;
protected Dispatcher Dispatcher
{
get { return _dispatcher; }
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged(this, e);
}
protected void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
Please let me know for any farther information.
Thanks
I don't think you need HierarchicalDataTemplate, your tree has known number of levels (TrackList>Track>Variation). You can simply do this:
<DockPanel.Resources>
<DataTemplate DataType="{x:Type src:Variation}">
<TextBlock Background="{Binding Path=color}" Height="15" Width="{Binding Path=elapsedtime}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type src:Track}">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<ItemsControl ItemsSource="{Binding Vars}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
</DataTemplate>
</DockPanel.Resources>
<ItemsControl ItemsSource="{Binding Tracks}" />
Where ItemsControl bind to Tracks property of the TrackList (ItemsControl.DataContext = TrackList).
You can represent your hierarchical data using a TreeView:
<TreeView ItemsSource="{Binding Tracks}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type src:Track}" ItemsSource="{Binding Vars}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>