MVVM Distribute Objects - c#

Maybe it is just a small step to the solution but I can't get it so far.
I did some WPF tutorials for DataContext and Binding, but I can't get how I could share the context and/or binding between (e.g.) two pages.
For example when you look at this one: https://msdn.microsoft.com/en-us/library/ms754356%28v=vs.110%29.aspx
<Label>Enter a Name:</Label>
<TextBox>
<TextBox.Text>
<Binding Source="{StaticResource myDataSource}" Path="Name" UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
</TextBox>
<Label>The name you entered:</Label>
<TextBlock Text="{Binding Source={StaticResource myDataSource}, Path=Name}"/>
This will be the result:
It's an easy example and there is no problem running and understanding this, but what I want is:
Fill the TextBox and the Label from code behind. I tried to name the TextBox tb and then just call tb.Text = "some text" - it works. I also tried to assign a DataContext for both the TextBox and the Label, then create a object and fill the DataContext with the object - this also worked.
Placing the Label on another page.
Problem 2 is the one that is really hard for me, especially in combination with problem 1.
For example: When I create the object in page 1 constructor and assign it the DataContext (ofcourse) only the TextBox on page 1 will contain the value.
I simply don't know how to share this one object I declared in page 1 with page 2 to set it also to the DataContext.
Maybe I just didn't find the perfect tutorial or explanation for me to understand how DataContext and Binding really works and how I can share objects between pages and windows.
Can you guys help me out?
If you need more informations, feel free to ask ;)

public class MySharedDataContext : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
}
In app.xaml create shared resource:
<Application x:Class="WpfApplication1.App"
xmlns:my="clr-namespace:WpfApplication1">
<Application.Resources>
<my:MySharedDataContext x:Key="MySharedDataContext" />
now you can use shared resource in both pages:
<TextBox Text="{Binding Name, Source={StaticResource MySharedDataContext}" />
if you want to sent Name value, better don't access TextBox directly, but set it in you MySharedDataContext class:
var dataContext = (MySharedDataContext)FindResource("MySharedDataContext");
dataContext.Name = "John Smith";

Related

How do I pass an input via binding to a custom property in UserControl? [duplicate]

