I am able to populate List in the code behind but the values do not bind to the combobox in xaml
Here is the sample code
public IEnumerable<string> _AllProperties;
public IEnumerable<string> AllProperties
{
get { return _AllProperties; }
set
{
if (_AllProperties == value) return;
_AllProperties = value;
OnPropertyChanged("AllProperties");
}
}
public MainWindow()
{
InitializeComponent();
// Create a list
List<string> PropertyValues = new List<string>();
// Add a range of items
string[] values = { "BlackOilFluid",
"Boundary",
"Casing",
"CheckValve",
"Choke",
"Completion",
"CompletionConingPoint",
"CompletionModel",
"CompositionalFluid",
"Compressor",
"EngineKeywords",
"ESP",
"Expander",
"FileBasedFluid",
"Flowline",
"FluidComponent",
"GasLiftInjection",
"GenericBooster",
"GenericEquipment",
"GravelPack",
"HeatExchanger",
"Injector",
"IPRBackPressure",
"IPRDarcy",
"IPRFetkovitch",
"IPRForchheimer",
"IPRHorizontalPI",
"IPRHydraulicFracture",
"IPRJones",
"IPRPIModel",
"IPRPSSBabuOdeh",
"IPRSSJoshi",
"IPRVogel",
"Junction",
"Liner",
"MeasurementPoint",
"MFLFluid",
"MultiphaseBooster",
"MultiplierAdder",
"NetworkSim",
"NodalAnalysisOp",
"OneSubseaBooster",
"OpenHole",
"Packer",
"PCP",
"PTProfileSim",
"Pump",
"PVTFluid",
"RodPump",
"SinglephaseSeparator",
"Sink",
"SlidingSleeve",
"Source",
"Study",
"SubsurfaceSafetyValve",
"ThreePhaseSeparator",
"Tubing",
"TubingPlug",
"TwoPhaseSeparator",
"WaterTempVelocitySurvey",
"Well",
"WetGasCompressor" };
PropertyValues.AddRange(values);
_AllProperties = PropertyValues;
}
I expect the combo box to be populate with the above defined list of strings.
Here is the XAML code:
<ComboBox Grid.Column="2" x:Name="propvar" HorizontalAlignment="Center" VerticalAlignment="Center" Height="30" Width="100" Margin="5,5,5,5" IsTextSearchEnabled="True" FontSize="11"
ItemsSource="{Binding AllProperties}" SelectionChanged="Var_SelectionChanged"
SelectedItem="{Binding Propselected, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" />
I do not quite understand why the values are not displayed in the combobox dropdown.
Try this:
Xaml:
<Window x:Class="TestChipDeleting.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"
mc:Ignorable="d"
Title="MainWindow"
Height="400"
Width="400">
<StackPanel VerticalAlignment="Center"
DataContext="{Binding}">
<ComboBox ItemsSource="{Binding Path=Properties}"
IsTextSearchEnabled="True"
FontSize="11"
Margin="8"
Height="30"
Width="200"
HorizontalAlignment="Center"
VerticalAlignment="Center"
SelectionChanged="Var_SelectionChanged"
SelectedItem="{Binding Propselected,
NotifyOnValidationError=True,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True,
ValidatesOnExceptions=True}"/>
</StackPanel>
</Window>
C#:
namespace Test
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
private void Var_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
}
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
BindItems();
}
private List<string> _properties;
public List<string> Properties
{
get { return _properties; }
set
{
if (value.Equals(_properties))
return;
_properties = value;
OnPropertyChanged(nameof(Properties));
}
}
private void BindItems()
{
string[] items =
{
"BlackOilFluid",
"Boundary",
"Casing",
"CheckValve",
"Choke",
"Completion",
"CompletionConingPoint",
"CompletionModel",
"CompositionalFluid",
"Compressor",
"EngineKeywords",
"ESP",
"Expander",
"FileBasedFluid",
"Flowline",
"FluidComponent",
"GasLiftInjection",
"GenericBooster",
"GenericEquipment",
"GravelPack",
"HeatExchanger",
"Injector",
"IPRBackPressure",
"IPRDarcy",
"IPRFetkovitch",
"IPRForchheimer",
"IPRHorizontalPI",
"IPRHydraulicFracture",
"IPRJones",
"IPRPIModel",
"IPRPSSBabuOdeh",
"IPRSSJoshi",
"IPRVogel",
"Junction",
"Liner",
"MeasurementPoint",
"MFLFluid",
"MultiphaseBooster",
"MultiplierAdder",
"NetworkSim",
"NodalAnalysisOp",
"OneSubseaBooster",
"OpenHole",
"Packer",
"PCP",
"PTProfileSim",
"Pump",
"PVTFluid",
"RodPump",
"SinglephaseSeparator",
"Sink",
"SlidingSleeve",
"Source",
"Study",
"SubsurfaceSafetyValve",
"ThreePhaseSeparator",
"Tubing",
"TubingPlug",
"TwoPhaseSeparator",
"WaterTempVelocitySurvey",
"Well",
"WetGasCompressor"
};
Properties = new List<string>(items);
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
Related
When I click on a row of WPF datagrid, I want to open a new Window that has information about a person from the clicked row (that can be changed) by using binding. How can I do it? And how can I save the changed information?
Thanks in Advance!
I did solve a simelar propblem using the Behaviours Libary.
First Make sure you have the Nuget package: "Microsoft.Xaml.Behaviors.Wpf" installed.
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
<DataGrid>
<i:Interaction.Triggers>
<i:EventTrigger
EventName="MouseUp">
<i:InvokeCommandAction
Command="{Binding OpenWindowCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}, Path=SelectedItem}">
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
Hope this snippet helps.
You can add a SelectedItem property in the ViewModel. Bind that to DataGrid's SelectedItem. Now you know which item the user has selected (clicked on).
In the DataGrid's MouseDown or MouseUp event you can open the new Window with the same ViewModel object as the DataContext. That way the new Window knows which item was selected. Bind the new Window's fields to the SelectedItem in the ViewModel.
If you have set up the INotifyPropertyChanged correctly, the values that are changed in the new Window also will show up in the DataGrid in the first Window.
Since the SelectedItem also is part of the collection, you will automatically have the changed values in the collection as well. (I assume this is what you mean with "save the changed information").
if your collection, that is bound to the DataGrid, is a collection of Person objects, the "SelectedItem property" could look like this:
public Person SelectedPerson
{
get
{
return _selectedPerson;
}
set
{
_selectedPerson = value;
PropertyChanged....
}
}
private Person _selectedPerson;
ViewModel.cs
namespace Procect1
{
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
ListOfPeople = new ObservableCollection<Person>();
}
string image;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private void Window2OpenExecute()
{
Window2 Window2 = new Window2(this);
Window2.Show();
}
private void AddPhotoExecute()
{
foreach (Window window in Application.Current.Windows)
{
if (window.GetType() == typeof(Window2))
{
OpenFileDialog op = new OpenFileDialog();
op.Title = "Select a picture";
op.Filter = "All supported graphics|*.jpg;*.jpeg;*.png|" +
"JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg|" +
"Portable Network Graphic (*.png)|*.png";
if (op.ShowDialog() == true)
{
(window as Window2).imgPhoto.Source = new BitmapImage(new Uri(op.FileName));
image = op.FileName;
}
}
}
}
private void AddPersonToListExecute()
{
foreach (Window window in Application.Current.Windows)
{
if (window.GetType() == typeof(Window2))
{
ListOfPeople.Add(new Person()
{
Name = (window as Window2).textBox_FirstName.Text,
Surname = (window as Window2).textBox_LastName.Text,
IdNumber = (window as Window2).textBox_IdNumber.Text,
Age = (window as Window2).textBox_Age.Text,
Image = image
});
(window as Window2).Close();
}
}
}
private void SerializeExecute()
{
Stream stream = File.OpenWrite(Environment.CurrentDirectory + "\\Serialization1.xml");
XmlSerializer people = new XmlSerializer(typeof(ObservableCollection<Person>));
people.Serialize(stream, ListOfPeople);
stream.Close();
}
private void DeSerializeExecute()
{
XmlSerializer deserializer = new XmlSerializer(typeof(ObservableCollection<Person>));
TextReader textReader = new StreamReader(Environment.CurrentDirectory + "\\Serialization1.xml");
ListOfPeople = (ObservableCollection<Person>)deserializer.Deserialize(textReader);
textReader.Close();
}
public ICommand Window2Open { get { return new RelayCommand(Window2OpenExecute); } }
public ICommand AddPersonToList { get { return new RelayCommand(AddPersonToListExecute); } }
public ICommand Serialize { get { return new RelayCommand(SerializeExecute); } }
public ICommand DeSerialize { get { return new RelayCommand(DeSerializeExecute); } }
public ICommand AddPhoto { get { return new RelayCommand(AddPhotoExecute); } }
public ObservableCollection<Person> ListOfPeople
{
get
{
return listOfPeople;
}
set
{
listOfPeople = value;
OnPropertyChanged("ListOfPeople");
}
}
public Person SelectedPerson
{
get
{
return _selectedPerson;
}
set
{
_selectedPerson = value;
OnPropertyChanged(nameof(SelectedPerson));
}
}
private Person _selectedPerson;
private ObservableCollection<Person> listOfPeople;
public event PropertyChangedEventHandler PropertyChanged;
}
}
Person.cs
namespace Procect1
{
public class Person : INotifyPropertyChanged
{
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
public string Surname
{
get
{
return surname;
}
set
{
surname = value;
OnPropertyChanged(nameof(Surname));
}
}
public string IdNumber
{
get
{
return idNumber;
}
set
{
idNumber = value;
OnPropertyChanged(nameof(IdNumber));
}
}
public string Age
{
get
{
return age;
}
set
{
age = value;
OnPropertyChanged(nameof(Age));
}
}
public string Image
{
get
{
return image;
}
set
{
image = value;
OnPropertyChanged(nameof(Image));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
private string name;
private string surname;
private string idNumber;
private string age;
private string image;
}
}
MainWindow.xaml
<Window x:Class="Procect1.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:Procect1"
mc:Ignorable="d"
Title="Lista pacjentów" Height="450" Width="800">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<!--<RowDefinition Height="auto" />-->
<!--<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>-->
</Grid.RowDefinitions>
<DataGrid Name="listView" MouseUp="listView_MouseUp" ItemsSource="{Binding ListOfPeople}" AutoGenerateColumns="False" SelectedItem="{Binding Index}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Header="Imię" />
<DataGridTextColumn Binding="{Binding Surname}" Header="Nazwisko" />
<DataGridTextColumn Binding="{Binding IdNumber}" Header="Pesel" />
<DataGridTextColumn Binding="{Binding Age}" Header="Wiek" />
<DataGridTemplateColumn Header="Zdjęcie" Width=" 100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Image}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Canvas>
<Button Canvas.Right="20" Canvas.Top="30" Height="50" Width="80" FontSize="18" Command="{Binding Window2Open}">Dodaj</Button>
<Button Canvas.Right="20" Canvas.Top="100" Height="50" Width="80" FontSize="18" Command="{Binding Serialize}">Serializuj</Button>
<Button Canvas.Right="20" Canvas.Top="170" Height="50" Width="80" FontSize="18" Command="{Binding DeSerialize}">Załaduj</Button>
</Canvas>
</Grid>
</Window>
MainWindow.cs
public partial class MainWindow : Window
{
public MainWindow( )
{
InitializeComponent();
}
private void listView_MouseUp(object sender, MouseButtonEventArgs e)
{
Window3 window3 = new Window3();
window3.Show();
}
}
Window2.xaml
<Window x:Class="Procect1.Window2"
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:Procect1"
mc:Ignorable="d"
Title="Dane Pacjenta" Height="600" Width="500">
<Canvas>
<Label Canvas.Left="20" Canvas.Top="20" FontSize="24">Imię</Label>
<TextBox x:Name="textBox_FirstName" Canvas.Left="190" Canvas.Top="30" Height="28" Width="200" ></TextBox>
<Label Canvas.Left="20" Canvas.Top="100" FontSize="24">Nazwisko</Label>
<TextBox x:Name="textBox_LastName" Canvas.Left="190" Canvas.Top="110" Height="28" Width="200"></TextBox>
<Label Canvas.Left="20" Canvas.Top="180" FontSize="24">Pesel</Label>
<TextBox x:Name="textBox_IdNumber" Canvas.Left="190" Canvas.Top="190" Height="28" Width="200"></TextBox>
<Label Canvas.Left="20" Canvas.Top="260" FontSize="24">Wiek</Label>
<TextBox x:Name="textBox_Age" Canvas.Left="190" Canvas.Top="270" Height="28" Width="200"></TextBox>
<Label Canvas.Left="20" Canvas.Top="340" FontSize="24">Zdjęcie</Label>
<Image Name="imgPhoto" Canvas.Left="190" Canvas.Top="350" Height="120" Width="150"></Image>
<Button Canvas.Right="20" Canvas.Top="400" FontSize="16" Command="{Binding AddPhoto}" >Dodaj zdjęcie</Button>
<Button Canvas.Left="250" Canvas.Bottom="20" Height="40" Width="80" FontSize="24" Command="{Binding AddPersonToList}">Zapisz</Button>
</Canvas>
</Window>
MainWindow2.cs
public partial class Window2 : Window
{
public Window2(ViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}
Window3.xaml
<Window x:Class="Procect1.Window3"
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:Procect1"
mc:Ignorable="d"
Title="Dane Pacjenta" Height="600" Width="500">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Canvas>
<Label Canvas.Left="20" Canvas.Top="20" FontSize="24">Imię</Label>
<TextBox x:Name="textBox_FirstName" Text="{Binding SelectedPerson.Name}" Canvas.Left="190" Canvas.Top="30" Height="28" Width="200" ></TextBox>
<Label Canvas.Left="20" Canvas.Top="100" FontSize="24">Nazwisko</Label>
<TextBox x:Name="textBox_LastName" Text="{Binding SelectedPerson.Surname}" Canvas.Left="190" Canvas.Top="110" Height="28" Width="200"></TextBox>
<Label Canvas.Left="20" Canvas.Top="180" FontSize="24">Pesel</Label>
<TextBox x:Name="textBox_IdNumber" Text="{Binding SelectedPerson.IdNumber}" Canvas.Left="190" Canvas.Top="190" Height="28" Width="200"></TextBox>
<Label Canvas.Left="20" Canvas.Top="260" FontSize="24">Wiek</Label>
<TextBox x:Name="textBox_Age" Text="{Binding SelectedPerson.Age}" Canvas.Left="190" Canvas.Top="270" Height="28" Width="200"></TextBox>
<Label Canvas.Left="20" Canvas.Top="340" FontSize="24">Zdjęcie</Label>
<Image Name="imgPhoto" Source="{Binding SelectedPerson.Image}" Canvas.Left="190" Canvas.Top="350" Height="120" Width="150"></Image>
<Button Canvas.Right="20" Canvas.Top="400" FontSize="16" Command="{Binding AddPhoto}" >Dodaj zdjęcie</Button>
<Button Canvas.Left="250" Canvas.Bottom="20" Height="40" Width="80" FontSize="24">Zapisz</Button>
</Canvas>
</Window>
In Window3.cs I haven't added any code.
RylayCommand.cs
public class RelayCommand : ICommand
{
private readonly Func<bool> _canExecute;
private readonly Action _execute;
public RelayCommand(Action execute)
: this(execute, null)
{
}
public RelayCommand(Action execute, Func<bool> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (_canExecute != null)
CommandManager.RequerySuggested -= value;
}
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute();
}
public void Execute(object parameter)
{
_execute();
}
}
I post it as an answer because I couldn't add it to the question.
On MainWindow you have bound the SelectedItem to "Item". If you look at the Output window (if you are using Visual Studio) when you are running the program, you find following error:
System.Windows.Data Error: 40 : BindingExpression path error: 'Index' property not found on 'object' ''ViewModel'
XAML is a very quite beast, so look there first if the bindings doesn't work.
You should bind the SelectedItem to the SelectedPerson in the ViewModel.
SelectedItem="{Binding SelectedPerson}"
To get the window3 to work, you need to set the DataContext to the main windows DataContext (the view model).
Window3 window3 = new Window3();
window3.DataContext = this.DataContext;
window3.Show();
So I've been trying to figure this out for 3 days and I just can't seem to find a solution.
This is what I am trying to achieve.
I have a simple WPF project with a RichTextBox in it.
What my application is doing is that it acts like a CMD.
What I want to do now is that I want to change the message it saved when I press enter, I want the previous message to change color.
Here is a GIF showing what it looks like
https://i.imgur.com/srszUKG.gifv
I tried binding the Foreground of the TextBox inside the DataTemplate but that just made it to where the text wouldnt even show up.
<DataTemplate>
<TextBlock Text="{Binding Path=.}" Foreground="White" Name="SavedBlocks" FontFamily="Consolas"/>
</DataTemplate>
So what are my options here, I essentially want to change the color of the text depending on how long the message is.
XAML
<Window x:Class="WpfApp1Eh.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:WpfApp1Eh"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ScrollViewer Name="Scroller" Margin="0" Background="Black">
<StackPanel>
<ItemsControl ItemsSource="{Binding ConsoleOutput, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}" Foreground="White" Name="SavedBlocks" FontFamily="Consolas"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBox Text="{Binding ConsoleInput, Mode=TwoWay}" Background="Black" Foreground="White" FontFamily="Consolas" Name="InputBlock" BorderBrush="{x:Null}" SelectionBrush="{x:Null}" />
</StackPanel>
</ScrollViewer>
</Grid>
</Window>
main.cs
public partial class MainWindow : Window
{
ConsoleContent dc = new ConsoleContent();
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded1;
DataContext = dc;
}
private void MainWindow_Loaded1(object sender, RoutedEventArgs e)
{
InputBlock.KeyDown += InputBlock_KeyDown;
InputBlock.Focus();
}
void InputBlock_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
dc.ConsoleInput = InputBlock.Text;
dc.RunCommand();
InputBlock.Focus();
Scroller.ScrollToBottom();
}
}
}
ConsoleContent.cs
public class ConsoleContent : INotifyPropertyChanged
{
string consoleInput = string.Empty;
ObservableCollection<string> consoleOutput = new ObservableCollection<string>() { "Console Emulation Sample..." };
public string ConsoleInput
{
get
{
return consoleInput;
}
set
{
consoleInput = value;
OnPropertyChanged("ConsoleInput");
}
}
public ObservableCollection<string> ConsoleOutput
{
get
{
return consoleOutput;
}
set
{
consoleOutput = value;
OnPropertyChanged("ConsoleOutput");
}
}
public void RunCommand()
{
ConsoleOutput.Add(ConsoleInput);
//myBrush = Brushes.Orange;
// do your stuff here.
ConsoleInput = String.Empty;
}
private System.Windows.Media.Brush _foregroundColor = System.Windows.Media.Brushes.DarkSeaGreen;
public System.Windows.Media.Brush ForegroundColor
{
get { return _foregroundColor; }
set
{
_foregroundColor = value;
OnPropertyChanged("ForegroundColor");
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propertyName)
{
if (null != PropertyChanged)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Change the type of consoleOutput from ObservableCollection<string> to ObservableCollection<YourType> where YourType is a class that represents a line of input with a text string and a Foreground Brush:
public class YourType : INotifyPropertyChanged
{
private string _text;
public string Text
{
get { return _text; }
set { _text = value; OnPropertyChanged("Text"); }
}
private Brush _foreground;
public Brush Foreground
{
get { return _foreground; }
set { _foreground = value; OnPropertyChanged("Foreground"); }
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propertyName)
{
if (null != PropertyChanged)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Bind to the properties of this class in your XAML:
<ScrollViewer Name="Scroller" Margin="0" Background="Black">
<StackPanel>
<ItemsControl ItemsSource="{Binding ConsoleOutput, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}"
Foreground="{Binding Foreground}"
Name="SavedBlocks" FontFamily="Consolas"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBox Text="{Binding ConsoleInput, Mode=TwoWay}" Background="Black" Foreground="White" FontFamily="Consolas" Name="InputBlock" BorderBrush="{x:Null}" SelectionBrush="{x:Null}" />
</StackPanel>
</ScrollViewer>
You can then set the Foreground property of each individual item in the source collection:
public void RunCommand()
{
ConsoleOutput.Add(new YourType { Text = ConsoleInput, Foreground = Brushes.Orange } );
ConsoleInput = String.Empty;
}
I have been struggling with this for a day or so, can't figure out what I'm doing wrong here. I want to be able to select any tab in my observable collection of tabs, and I want my selection to be visible in the UI. I have tried SelectedIndex and SelectedItem. I can see that my Properties are set but my tabs are not selected, nothing happens in the UI. Here is my code:
MainWindow.xaml
<Window x:Class="WpfApplication5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:WpfApplication5"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<ViewModel xmlns="clr-namespace:WpfApplication5" />
</Window.DataContext>
<StackPanel>
<Button Content="Select Tab Index 0" Click="Button_Click_0"/>
<Button Content="Select Tab Index 1" Click="Button_Click_1"/>
<Label Content="{Binding SelectedIndex, UpdateSourceTrigger=PropertyChanged}" />
<TabControl ItemsSource="{Binding Tabs}" SelectedIndex="{Binding SelectedIndex, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<uc:TabContent Content="{Binding Content}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</StackPanel>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click_0(object sender, RoutedEventArgs e)
{
var viewModel = (ViewModel)DataContext;
viewModel.SelectedIndex = 0;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
var viewModel = (ViewModel)DataContext;
viewModel.SelectedIndex = 1;
}
}
ViewModel.cs
class ViewModel
{
private int _selectedIndex = 0;
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Tab> _tabCollection = new ObservableCollection<Tab>();
public ViewModel()
{
Tabs.Add(new Tab { Header = "Tab1", Content = new WpfApplication5.TabContent() });
Tabs.Add(new Tab { Header = "Tab2", Content = new WpfApplication5.TabContent() });
}
public ObservableCollection<Tab> Tabs
{
get { return _tabCollection; }
}
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
_selectedIndex = value;
NotifyPropertyChanged("SelectedIndex");
}
}
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Tab.cs
class Tab
{
public UserControl Content { get; set; }
public string Header { get; set; }
}
TabContent.xaml
<Grid>
<Label Content="Hello World!" />
</Grid>
Your ViewModel class doesn't implement the INotifyPropertyChanged interface:
class ViewModel : INotifyPropertyChanged
{
...
That's your issue.
I have a collection of Patients which I set in my ComboBox, whenever I ran and test the form, it seems fine, but whenever I update a record or add another one (from another form), the ComboBox doesn't get updated. I can do a remedy to this by using the code behind and IContent interface but I like to reduce the use of code behind as much as possible. Can you pinpoint to me what markup or code that is lacking?
Here is my Grid Markup (I omitted the RowDefinitions and ColumnDefinitions)
<UserControl.Resources>
<common:ImageSourceConverter x:Key="ToImageSourceConverter" />
<businessLogic:PatientMgr x:Key="PatientsViewModel" />
<ObjectDataProvider x:Key="ItemsSource" ObjectInstance="{StaticResource PatientsViewModel}" />
</UserControl.Resources>
<Grid x:Name="DetailsGrid" DataContext="{StaticResource ItemsSource}">
<Border Grid.Row="1" Grid.RowSpan="8" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Turquoise" BorderThickness="2">
<Image x:Name="InsideButtonImage"
VerticalAlignment="Center"
HorizontalAlignment="Center"
StretchDirection="Both" Stretch="Uniform"
Source="{Binding ElementName=FullNameComboBox, Path=SelectedItem.PictureId, Converter={StaticResource ToImageSourceConverter}, UpdateSourceTrigger=PropertyChanged}"
/>
</Border>
<TextBox x:Name="IdTextBox" Text="{Binding ElementName=FullNameComboBox, Path=SelectedItem.Id, UpdateSourceTrigger=PropertyChanged}" Grid.Row="0" Grid.Column="1" Visibility="Collapsed"/>
<ComboBox x:Name="FullNameComboBox" Grid.Row="10" Grid.Column="1" IsEditable="True"
ItemsSource="{Binding Path=ComboBoxItemsCollection, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath = "FullName"
SelectedIndex="0"
SelectionChanged="FullNameComboBox_OnSelectionChanged"
/>
<TextBox x:Name="GenderTextBox" Text="{Binding ElementName=FullNameComboBox, Path=SelectedItem.GenderName, UpdateSourceTrigger=PropertyChanged}" Grid.Row="11" Grid.Column="1" IsReadOnly="True" IsReadOnlyCaretVisible="True"/>
</Grid>
Here is my BusinessLogicLayer
public class PatientMgr :INotifyPropertyChanged
{
#region Fields
private readonly PatientDb _db;
private Patient _entity;
private List<Patient> _entityList;
private ObservableCollection<Patient> _comboBoxItemsCollection;
private Patient _selectedItem;
#endregion
#region Properties
public Patient Entity
{
get { return _entity; }
set
{
if (Equals(value, _entity)) return;
_entity = value;
OnPropertyChanged();
}
}
public List<Patient> EntityList
{
get { return _entityList; }
set
{
if (Equals(value, _entityList)) return;
_entityList = value;
OnPropertyChanged();
}
}
public ObservableCollection<Patient> ComboBoxItemsCollection
{
get { return _comboBoxItemsCollection; }
set
{
if (Equals(value, _comboBoxItemsCollection)) return;
_comboBoxItemsCollection = value;
OnPropertyChanged();
}
}
public Patient SelectedItem
{
get { return _selectedItem; }
set
{
if (Equals(value, _selectedItem)) return;
_selectedItem = value;
OnPropertyChanged();
}
}
#endregion
#region Constructor
public PatientMgr()
{
_db = new PatientDb();
Entity = new Patient();
EntityList = new List<Patient>();
Parameters = new Patient();
ComboBoxItemsCollection = new ObservableCollection<Patient>(_db.RetrieveMany(Entity));
SelectedItem = ComboBoxItemsCollection[0];
}
#endregion
public List<Patient> RetrieveMany(Patient parameters)
{
return _db.RetrieveMany(parameters);
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Because ItemsSource used statically, your ComboBox doesn't get notified when it's source changed. Try using an instance of PatientMgr as your UserControl.DataContext like this:
public partial class YourUserControl: UserControl
{
private PatientMgr PatientsViewModel;
public YourUserControl()
{
InitializeComponent();
PatientsViewModel = new PatientMgr();
DataContext = PatientsViewModel;
}
public void AddComboItem(Patient item)
{
PatientsViewModel.ComboBoxItemsCollection.Add(item);
}
public void UpdateComboItem(Patient item)
{
//your update code
}
After removing static bindings, your XAML should looks like this:
<UserControl.Resources>
<common:ImageSourceConverter x:Key="ToImageSourceConverter" />
</UserControl.Resources>
<Grid x:Name="DetailsGrid"
<Border Grid.Row="1" Grid.RowSpan="8" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Turquoise" BorderThickness="2">
<Image x:Name="InsideButtonImage" ...
Since ComboBoxItemsCollection is an ObservableCollection, it notifies UI on add/remove automatically, however you have to write proper update functionality in order to force UI to refresh.
EDIT:
To implement an update method by taking advantage of Editable feature of ComboBox you could follow these steps:
Defined an integer property to track the last valid index (index != -1) of combo-box selected item and bind it to FullNameComboBox.SelectedIndex.
Defined a string property that represents the text value of combo-box selected item and bind it to FullNameComboBox.Text (the binding should trigger on LostFocus so the changes only accepted when user finished typing).
Find and remove ComboBoxItemsCollection.OldItem (find it by last valid index) and insert ComboBoxItemsCollection.ModifiedItem when FullNameComboBox.Text changes. Using remove and insert instead of assigning (OldItem = ModifiedItem;) will force UI to update.
In PatientMgr Code:
public int LastValidIndex
{
get { return _lastIndex; }
set
{
if (value == -1) return;
_lastIndex = value;
OnPropertyChanged();
}
}
public string CurrentFullName
{
get
{
return SelectedItem.FullName;
}
set
{
var currentItem = SelectedItem;
ComboBoxItemsCollection.RemoveAt(LastValidIndex);
currentItem.FullName = value;
ComboBoxItemsCollection.Insert(LastValidIndex, currentItem);
SelectedItem = currentItem;
}
}
In UserControl.Xaml :
<ComboBox x:Name="FullNameComboBox" Grid.Row="1" IsEditable="True"
ItemsSource="{Binding Path=ComboBoxItemsCollection,
UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedItem}"
SelectedIndex="{Binding LastValidIndex}"
IsTextSearchEnabled="False"
Text="{Binding CurrentFullName, UpdateSourceTrigger=LostFocus}"
DisplayMemberPath = "FullName"
/>
I don’t like this:
public ObservableCollection<Patient> ComboBoxItemsCollection
{
get { return _comboBoxItemsCollection; }
set
{
if (Equals(value, _comboBoxItemsCollection)) return;
_comboBoxItemsCollection = value;
OnPropertyChanged();
}
}
Try this:
OnPropertyChanged(“ComboBoxItemsCollection”);
And, are you sure this equals is resolved right?
if (Equals(value, _comboBoxItemsCollection)) return;
Try to debug it …
Your problem has to do with that IsEditable="True" set on the ComboBox. Your bindings work fine for me.
Here is what i've just tried:
Simple Patient model with its FullName property:
public class Patient : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _FullName;
public string FullName
{
get { return _FullName; }
set
{
_FullName = value;
PropertyChanged(this, new PropertyChangedEventArgs("FullName"));
}
}
}
This is the XAML:
<Window x:Class="PatientsStack.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PatientsStack"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:PatientsMgr x:Key="PatientsViewModel"/>
<ObjectDataProvider x:Key="ItemsSource" ObjectInstance="{StaticResource PatientsViewModel}" />
</Window.Resources>
<Grid DataContext="{StaticResource ItemsSource}">
<StackPanel>
<ComboBox x:Name="FullNameComboBox" IsEditable="True"
ItemsSource="{Binding Path=ComboBoxItemsCollection, UpdateSourceTrigger=PropertyChanged}"
TextSearch.TextPath="FullName"
DisplayMemberPath="FullName"
SelectedItem="{Binding SelectedPatient}"
Text="{Binding SelectedPatient.FullName, Mode=TwoWay}"
VerticalAlignment="Top"
IsTextSearchEnabled="False"
SelectedIndex="0">
</ComboBox>
<Button Content="Change name" Command="{Binding ChangeNameCommand}" CommandParameter="{Binding ElementName=FullNameComboBox, Path=SelectedItem}"/>
<Button Content="Add patient" Command="{Binding AddPatientCommand}" CommandParameter="{Binding ElementName=FullNameComboBox, Path=SelectedItem}"/>
</StackPanel>
</Grid>
This two pieces right here did the job:
SelectedItem="{Binding SelectedFilter}"
Text="{Binding SelectedPatient.FullName, Mode=TwoWay}"
Without the Text binding, the updated worked, but you didn't see it unless you click the drop down.
As you see, i have 2 command to change one existing Patient's FullName or to add a new one. Both work as expected.
Here is the ViewModel for this:
public class PatientsMgr : INotifyPropertyChanged
{
private ObservableCollection<Patient> _ComboBoxItemsCollection;
public ObservableCollection<Patient> ComboBoxItemsCollection
{
get
{
return _ComboBoxItemsCollection;
}
set
{
_ComboBoxItemsCollection = value;
PropertyChanged(this, new PropertyChangedEventArgs("ComboBoxItemsCollection"));
}
}
private Patient _SelectedPatient;
public Patient SelectedPatient
{
get { return _SelectedPatient; }
set
{
_SelectedPatient = value;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedPatient"));
}
}
public ICommand ChangeNameCommand { get; set; }
public ICommand AddPatientCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public PatientsMgr()
{
ComboBoxItemsCollection = new ObservableCollection<Patient>();
ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient1" });
ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient2" });
ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient3" });
ChangeNameCommand = new RelayCommand<Patient>(ChangePatientName);
AddPatientCommand = new RelayCommand<Patient>(AddPatient);
}
public void ChangePatientName(Patient patient)
{
patient.FullName = "changed at request";
}
public void AddPatient(Patient p)
{
ComboBoxItemsCollection.Add(new Patient() { FullName = "patient added" });
}
}
I am posting my RelayCommand too, but i am sure you have it defined for your actions:
public class RelayCommand<T> : ICommand
{
public Action<T> _TargetExecuteMethod;
public Func<T, bool> _TargetCanExecuteMethod;
public RelayCommand(Action<T> executeMethod)
{
_TargetExecuteMethod = executeMethod;
}
public bool CanExecute(object parameter)
{
if (_TargetExecuteMethod != null)
return true;
return false;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
T tParam = (T)parameter;
if (_TargetExecuteMethod != null)
_TargetExecuteMethod(tParam);
}
}
First: I am new to MVVM and WPF.
I am trying to create a little application with a tabbed user interface. Users can create products and storage locations, using a button which should open a new TabItem.
My code in the view looks like this:
<TabControl ItemsSource="{Binding Workspaces}"
IsSynchronizedWithCurrentItem="True"
Margin="3"
DockPanel.Dock="Top">
<TabControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding DisplayName}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
and the View Model is this:
ObservableCollection<WorkspaceViewModel> _workspaces;
public ObservableCollection<WorkspaceViewModel> Workspaces
{
get
{
if (_workspaces == null)
{
_workspaces = new ObservableCollection<WorkspaceViewModel>();
}
return _workspaces;
}
set
{
_workspaces = value;
}
}
public void AddProduct(object obj)
{
Workspaces.Add(new ProductViewModel());
}
Various other buttons add different ViewModels to the Workspaces Collection.
I have defined multiple Data Template (one for each ViewModel). Here is one:
<DataTemplate DataType="{x:Type vm:ProductViewModel}">
<vw:ProductView />
</DataTemplate>
The WorkspaceViewModel is this:
namespace Inventory.Desktop.ViewModels
{
public abstract class WorkspaceViewModel : INotifyPropertyChanged
{
#region Events and EventHandlers
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
and eg the ProductViewModel
namespace Inventory.Desktop.ViewModels
{
public class ProductViewModel: WorkspaceViewModel
{
private Product _product;
private string _displayName;
public string DisplayName
{
get
{
if (String.IsNullOrEmpty(_displayName))
{
return "Neues Produkt";
} else
{
return _displayName;
}
}
set
{
_displayName = value;
NotifyPropertyChanged("DisplayName");
}
}
#region Public Properties
public Product Product
{
get
{
return _product;
}
set
{
_product = value;
NotifyPropertyChanged("Product");
}
}
public string Title
{
get
{
return _product.Title;
}
set
{
_product.Title = value;
NotifyPropertyChanged("Title");
}
}
public string ScanCode
{
get
{
return _product.ScanCode;
}
set
{
_product.ScanCode = value;
NotifyPropertyChanged("ScanCode");
}
}
public string Manufacturer
{
get
{
return _product.Manufacturer;
}
set
{
_product.Manufacturer = value;
NotifyPropertyChanged("Manufacturer");
}
}
public string ManufacturerNumber
{
get
{
return _product.ManufacturerNumber;
}
set
{
_product.ManufacturerNumber = value;
NotifyPropertyChanged("ManufacturerNumber");
}
}
public string Description
{
get
{
return _product.Description;
}
set
{
_product.Description = value;
NotifyPropertyChanged("Description");
}
}
#endregion
#region Commands
private ICommand _saveCommand;
public ICommand SaveCommand
{
get
{
return _saveCommand;
}
set
{
_saveCommand = value;
}
}
#endregion
#region Command Executions
public void Save(object obj)
{
using (var db = new InvContext())
{
db.Products.Attach(Product);
db.Entry(Product).State = Product.ProductId == 0 ?
EntityState.Added : EntityState.Modified;
db.SaveChanges();
}
MessageBox.Show("Product saved: " + Product.Title);
}
#endregion
#region Constructors
public ProductViewModel()
{
if (_product == null)
{
_product = new Product();
}
SaveCommand = new RelayCommand(new Action<object>(Save));
}
#endregion
}
}
Here the ProductView.xaml view:
<UserControl x:Class="Inventory.Desktop.Views.ProductView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="450">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" FlowDirection="RightToLeft">
<Button Name="SaveProductButton" Command="{Binding SaveCommand}" Content="Speichern" Margin="3" BorderThickness="0">
</Button>
</StackPanel>
<StackPanel DockPanel.Dock="Top" VerticalAlignment="Stretch">
<Label Content="Scan Code" />
<TextBox Text="{Binding Path=ScanCode}" HorizontalAlignment="Stretch" Margin="3" Padding="3" Height="50" TextAlignment="Right">
<TextBox.Background>
<ImageBrush ImageSource="..\Images\Barcode32.png" AlignmentX="Left" Stretch="None" />
</TextBox.Background>
</TextBox>
<Label Content="Bezeichnung" />
<TextBox Text="{Binding Path=Title, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="3" />
<Label Content="Hersteller" />
<TextBox Text="{Binding Path=Manufacturer, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="3" />
<Label Content="Hersteller Nummer" />
<TextBox Text="{Binding Path=ManufacturerNumber, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="3" />
<Label Content="Beschreibung / Information" />
<TextBox Text="{Binding Path=Description, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="3" />
</StackPanel>
</DockPanel>
</UserControl>
and here the code-behind ProductView.xaml.cs:
namespace Inventory.Desktop.Views
{
/// <summary>
/// Interaktionslogik für ProductView.xaml
/// </summary>
public partial class ProductView : UserControl
{
ProductViewModel _productModel = new ProductViewModel();
public ProductView()
{
InitializeComponent();
base.DataContext = _productModel;
}
}
}
What's currently working:
When I click a button, I got a new TabItem displaying the correct view and all commands work correctly.
What's not working:
When I open a TabItem, enter some information, and then I open another TabItem with a different ViewModel, switching the focus to the new TabItem and then back to the original oen, then all entered information are gone (object is null).
When I open a TabItem, enter some information, and then I open another TabItem with the same ViewModel, then both TabItems show the the same information.
When I add a new TabItem, it doesn't get focus.
I am totally lost and I hope you can tell me what I am doing wrong.
Best
Stefan
Have a property on your ViewModel to store the reference to current/selected tab
public WorkspaceViewModel SelectedTab
{
get { return _selectedTab; }
set
{
_selectedTab = value;
RaisePropertyChanged(() => SelectedTab);
}
}
and bind this to SelectedItem property on TabControl.
<TabControl ItemsSource="{Binding Workspaces}"
SelectedItem="{Binding SelectedTab, Mode=TwoWay}"
Margin="3"
DockPanel.Dock="Top">
<TabControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding DisplayName}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
And finally, you want to update SelectedTab property whenever you are adding a new tab. Modify your AddProduct like this:
public void AddProduct(object obj)
{
var workspace = new ProductViewModel();
Workspaces.Add(workspace);
SelectedTab = workspace;
}