Setting DataContext in XAML C# WPF [duplicate] - c#

This question already has answers here:
Why does WPF support binding to properties of an object, but not fields?
(2 answers)
Closed 2 years ago.
So I think the title is a little confusing, but I couldn't think of anything better. Here's what I have:
CueSheet.cs
class CueSheet : INotifyPropertyChanged
{
private ObservableCollection<Track> _tracks;
public ObservableCollection<Track> Tracks
{
get => _tracks;
set
{
if (_tracks != value)
_tracks = value;
NotifyPropertyChanged();
}
}
private string _path;
public string Path
{
get => _path;
private set
{
if (_path != value)
_path = value;
NotifyPropertyChanged();
}
}
... other code...
}
public class Track : INotifyPropertyChanged
{
... INotifyPropertyChanged event handler ...
private string _title;
public string Title
{
get => _title;
set
{
_title = value;
NotifyPropertyChanged();
}
}
... 2 more properties (Frame and Index) coded as above ...
}
MainWindow.xaml.cs
public partial class MainWindow : INotifyPropertyChanged
{
CueSheet cueSheet;
public MainWindow()
{
InitializeComponent();
cueSheet = new CueSheet();
this.DataContext = this;
// DataContext Bindings
txtCueFilePath.DataContext = cueSheet;
dgCueTracks.DataContext = cueSheet;
}
...Other Code...
}
cueSheet is populated later in the code by reading from a file.
MainWindow.xaml
... other code ...
<TextBox Name="txtCueFilePath"
Grid.Row="1" Grid.Column="1"
IsReadOnly="False"
Background="LightGray"
Text="{Binding Path=Path, Mode=OneWay}"/>
... other code ...
<DataGrid Grid.Column="2" Name="dgCueTracks2"
BorderBrush="Black" BorderThickness="1"
Margin="0 5 5 5"
AutoGenerateColumns="False"
ItemsSource="{Binding Path=Tracks}">
<DataGrid.Columns>
<DataGridTextColumn Header="Title" Binding="{Binding Title}"/>
<DataGridTextColumn Header="Index" Binding="{Binding Index}"/>
<DataGridTextColumn Header="Frame" Binding="{Binding Frame}"/>
</DataGrid.Columns>
</DataGrid>
... other code ...
Ok, so now that you see what I'm working with, I will say that this all works just fine. However I want to have all the binding taken care of in one place, and id like that place to be the XAML file. But I can't work out how to set the DataContext from inside the MainWindow.xaml. I see there is a Datacontext property for DataGrid and TextBox, but I can't figure out how to use them.

If you want any XAML element to refer to itself, you can use {RelativeSource}. To have the Window set itself as its own DataContext, just add the following to the opening tag of the Window:
DataContext="{Binding RelativeSource={RelativeSource Self}}"

It appears that you cant bind a DataContext to a field, it has to be a property. Changing cueSheet to a property allowed me to bind it as the DataContext of my TextBox and DataGrid.
While
DataContext="{Binding cueSheet}"
didn't work because it was just a field, changing it to a property worked
DataContext="{Binding CueSheet}"
and I no longer need txtCueFilePath.DataContext = cueSheet; and dgCueTracks.DataContext = cueSheet; in the code-behind.

Related

How to properly bind Checkbox property