I have a UserControl that I want to participate in data binding. I've set up the dependency properties in the user control, but can't get it work.
The uc displays the correct text when I call it with static text (e.g BlueText="ABC") . When i try to bind it to a local public property, it is always blank.
<src:BlueTextBox BlueText="Feeling blue" /> <!--OK-->
<src:BlueTextBox BlueText="{Binding Path=MyString}" /> <!--UserControl always BLANK!-->
<TextBox Text="{Binding Path=MyString}" Width="100"/> <!--Simple TextBox Binds OK-->
I've boiled the code down to the following simplified example. Here is the XAML of the UserControl:
<UserControl x:Class="Binding2.BlueTextBox" ...
<Grid>
<TextBox x:Name="myTextBox" Text="{Binding BlueText}" Foreground="Blue" Width="100" Height="26" />
</Grid>
Here is the code behind of the UserControl:
public partial class BlueTextBox : UserControl
{
public BlueTextBox()
{
InitializeComponent();
DataContext = this; // shouldn't do this - see solution
}
public static readonly DependencyProperty BlueTextProperty =
DependencyProperty.Register("BlueText", typeof(string), typeof(BlueTextBox));
public string BlueText
{
get { return GetValue(BlueTextProperty).ToString(); }
set { SetValue( BlueTextProperty, value.ToString() ); }
}
This seems like it should be really easy, but I can't make it work. Thanks for your help!
More info: When i was trying the fix suggested by Eugene, I noticed some peculiar behavior. I added a PropertyChangedCallback to the metadata; this allows me to watch the value of BlueText getting set. When setting the string to a static value (="feeling blue") the PropertyChanged event fires. The data binding case does not fire PropertyChanged. I think this means the data-bound value is not getting sent to the UserControl. (I think the constructor does not get called in the static case)
Solution: The problems were correctly identified by Arcturus and jpsstavares. First, I was overwriting the data binding when is set DataContext=this in the constructor of the control. This prevented the data bound value from getting set. I also had to name the control x:Name=root, and specify the Binding ElementName=root int the XAML. To get the TwoWay binding, I needed to set Mode=TwoWay in the caller. Here is the correct code:
<src:BlueTextBox BlueText="{Binding Path=MyString, Mode=TwoWay}}" /> <!--OK-->
Now the XAML in the UserControl:
<UserControl x:Class="Binding2.BlueTextBox" x:Name="root"...
<Grid>
<TextBox x:Name="myTextBox" Text="{Binding ElementName=root, Path=BlueText}" Foreground="Blue" Width="100" Height="26" />
</Grid>
Finally I removed the DataContext=this in the constructor of the UserControl.
public BlueTextBox()
{
InitializeComponent();
//DataContext = this; -- don't do this
}
Thanks everyone for the tremendous help!
You set the DataContext in the Control to itself, thus overwriting the DataContext when using this Control in other controls. Taking your binding as example in your situation:
<src:BlueTextBox BlueText="{Binding Path=MyString}" />
Once loaded and all the Datacontext is set, it will look for the path MyString in your BlueTextBox thing control due to you setting the DataContext to it. I guess this is not how you intended this to work ;).
Solution:
Change the text binding either one of the 2 bindings:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type src:BlueTextBox}}, Path=BlueText}
or
Name your control Root (or something like that)
<UserControl x:Name="Root"
{Binding ElementName=Root, Path=BlueText}
And remove the
DataContext = this;
from the constructor of your UserControl and it should work like a charm..
I think in this case you need to set the ElementName property in the binding. Something like this:
<UserControl x:Class="Binding2.BlueTextBox" x:Name="blueTextBox"...
<Grid>
<TextBox x:Name="myTextBox" Text="{Binding ElementName=blueTextBox, Path=BlueText}" Foreground="Blue" Width="100" Height="26" />
</Grid>
Possibly you need to add to your property FrameworkPropertyMetadata where specify FrameworkPropertyMetadataOptions.AffectsRender and AffectsMeasure.
FrameworkPropertyMetadataOptions enumeration MSDN article
I know this is an old topic but still.
Also mention the PropertyChangedCallback on the UIPropertyMetadata during registering your DP

bind a ComboboxItem as a checkbox to its presence in a list?

