Passing value from bound ViewModel to UserControl - c#

I am trying to create a conditional textbox usercontrol on which there will be a property that accepts
Condition (true|false, Binding)
FalseValue (true|false, Binding, StaticResource)
TrueValue (true|false, Binding, StaticResource)
The problem is my StaticResource and literal value is working good except for the binding.
<Grid Background="#FFBDBDBD" Margin="0,154,0,266">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="33*"></RowDefinition>
<RowDefinition Height="33*"></RowDefinition>
<RowDefinition Height="33*"></RowDefinition>
</Grid.RowDefinitions>
<userControls:ConditionalTextBox
Grid.Column="0"
Grid.Row="0"
Condition="{Binding Path=IsTrue, Mode=TwoWay}"
FalseValue="{StaticResource FalseValRes}"
TrueValue="{StaticResource TrueValRes}"
HorizontalAlignment="Left" Width="500">
</userControls:ConditionalTextBox>
<userControls:ConditionalTextBox
Grid.Column="0"
Grid.Row="1"
Condition="True"
FalseValue="{Binding FalseValueDefined}"
TrueValue="{Binding TrueValueDefined}"
HorizontalAlignment="Left" Width="500">
</userControls:ConditionalTextBox>
<userControls:ConditionalTextBox
Grid.Column="0"
Grid.Row="2"
Condition="False"
FalseValue="False Value (string)"
TrueValue="True Value (string)"
HorizontalAlignment="Left" Width="500">
</userControls:ConditionalTextBox>
</Grid>
with code behind
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.DataContext = new VMTest
{
IsTrue = true,
FalseValueDefined = "False Value (Binding)",
TrueValueDefined = "True Value (Binding)"
};
}
}
and this VM
public class VMTest : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isTrue;
public bool IsTrue
{
get { return isTrue; }
set
{
isTrue = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsTrue"));
}
}
private string trueValueDefined;
public string TrueValueDefined
{
get { return trueValueDefined; }
set
{
trueValueDefined = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("TrueValueDefined"));
}
}
public string falseValueDefined;
public string FalseValueDefined
{
get { return falseValueDefined; }
set
{
falseValueDefined = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("FalseValueDefined"));
}
}
}
as the result, StaticResource and literal value reached successfully to UserControl and maybe i missed something on bindings that it wont affect
Result
Any help would be appreciated.
TIA

I would start by looking at the datacontext resolution. Have you tried, just to make sure the datacontext is correct:
<userControls:ConditionalTextBox
x:Name="boundControl"
Grid.Column="0"
Grid.Row="1"
Condition="True"
FalseValue="{Binding FalseValueDefined}"
TrueValue="{Binding TrueValueDefined}"
HorizontalAlignment="Left" Width="500">
</userControls:ConditionalTextBox>
and
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.boundControl.DataContext = new VMTest
{
IsTrue = true,
FalseValueDefined = "False Value (Binding)",
TrueValueDefined = "True Value (Binding)"
};
}
}
It might also be helpful to examine the binding trace information. Try
"{Binding FalseValueDefined, PresentationTraceSources.TraceLevel=High}"
If you run your program and look at the visual studio debug output, you will get some debug rows describing WPF's attempts to resolve the binding.

Related

C#: Binding textbox to object attribute

I am new to object binding and I don' succeed to make it work.
I have a xaml window with the following textbox:
<Grid x:Name="gr_main" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="180,65,0,0" DataContext="{Binding currentproj}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Row="0" Grid.Column="2" x:Name="txt_localdir" Height="25" TextWrapping="Wrap" Width="247" IsEnabled="False" Text="{Binding Path=Localdir, UpdateSourceTrigger=PropertyChanged}"/>
In the cs code of the main window, I define an instance of my Project class, called currentproj, as follows:
public partial class MainWindow : Window{
Project currentproj;
public MainWindow()
{
currentproj = new Project();
InitializeComponent();
}}
The project class (defined in a Project.cs file) is as follows:
public partial class Project : Component, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _localdir;
public string Localdir
{
get { return _localdir; }
set
{
if (value != _localdir)
{
_localdir = value;
NotifyPropertyChanged("Localdir");
}
}
}
public Project()
{
InitializeComponent();
}
public Project(IContainer container)
{
container.Add(this);
InitializeComponent();
}}
However, even if I am binding the textbox.text attribute to the Localdir path of the currentproj object, the textbox is never updated. I see the PropertyChanged event is alwais null when I set the value of Localdir, but I don't understand why.
Data binding works on the DataContext. The Grid's DataContext is not set correctly, this should be removed.
so the Grid definition should be:
<Grid x:Name="gr_main" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="180,65,0,0">
Setting the Window DataContext to currentProj is done by:
public partial class MainWindow : Window{
Project currentproj;
public MainWindow()
{
currentproj = new Project();
DataContext = currentproj;
InitializeComponent();
}}