I am working on a wpf application and I am dealing with checkboxes now
The problem when I set the Ischecked property to "True" like this:
<CheckBox Visibility="{Binding checkVisibility}" IsChecked="true"
IsEnabled="True"></CheckBox>
I get my checkbox checked
But when I try to bind a booléan property that it's value is "true" i get my checkbox always unchecked
I can't see where is the proplem
this is my xaml
<CheckBox Visibility="{Binding checkVisibility}" IsChecked="{Binding Path=test,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" IsEnabled="True"></CheckBox>
this is my property
public bool _test = true;
public bool test {
get {
return _test;
}
set {
if (_test == value) {
return;
}
_test = value;
RaisePropertyChanged("test");
}
}
I want my checkbox to reflect my property value and vice versa but it's not the case as my checkbox is always unchecked
I am missing something?
Edit
Here is my VM:
namespace X{
public class MyViewModel :
{
public MyViewModel()
{
TestCheckbox();
}
#Region Properties
public bool _test1 = true;
public bool test1
{
get
{
return _test1;
}
set
{
if (_test1 == value)
{
return;
}
_test1 = value;
RaisePropertyChanged("test1");
}
}
ObservableCollection<Output> _output_List;
public ObservableCollection<Output> output_List
{
get { return _output_List; }
set
{
if (_output_List == value) return;
_output_List = value;
RaisePropertyChanged("output_List");
}
}
#EndRegion
#Region ButtonCommand
private RelayCommand _SetOutputPropertiesCommand;
public RelayCommand SetOutputPropertiesCommand => _SetOutputPropertiesCommand
?? (_SetOutputPropertiesCommand = new RelayCommand(
() =>
{
foreach (Output item in output_List)
{
item.myvalue=Test2;
}
}
}));
#EndRegion
#Region Method
public void TestCheckbox()
{
Console.Writeline(Test2);
}
#EndRegion
}
public class Output
{
public string label { get; set; }
public string type { get; set; }
public bool myvalue { get; set; }
public bool Test2 { get; set; }
[System.ComponentModel.DataAnnotations.Schema.NotMapped]
[JsonIgnore]
public string checkVisibility { get; set; }
}
}
My Xaml : my checkboxes are integrated in a DataGrid view
<DataGrid x:Name ="GridO" Style="{x:Null}"
ItemsSource= "{Binding output_List,UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="False" CellStyle="{StaticResource Body_Content_DataGrid_Centering}"
Margin="5,0" IsReadOnly="True" SelectionMode="Single" RowHeight="50" Height="Auto">
<DataGrid.Columns>
<DataGridTextColumn Width="40*" Binding="{Binding label}">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="Input"></TextBlock>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
<DataGridTextColumn Width="40*" Binding="{Binding type}">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text = "Type"></TextBlock>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
<DataGridTemplateColumn Width="20*" >
<DataGridTemplateColumn.Header>
<TextBlock Text="Value" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Margin="20,0,0,0" Visibility="{Binding checkVisibility }" IsChecked="{Binding Test2,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" IsEnabled="True"></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button x:Name="Valid_output" Cursor="Hand" Background="Transparent" Height="55" Width="140" Margin="0,0,20,0" Command="{Binding SetOutputPropertiesCommand}" >
The bind is Working with "Test2" and not working with "Test1" which not in the same class as Test2
NB: the Button command(which is in the same class as "Test1") is working well
My DataTemplate: are in the App.xaml
xmlns:v="clr-namespace:X.View"
xmlns:vm="clr-namespace:X"
<DataTemplate DataType="{x:Type vm:MyViewModel}">
<v:MyWindow/>
I see the main problem in the DataGrid. You set ItemsSource to a collection, so for every row in the DataGrid the data source is one item in that collection. Not your MyViewModel.
This is the reason, why the Test2 is working - it is in the class which instances are in the collection.
You could try to add CheckBox to the window without DataGrid and bind the Test1 - it should work.
If you really want to use some property from your MyViewModel, you can, but it is not that easy. You have to have there something like:
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:YourNamespace"
<!--...something...-->
<DataGrid.Resources>
<local:BindingProxy x:Key="Proxy" Data="{Binding}" />
</DataGrid.Resources>
<!--...something...-->
And then your binding in the DataGrid will look like this:
IsChecked="{Binding Data.Test1, Source={StaticResource Proxy}}"
Of course I don't know the exact settings of your application so you will have to complete/change the code according to them.
I suggest you to add a ViewModelBase class, like that
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName =
null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Extends your ViewModel with this ViewModelBase class and implements your property
private bool myProperty;
public bool MyProperty { get; set; OnPropertyChanged(); }
Then you just have to bind on your property
<CheckBox IsChecked="{Binding MyProperty}"></CheckBox>
EDIT: To set your ViewModel as DataContext for your View you can set it from the code
MyView.DataContext = new MyViewModel();
or from the view, in the Window/User Control
<Window x:Class="MyApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyApplication"
Title="MainWindow" Height="350" Width="525"
DataContext="local.MyViewModel">