I am new to wpf and aside from the reading of 'WPF unleashed', I try to write a little program, it gives me a direction to go to.
So here is my problem : I want to manage my books, at home, with a little library program. Each book is stored in a Book class instance. thus, This Book class contains all the informations of the book, and among them its keywords, which is a ObservableCollection in a 'Keywords' property:
public ObservableCollection<string> Keywords
{
get => _keywords;
set
{
_keywords = value;
OnPropertyChanged("Keywords");
}
}
(_keywords is a string).
What I want is to display a textBox and a combobox which show related piece of informations: the textBox show all the Keywords of the selected book, and the combobox show a list of checkboxes, it is filled by the list of all the Keywords in all the books of the library, listed once (duplicate removed) and each checkbox is checked if the keyword is present in the listbox of the selected book.
I will try to explain it differently : each book contains a list of all its keywords. The combobox contains all the existing keywords (of all the books) and those related to the selected book are checked : it allows me 2 ways to add/edit/remove keywords : with the textbox, or by checking/unchecking the checkBoxes.
Back to my problem : how can I code this, using a maximum of databinding?
For the textbox, I see no problem : I will create a property in 'Book' say 'KeywordsForTextbox' with only a getter returning the keywords collection items merged with the appropriate separation character. then, using a dataContext pointed to the selectedBook, the text property of the textbox is bound to KeywordsForTextbox.
I would rather not considering the textbox edition for now.
But for the combobox, there are 2 things to bind:
1/ the list of all the keywords. I can put in my BookManagement class (which ... manages the Book class) a property whose getter will return the list of all the keywords (via link).
2/ each ComboboxItem will be edited in XAML but how to implement the behavior of setting it as checked/unchecked if the given keyword(got by using the property ItemSource of the checkbox to the Keyword property of the Book instance) is contained in the selectedItem.Keywords ?
more shortly : how to bind the checked property of a CheckBoxItem to the presence of this string in listView's selectedItem.Keywords (a list of strings)?
I apologize for the messy aspect of my question!
thank you.
EDIT
Well, I managed to write all the stuff, it builds and run, but there is a problem; in fact in the combobox, the itemsSource was written like this:
ItemsSource="{Binding Source={StaticResource AllBooks}}"
and AllBooks is a resource:
<ObjectDataProvider
x:Key="AllBooks"
MethodName="listOfAllKeywords"
ObjectType="{x:Type mangmt:BookManagement}" />
here is the method:
public static ObservableCollection<string> listOfAllKeywords()
{
if (App.Books != null)
{
IEnumerable<string> kws = App.Books.SelectMany(book => book.Keywords).Distinct();
return new ObservableCollection<string>(kws);
}
else return new ObservableCollection<string>();
}
When I run my program, the window is displayed normally, but if I click on a book to display it, and if I click on the combobox, at the second click I get an exception :
System.InvalidCastException : 'Impossible d'effectuer un cast d'un objet de type 'MS.Internal.NamedObject' en type 'System.String'.'
which saying the cast is impossible.
I saw in the debugging that in the multibinding, the seconf parameter is null(anyway it seems not very relevant because the exception is caused by the first parameter, but yet it's annoying).
I recall you my multibinding:
<ComboBox
x:Name="cbb_Keywords"
Grid.Column="2"
Width="300"
Margin="5,0,0,0"
HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource AllBooks}}"
DataContext="{Binding ElementName=listBoxBooks,Path=SelectedItem,Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Width="200">
<MultiBinding Converter="{StaticResource TextInListTrueFalseConverter}" >
<Binding Path="KeywordsForTextbox"></Binding>
<Binding RelativeSource="{RelativeSource Self}" Path="Content"></Binding>
</MultiBinding>
</CheckBox>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
here is KeywordsForTextBox:
public string KeywordsForTextbox
{
get { return string.Join(",", _keywords); }
}
(it's in the Book class)
is the first binding (of the multibinding) wrong?why?
thank you.
EDIT 2
I created a new post because this one is too messy(too much text in my question) and the area of the possible causes is much restricted as I saw this was a dependencyProperty problem. here is the link : here in stack overflow
based on my suggestion in the comment, IMultiValueConverter (Sorry in VB):
Public Class TextInListTrueFalseConverter
Implements IMultiValueConverter
Public Function Convert(values() As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IMultiValueConverter.Convert
Dim Checked As Boolean = False
If Not values Is Nothing Then
If values.Count = 2 Then
Dim ListString As String = values(0)
Dim WordToFind As String = values(1)
If Not ListString Is Nothing Then
Dim KeywordList As List(Of String) = ListString.Split(";").ToList 'Assuming you seperator is a ;
If KeywordList.Contains(WordToFind) Then Checked = True
End If
End If
End If
Return Checked
End Function
Public Function ConvertBack(value As Object, targetTypes() As Type, parameter As Object, culture As CultureInfo) As Object() Implements IMultiValueConverter.ConvertBack
Throw New NotImplementedException()
End Function
End Class
XAML:
<CheckBox >
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource TextInListTrueFalseConverter}">
<Binding Path="KeywordsForTextbox" />
<Binding RelativeSource="{RelativeSource Self}" Path="Content" />
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
(use DataContext of the Combo to bind to the Book class, and use ItemsSource to load the KeyWords)
Finally, add <Local:TextInListTrueFalseConverter x:Key="TextInListTrueFalseConverter" /> as a resource, adding the "Local" namespace if not already present.e.g. xmlns:Local="clr-namespace:APPNAME".
Not tested, but think it should work, or at least give you something to go on.
EDIT
My bad. I had it in my head that the ComboBoxItems would Inherit the DataContext from the ComboBox, but of course they do not - they are bound to the ItemsSource obviously. Try the following changes, I have updated the first Binding to be to the ListBox of Books. I have also added in the <CheckBox.IsChecked> where appropriate, and also added Content="{Binding}" to the CheckBox:
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Width="200" Content={Binding}>
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource TextInListTrueFalseConverter}" >
<Binding ElementName=listBoxBooks, Path=SelectedItem.KeywordsForTextbox"></Binding>
<Binding RelativeSource="{RelativeSource Self}" Path="Content"></Binding>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
You may also wish to add some validation to the IMultiValueConverter to make sure the passed values are not unset, to avoid an exception: If Not values(0) Is DependencyProperty.UnsetValue And Not values(1) Is DependencyProperty.UnsetValue Then in VB.