C# WPF - ComboBox of databound ObservableCollection of classes

Recently I started converting a proof of concept UWP app to a working WPF app.
What I want is to have two dropdowns (combobox) of "characters" I can choose, I want them databound to an ObservableCollection property, where the characters I selected is stored in a different Character property for player 1 then player 2.
I had databinding on dropdowns working in the UWP app, but I can't get it to work in the WPF app.
In the WPF app, the comboboxes stay empty and I can't select an option.
I tried following the answer to this question, but I think I'm missing something: Binding a WPF ComboBox to a custom list
Here is the code, kept minimal:
Character.cs
public class Character : INotifyPropertyChanged
{
private int _id;
public int Id
{
get
{
return _id;
}
set
{
_id = value;
}
}
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public Character(int id, string name)
{
Id = id;
Name = name;
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindow.xaml
<Window x:Class="SmashWiiUOverlayManager.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:SmashWiiUOverlayManager"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Grid.Row="0">
<ComboBox
Name="Player1CharacterDropdown"
ItemsSource="{Binding CharacterList, Mode=TwoWay}"
SelectedItem="{Binding Player1SelectedCharacter, Mode=TwoWay}"
SelectedValuePath="Name"
DisplayMemberPath="Name"
Width="144">
</ComboBox>
</StackPanel>
<StackPanel Grid.Column="0" Grid.Row="1" HorizontalAlignment="Right">
<ComboBox
Name="Player2CharacterDropdown"
ItemsSource="{Binding CharacterList, Mode=TwoWay}"
SelectedItem="{Binding Player2SelectedCharacter, Mode=TwoWay}"
SelectedValuePath="Character"
DisplayMemberPath="Name"
Width="144">
</ComboBox>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<Character> _characterList;
public ObservableCollection<Character> CharacterList
{
get
{
return _characterList;
}
set
{
_characterList = value;
}
}
private Character _player1SelectedCharacter;
public Character Player1SelectedCharacter
{
get
{
return _player1SelectedCharacter;
}
set
{
_player1SelectedCharacter = value;
}
}
private Character _player2SelectedCharacter;
public Character Player2SelectedCharacter
{
get
{
return _player2SelectedCharacter;
}
set
{
_player2SelectedCharacter = value;
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
CharacterList = new ObservableCollection<Character>
{
new Character(0, "Mario"),
new Character(1, "Luigi"),
new Character(2, "Wario"),
};
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
This code currently leaves the comboboxes empty.
When I use:
Player1CharacterDropdown.ItemsSource = new ObservableCollection<Character>
{
new Character(0, "Mario", ".\\images\\mario.png"),
new Character(1, "Luigi", ".\\images\\luigi.png"),
new Character(2, "Wario", ".\\images\\wario.png"),
};
... the combox gets filled, but it's databound to the property, which is what I would like.
What am I missing here?

How to get ComboBox content value?

I would like to get content from my combobox. I already tried some ways to do that, but It doesn't work correctly.
This is example of my combobox:
<ComboBox x:Name="cmbSomething" Grid.Column="1" Grid.Row="5" HorizontalAlignment="Center" Margin="0 100 0 0" PlaceholderText="NothingToShow">
<ComboBoxItem>First item</ComboBoxItem>
<ComboBoxItem>Second item</ComboBoxItem>
</ComboBox>
After I click the button, I want to display combobox selected item value.
string selectedcmb= cmbSomething.Items[cmbSomething.SelectedIndex].ToString();
await new Windows.UI.Popups.MessageDialog(selectedcmb, "Result").ShowAsync();
Why this code does not work?
My result instead of showing combobox content, it shows this text:
Windows.UI.Xaml.Controls.ComboBoxItem
You need the Content property of ComboBoxItem. So this should be what you want:
var comboBoxItem = cmbSomething.Items[cmbSomething.SelectedIndex] as ComboBoxItem;
if (comboBoxItem != null)
{
string selectedcmb = comboBoxItem.Content.ToString();
}
I have expanded on my suggestion regarding using models instead of direct UI code-behind access. These are the required parts:
BaseViewModel.cs
I use this in a lot of the view models in my work project. You could technically implement it directly in a view model, but I like it being centralized for re-use.
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Hashtable values = new Hashtable();
protected void SetValue(string name, object value)
{
this.values[name] = value;
OnPropertyChanged(name);
}
protected object GetValue(string name)
{
return this.values[name];
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
ComboViewModel.cs
This what you'll bind to make it easy to get values. I called it ComboViewModel because I'm only dealing with your ComboBox. You'll want a much bigger view model with a better name to handle all of your data binding.
public class ComboViewModel : BaseViewModel
{
public ComboViewModel()
{
Index = -1;
Value = string.Empty;
Items = null;
}
public int Index
{
get { return (int)GetValue("Index"); }
set { SetValue("Index", value); }
}
public string Value
{
get { return (string)GetValue("Value"); }
set { SetValue("Value", value); }
}
public List<string> Items
{
get { return (List<string>)GetValue("Items"); }
set { SetValue("Items",value); }
}
}
Window1.xaml
This is just something I made up to demonstrate/test it. Notice the various bindings.
<Window x:Class="SO37147147.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ComboBox x:Name="cmbSomething" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" HorizontalAlignment="Center" MinWidth="80"
ItemsSource="{Binding Path=Items}" SelectedIndex="{Binding Path=Index}" SelectedValue="{Binding Path=Value}"></ComboBox>
<TextBox x:Name="selectedItem" MinWidth="80" Grid.Row="2" Grid.Column="0" Text="{Binding Path=Value}" />
<Button x:Name="displaySelected" MinWidth="40" Grid.Row="2" Grid.Column="1" Content="Display" Click="displaySelected_Click" />
</Grid>
</Window>
Window1.xaml.cs
Here's the code-behind. Not much to it! Everything is accessed through the dataContext instance. There's no need to know control names, etc.
public partial class Window1 : Window
{
ComboViewModel dataContext = new ComboViewModel();
public Window1()
{
InitializeComponent();
dataContext.Items=new List<string>(new string[]{"First Item","Second Item"});
this.DataContext = dataContext;
}
private void displaySelected_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(String.Format("Selected item:\n\nIndex: {0}\nValue: {1}", dataContext.Index, dataContext.Value));
}
}
You can add business logic for populating models from a database, saving changes to a database, etc. When you alter the properties of the view model, the UI will automatically be updated.

Why my text box is not correctly initialized when the datacontext is an list?

I have a Hero class. It has only one property Name and it implemented the interface about the changes.
public class Hero : INotifyPropertyChanged, INotifyCollectionChanged
{
public string Name { get { return _name; }
set
{
_name = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
if (CollectionChanged != null)
{
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace));
}
}
}
private string _name = "";
}
My xaml is follows. I bind the text's datacontext to a collection named Heros which is defined in back code.
<Window x:Class="Chap21_2.TestCollection"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TestCollection" Height="640" Width="480"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBox Grid.Row="1" Text="{Binding Name}" DataContext="{Binding Heros}"></TextBox>
<Button Grid.Row="2" Content="aa" ></Button>
</Grid>
</Window>
And here're my back code. It initlialize the collection, the problem is when the Init() order is changed, the result is different.
public partial class TestCollection : Window
{
public ObservableCollection<Hero> Heros { get { return _heros; } set { _heros = value; } }
private ObservableCollection<Hero> _heros = new ObservableCollection<Hero>();
public TestCollection()
{
// If move Init() here, it'll works.
InitializeComponent();
Init();
}
void Init()
{
Hero hero = new Hero("Bu Lv", 100, 88, 100, 30);
_heros.Add(hero);
hero.HP = 88;
hero = new Hero("Fei Zhang", 100, 88, 100, 30);
hero.HP = 90;
_heros.Add(hero);
}
}
When I start my code , the text box isn't display "Bu Lv" I expected.
But If I move Init() before InitializedComponent(), it works.
Why?
It's probably due to binding a collection to a single textbox. If I change it to use an ItemsControl, the Init can be after InitializeComponent. Here are the things I did.
Hero.cs
public class Hero : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name = string.Empty;
public string Name
{
get { return _name; }
set
{
_name = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
}
}
TestCollection.xaml
This includes your original with some changes for illustration purposes.
<Window x:Class="Chap21_2.TestCollection"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TestCollection" Height="640" Width="480"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<!-- Your original binding -->
<TextBox Grid.Row="0" Text="{Binding Name}" DataContext="{Binding Heros}" />
<!-- My added ItemsControl for example purposes -->
<ItemsControl Grid.Row="1" ItemsSource="{Binding Heros}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Grid.Row="2" Content="aa" ></Button>
</Grid>
</Window>
TestCollection.xaml.cs
Your example didn't include all of the other properties, so I commented them out for the purpose of this example.
public partial class TestCollection : Window
{
public ObservableCollection<Hero> Heros { get { return _heros; } set { _heros = value; } }
private ObservableCollection<Hero> _heros = new ObservableCollection<Hero>();
public TestCollection()
{
// Putting this here allows the collection to populate BEFORE the UI is initialized.
// Init();
InitializeComponent();
// Putting it here is normal. The ItemsControl works, but the single TextBox binding will not.
Init();
}
private void Init()
{
Hero hero;
//Hero hero = new Hero("Bu Lv", 100, 88, 100, 30);
//hero.HP = 88;
hero = new Hero();
hero.Name = "Bu Lv";
_heros.Add(hero);
//hero = new Hero("Fei Zhang", 100, 88, 100, 30);
//hero.HP = 90;
hero = new Hero();
hero.Name = "Fei Zhang";
_heros.Add(hero);
}
}
My suggestion is that if you are wanting to display a single model from a collection, you may want to add a "SelectedHero" or similar property that contains only a single Hero instance. A collection bound to something is usually meant to render all instances in the collection.
Either way you look at it, you can put the Init() before or after IntializeComponent() because you are not directly interacting with the UI.

Bind Nested object to TextBox

I want to Bind ChildProperty to the TextBox in xaml.
XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="ChildProperty" Grid.Column="0" Grid.Row="0"/>
<TextBox Text="{Binding Path=ChildProperty}" Grid.Column="1" Grid.Row="0" Width="50"/>
<TextBlock Text="ParentProperty" Grid.Column="0" Grid.Row="1"/>
<TextBox Text="{Binding Path=ParentProperty}" Grid.Column="1" Grid.Row="1" Width="50"/>
</Grid>
DataContext:
public NotifyParentChangePropertyInChildClass()
{
InitializeComponent();
this.DataContext = new ParentClass();
}
Parent & Child Class:
public class ParentClass :INotifyPropertyChanged
{
private int parentProperty;
public int ParentProperty
{
get { return parentProperty; }
set
{
parentProperty = value;
RaisePropertyChanged("ParentProperty");
}
}
public ParentClass()
{
ChildClass obj = new ChildClass();
obj.ChildProperty = 100;
parentProperty = 200;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class ChildClass : INotifyPropertyChanged
{
private int childProperty;
public int ChildProperty
{
get { return childProperty; }
set
{
childProperty = value;
RaisePropertyChanged("ChildProperty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
When I run above code, In out put window Message "System.Windows.Data Error: 40 : BindingExpression path error: 'ChildProperty' property not found on 'object' ''ParentClass' (HashCode=59593954)'. BindingExpression:Path=ChildProperty; DataItem='ParentClass' (HashCode=59593954); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')"
The Error you are getting is right. You had set the DataContext to be ParentClass, whereas you are setting the binding property from ChildClass. You can only have one DataContext. To use both the properties you can define the properties in same class or derive from one and use the child class as datacontext.
Define property of Type ChildClass in ParentClass as below:
ChildClass _childClass;
public ChildClass ChildClass
{
get { return _childClass; }
set { _childClass = value; RaisePropertyChanged("ChildClass"); }
}
and in constructor of ParentClass initialize instance of _childClass.
Change the binding of textbox as:
<TextBox Text="{Binding Path=**ChildClass.ChildProperty**}" Grid.Column="1" Grid.Row="0" Width="50"/>
Thanks

Categories

Resources