WPF: Two way binding does not set source

My two way binding is just working from the source to the TextBox - I can see the default value in the TextBox and even the new value when I change it from the code-behind, but when I change the Text in the TextBox the value doesn't get updated in the Model, even after the TextBox loses focus. The DataContext is also set.
Version.Set doesn't even get called - tested by setting a breakpoint.
XAML:
<DataGrid ItemSource="{Binding Issues}">
<DataGrid.RowDetailsTemplate>
<TextBox Text="{Binding Path=TestReport.Version, Mode=TwoWay}"/>
</DataGrid.RowDetailsTemplate>
</DataGrid>
Models:
public class TestIssue
{
public JiraIssue Issue { get; set; }
public TestReport TestReport { get; set; }
}
public class TestReport : INotifyPropertyChanged
{
private string version = "Defalut Value";
public string Version
{
get => this.version;
set
{
if (value == this.version) return;
this.version = value;
this.OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Code Behind:
public partial class MainWindow : Window
{
public ObservableCollection<TestIssue> Issues { get; set; } = new ObservableCollection<TestIssue>();
public MainWindow()
{
this.DataContext = this;
this.InitializeComponent();
}
}
EDIT: Explicitly setting the UpdateSourceTrigger works, even setting it to FocusLost, which confuses me even more.
First, your XAML code is incorrect and it should be like this:
<DataGrid ItemsSource="{Binding Issues}">
<DataGrid.RowDetailsTemplate>
<ItemContainerTemplate >
<TextBox Text="{Binding Path=TestReport.Version, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</ItemContainerTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
You cannot place <TextBox/> element inside <DataGrid.RowDetailsTemplate> directlyand it should be placed inside <ItemContainerTemplate >.
To update the TextBox you need to tell TextBox element when it should update its value when the source changes by adding UpdateSourceTrigger=PropertyChanged to the binding script as shown in the above code.

Cannot edit members in WPF DataGrid DataGridTextColumn

I'm working on a small WPF application and I would like to present a list of strings to the user which they may edit, add to, or delete from.
The first thing I've done is create a base class with view model change notification:
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
After this, I created a wrapper for strings for binding/change notification:
public class StringViewModel : BaseViewModel
{
private string value;
public string Value
{
get { return this.value; }
set
{
if (value == this.value) return;
this.value = value;
this.RaisePropertyChanged(nameof(this.Value));
}
}
}
Then I have a view model which uses this class (I have elided other members which are not relevant):
public class UserSettingsDataViewModel : BaseViewModel
{
private ObservableCollection<StringViewModel> blacklistedFiles;
public ObservableCollection<StringViewModel> BlacklistedFiles
{
get { return this.blacklistedFiles; }
set
{
if (Equals(value, this.blacklistedFiles)) return;
this.blacklistedFiles = value;
this.RaisePropertyChanged(nameof(this.BlacklistedFiles));
}
}
}
Finally, I have included this in my XAML for the screen in question:
<WrapPanel>
<Label>Blacklisted files</Label>
<DataGrid ItemsSource="{Binding Data.BlacklistedFiles}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Value}" Header="File name" />
<DataGridTemplateColumn Header="Remove">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Remove" Command="Delete" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</WrapPanel>
Everything works, except I cannot edit the values of new or existing members. I can click on the cell and get it to enter editing mode, but pressing keys does nothing (except I do seem to be able to add or remove spaces). I feel like there must be a straightforward fix for this but it is eluding me.
This turns out to be because this WPF form exists inside a Windows Forms application. The steps to resolve were:
Add a reference to the WindowsFormsIntegration library.
In the class that creates the window, add the using statement using Systems.Windows.Forms.Integration.
Call ElementHost.EnableModelessKeyboardInterop on the window.
I have to give some credit to this answer, which helped me figure out what was wrong.

Bind ObservableCollection to ComboBox in DataGrid is not working

I have ready many articles which covers binding Observable Collection to ComboBox but I stil can't figure it out why my collection isn't bindng to ComboBox placed in DataGrid.
My Model
class DDV
{
public DDV(int ID, string Naziv)
{
_ID = ID;
_Naziv = Naziv;
}
private int _ID;
public int ID
{
get { return _ID; }
set { _ID = value; }
}
private string _Naziv;
public string Naziv
{
get { return _Naziv; }
set { _Naziv = value; }
}
My ViewModel:
class ArtikliStoritveViewModel : INotifyPropertyChanged
{
public ArtikliStoritveViewModel()
{
DDVData.Add(new DDV(1, "Ceka"));
DDVData.Add(new DDV(2, "Zeka"));
}
private ObservableCollection<DDV> _DDVData = new ObservableCollection<DDV>();
public ObservableCollection<DDV> DDVData
{
get
{
return this._DDVData;
}
set
{
_DDVData = value;
}
}
DataContext:
<Window.DataContext>
<local:ArtikliStoritveViewModel/>
</Window.DataContext>
Binding:
<DataGridTemplateColumn Header="Test">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox
x:Name="cmbDDV"
ItemsSource="{Binding DDVData}"
DisplayMemberPath="Naziv"
/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
I have observable collection in View model:
private ObservableCollection<DDV> _DDVData = new ObservableCollection<DDV>();
public ObservableCollection<DDV> DDVData
{
get
{
return this._DDVData;
}
set
{
_DDVData = value;
}
}
My DataGrid Bind to tihs View Model:
<DataGrid ItemsSource="{Binding Path=ArtikliStoritveData}
In constructor in View model I bind to collection:
DDV _ddv;
public ArtikliStoritveViewModel()
{
_ddv = new DDV { ID = 1, Naziv = "Ceka" };
DDVData.Add(_ddv);
}
So everything must stay in this View Model.
What else I have to do to make this work. Currently nothing is binding.
Regards,
Igor
Your mistake is in your data model. What is available to your DataTemplate is an instance of your DDV class and that has no collection property named DDVData to data bind to. Instead, you need to add a collection to your DDV class that you can data bind to the ComboBox in each row of the DataGrid and then data bind the collection property named DDVData from the view model to the DataGrid.ItemsSource property:
<DataGrid ItemsSource="{Binding CollectionPropertyInViewModel}">
...
<DataGridTemplateColumn Header="Test">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="cmbDDV"
ItemsSource="{Binding CollectionPropertyInModelClass}"
DisplayMemberPath="Naziv" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
...
</DataGrid>
Incidentally, if you had looked in your Output Window in Visual Studio, you should have seen some errors saying something like there is no property named DDVData found in object DDV or something like that... helpful clues.
UPDATE >>>
I updated the code above to make it clearer. With your information that you just provided, you'd need to do this:
<DataGrid ItemsSource="{Binding DDVData}">
...
<DataGridTemplateColumn Header="Test">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="cmbDDV"
ItemsSource="{Binding ArtikliStoritveData}"
DisplayMemberPath="Naziv" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
...
</DataGrid>
This will work when:
The DataGrid is in the Window that has the ArtikliStoritveViewModel instance set as its DataContext
The DDVData property is in the ArtikliStoritveViewModel
The DDV class has a collection property declared in it named ArtikliStoritveData
If that is not the case, then this won't work. You said I have some other Observable Collection in Model View, so I can not change it to DDVDData in DDV model... I have no idea what you mean by Model View, but if you set the Window.DataContext to an instance of you ArtikliStoritveViewModel, the that is what it has access to. So, you'll either need to add the collection into the DDV class, or the ArtikliStoritveViewModel class.

Observable Dictionary and updating bindings

I've started using WPF recently and I have two problems with bindings..
I'm using this ObservableDictionary.
When i bind it to TextBox it works flawlessly but i have a problem with DataGrid:
My Button Bindings:
Text="{Binding AdiDictionary[AssetName], Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
My DataGrid Bindings:
<DataGrid Grid.Row="1" Margin="5" Name="AdiDataGrid" Background="Transparent" IsReadOnly="True" ItemsSource="{Binding AdiDictionary, Mode=OneWay}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Meta-Data Name" Binding="{Binding Path=Key}" />
<DataGridTextColumn Header="Meta-Data Attribute" Binding="{Binding Path=Value}" Width="1*"/>
</DataGrid.Columns>
</DataGrid>
In my understanding it works like this because ObservableDictionary.Value doesn't implement INotifyPropertyChanged, however I tried multiple solutions and i cannot make it work:(
Second thing: User should have a possibility of loading settings file.
void LoadAdiSettingsFileExecute()
{
var loadDialog = new Microsoft.Win32.OpenFileDialog();
loadDialog.DefaultExt = ".txt";
loadDialog.Filter = "Txt files (*.txt)|*.txt";
if ((bool) loadDialog.ShowDialog())
{
var textFile = AdiSettingsFile.ReadingSettingsFileToDictionary(loadDialog.FileName);
foreach (KeyValuePair<string, string> x in textFile)
{
AdiDictionary[x.Key] = x.Value;
}
RaisePropertyChanged("AdiDictionary");
}
}
bool CanLoadAdiSettingsFileExecute()
{
return true;
}
public ICommand LoadAdiSettingsFile {get {return new RelayCommand(LoadAdiSettingsFileExecute, CanLoadAdiSettingsFileExecute);}}
Unfortunately, while it works - when I debug to see AdiDictionary values they are all there, however it doesn't update any of the TextBoxes or DataGrid:(
Any help will be much appreciated:)
EDIT: Oh and one thing i forgot to add - when i tried loading file in constructor it worked in textbox and datagrid, so it's probably problem with Bindings.
EDIT 2:
Ok, so probably noob mistake - I didn't know that every TabItem is creating new instance of my ViewModel, and ViewModel constructor is creating new instance of my ObservableDictionary. So i changed it to something like this:
private static ObservableDictionary<string, string> _adiDictionary = new ObservableDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
And IT WORKS! However it's slow as hell.. When i change one value of dictionary i have to wait ~7 seconds for it to process everything. When i load a file that changes about 20 values of that dictionary i have to wait around a minute for it to process it. Do you know why?
Your ObservableDictionary.Value should implement INotifyPropertyChanged and then all will work magically.
This is code behind:
public partial class MainWindow : Window
{
public MyDictionary<string, string> Dic
{
get;
set;
}
public MainWindow()
{
InitializeComponent();
this.Dic = new MyDictionary<string, string>();
this.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.Dic["0"] = new Random().Next().ToString();
}
}
public class MyDictionary<TKey, TValue> : Dictionary<TKey, TValue>, INotifyPropertyChanged
{
public TValue this[TKey index]
{
get
{
return base[index];
}
set
{
base[index] = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(Binding.IndexerName));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
This is what I have in my XAML:
<StackPanel>
<TextBox Text="{Binding Path=Dic[0], Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="click me" Click="Button_Click"/>
</StackPanel>
Its easy to implement INotifyPropertyChanged in a class. Just fire the PropertyChanged event.
I think your binding source is in ths calss AdiDictionary not itself so
this code RaisePropertyChanged("AdiDictionary") will not notify your binding to update the
TextProperty as your except

Categories