WinRT DependencyProperty is never set

I have some issues with my DependencyProperty in a custom UserControl.
I need to display informations about people in a particular way. To achieve this, I have several UserControls that receive a List<PeopleList> which contains (obviously) one or more People.
Let me show you my (simplified) code and I'll then explain to you the actual behavior of my app.
Here is my UserControl :
public abstract class PeopleLine : UserControl
{
public static readonly DependencyProperty PeopleListProperty =
DependencyProperty.Register("PeopleList", typeof(List<PeopleModel>), typeof(PeopleLine), new PropertyMetadata(default(List<PeopleModel>)));
public List<PeopleModel> PeopleList
{
get { return (List<PeopleModel>)GetValue(PeopleListProperty); }
set { SetValue(PeopleListProperty, value); }
}
}
Then my xaml :
<local:PeopleLine
x:Class="MyApp.Controls.EventSheet.OnePeople"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyApp.Controls.EventSheet"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid
Margin="0 5"
VerticalAlignment="Top"
Height="51">
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
Foreground="Red"
FontSize="25"
Text="{Binding PeopleList[0].Name}"/>
</Grid>
</local:PeopleLine>
And this all starts with my Page which contains an ItemsControl with a correct ItemsSource (I already checked it) and an ItemTemplateSelector (also working perfectly). Here is one of the DataTemplate used by the selector :
<DataTemplate x:Key="OnePeople">
<peoplecontrols:OnePeople
PeopleList="{Binding LinePeopleList}"/>
</DataTemplate>
I'm using several Models That are not really important here since I simplified my code to only have the most important information.
So, back to my issue. When replacing the peoplecontrols:OnePeople in the selector's DataTemplate by a string and putting LinePeopleList[0].Nameas Text, I have the correct text displayed, proving me that my data is correct at this point.
Problem is that when putting back my peoplecontrols:OnePeople, my DependencyProperty is never set. I put a breakpoint at PeopleList's setter and it never triggers.
I tried several modifications (especially those that are given in this post, so replacing the typeof(List<PeopleModel>)by typeof(object) has already been tried) with no success. Also, I tried to replace my DependencyProperty to a string and directly send the name in the DataTemplate but the setter is still not called...
I have no more ideas now and don't understand what's wrong with my code. Any help would be greatly appreciated.
Thanks in advance.
Thomas
Try adding the following line in your UserControl's Constructor, after the call to InitializeComponent:
(this.Content as FrameworkElement).DataContext = this;
I created a sample app on regarding this. Hopefully it reflects your situation correctly:
https://github.com/mikoskinen/uwpusercontrollistdp
If you clone the app and run it, you'll notice that the binding doesn't work. But if you uncomment the Datacontext = this line from UserControl, everything should work OK. Here's working code:
public PeopleLine()
{
this.InitializeComponent();
(this.Content as FrameworkElement).DataContext = this;
}

Failing to successfully expose my ViewModel data to my View for XAML binding

I'm building a Windows Universal app and trying to expose data from my ViewModel to my View so that I can bind it to XAML elements. I have completely commented out all of my code at this point and am just writing lines of test code to try and get it to work, that is what is in the examples below. Binding directly from the View (if I create an object there as a test) does work.
Please help me to understand where I am going wrong, I think I've read every binding tutorial on the internet and still just don't get it.
View (MainPage.xaml.cs):
public MainPage()
{
InitializeComponent();
DataContext = new MainViewModel();
}
ViewModel (MainViewModel.cs):
public class MainViewModel
{
public Term newTerm = new Term
{
TermName = "Table",
TermDescription = "You eat dinner on it"
};
}
XAML (MainPage.xaml):
<StackPanel DataContext="{Binding newTerm}" x:Name="mvvmStack" Orientation="Vertical">
<TextBlock x:Name="mvvmTermName" Text="{Binding TermName, FallbackValue='Fallingback'}" />
<TextBlock x:Name="mvvmDescription" Text="{Binding TermDescription, FallbackValue='Fallingback', TargetNullValue='Unknown'}" />
</StackPanel>
The error I get is:
Error: BindingExpression path error: 'newTerm' property not found on ''. BindingExpression: Path='newTerm' DataItem=''; target element is 'Windows.UI.Xaml.Controls.StackPanel' (Name='mvvmStack'); target property is 'DataContext' (type 'Object')
I have read about this type of error and although I have some idea of what it is trying to say I cannot work out how to fix it. I'm very much a complete beginner with coding, especially C# so please take that into account when answering :-)
Just try to change it from field to a property and it will be working correctly. You can't bind to fields.
EDIT:
private Term _term;
public Term NewTerm{
get{return _term;}
set
{
_term= value;
OnPropertyChanged("Term");
}
}
if you need to add notify the view of changes in the viewmodel you need to implement INotifyPropertyChanged.
check this answer it will provide an example for property changed. https://stackoverflow.com/a/27685925/1448382
If you want to bind the view to sub properties, you have two options depending on the situation:
1- Relative Binding: this scenario is used when you will not modify the properties inside the Term object from the ViewModel i.e. they will be just initialized in the viewmodel and can be modified in the view, just like the way you are doing it. Plesae note, that anything you need to bind to should be a property and not a field.
2- Binding to Viewmodel directly: this scenario is used when you will modify the properties inside the Term object from the Viewmodel after the view load. This way you will need to add properties to the viewmodel for the properties TermName and TermDescription.
public string TermName{
get{return NewTerm.Name;}
set{NewTerm.Name = value;
OnPropertyChanged("TermName");
}//The same is applied for TermDescription
But be aware that you will need to remove the binding on the Stackpanel object since you have defined the properties directly in the Viewmodel.
Try something like that:
<Page.Resources>
<viewModels:MainViewModel x:Key="MainViewModel" />
</Page.Resources>
And then:
<StackPanel x:Name="mvvmStack" Orientation="Vertical">
<TextBlock x:Name="mvvmTermName" Text="{Binding newTerm.TermName, Source={StaticResource MainViewModel} FallbackValue='Fallingback'}" />
<TextBlock x:Name="mvvmDescription" Text="{Binding newTerm.TermDescription, Source={StaticResource MainViewModel} FallbackValue='Fallingback', TargetNullValue='Unknown'}" /></StackPanel>
Of cource newTerm should be an property with INotifyChanged

How to validate a UI in Silverlight?

My UI is simple. In a Silverlight 5.0 application, I'm using MVVM and I let the user adds many textboxes as he wants to add in a ObservableCollection<Model> and a Button.
Model just have one property and its datatype is an integer.
The data template for this model is just a simply textbox.
<TextBox Text="{Binding Number}" />
So the idea is, when all the textboxes does not have any error, the command is enabled, but if any model has an error, the command should be disabled.
How can I implement this validation?
Thanks in advance.
You can simply throw an exception in appropriate property`s setter:
public int Number
{
get {//...}
set {
if(value >= 10)
throw new Exception("Number should be less than 10");
_number = number;
}
}
And your binding should be:
<TextBox Text="{Binding Number, Mode="TwoWay" ValidateOnExceptions="True"}" />
FrameworkElement has BindingValidationErrorEvent, which can be used for implement enable/disable command logic. Remember to set NotifyOnValidationError to True for your binding.
p.s.Also, i suggest you read about INotifyDataErrorInfo

Categories

Resources