How to do Binding Stackpanel and stringFormat? - wpf

I have some stackpanel that have under him 4 textblock that need to show some information.
I do some binding of the stackpanel ( DataContext ) and binding the textblock with the information that will hold by the object that was bind to the stackpanel.
I wrote the code + xaml and nothing work.
I get exception about format wrong.
The code:
public partial class SomeDemoClass: UserControl
{
classObjDemo c1;
public SomeDemoClass()
{
InitializeComponent();
c1 = new classObjDemo()
{
val1 = 5.5,
val2 = 2.3
};
}
}
The xaml ( that match the class 'SomeDemoClass' )
<StackPanel x:Name="LayoutRoot" DataContext="{Binding ElementName=SomeDemoClass, Path=c1">
<TextBlock Text="{Binding val1, StringFormat={0:F} }" />
<TextBlock Text="{Binding val2, StringFormat={0:F} }" />
</StackPanael>

Of you put x:Name="SomeDemoClass" in the in the op of your xaml and make c1 a public property instead of a field it would work. ElementName references elements in your xaml by name and binding only works on properties and dependency properties.
<UserControl x:Name="SomeDemoClass" ...
public classObjDemo c1 { get; set; }
Also check your Visual Studio output window for binding errors.
EDIT
Also make sure v1 and v2 of the classObjDemo are public properties
And escape { in your xaml. See http://elegantcode.com/2009/04/07/wpf-stringformat-in-xaml-with-the-stringformat-attribute/
<TextBlock Text="{Binding val2, StringFormat={}{0:F} }" />

Related

Data Binding doesn't work in xaml

I try to use binding to display Hi in the Text content.
However, when clicking the button, it doesn't work.
Could someone help me to solve the problem?
Thanks.
1.XAML CODE :
<Window x:Class="Wpftest.binding.Window0"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window0" Height="300" Width="300">
<Grid>
<TextBox x:Name="textBox2" VerticalAlignment="Top" Width="168"
Text="{Binding Source= stu, Path= Name, Mode=TwoWay}"/>
</Grid>
</Window>
2.Class :
namespace Wpftest.binding.Model
{
public class student : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string name;
public string Name
{
get { return name; }
set { name = value;
if(this.PropertyChanged != null)
{
this.PropertyChanged.Invoke(this, new
PropertyChangedEventArgs("Name"));
}
}
}
}
}
3.XAML.cs:
namespace Wpftest.binding
{
public partial class Window0 : Window
{
student stu;
public Window0()
{
InitializeComponent();
stu = new student();
}
private void button_Click(object sender, RoutedEventArgs e)
{
stu.Name += "Hi!";
}
}
}
There are many ways to achieve what you need; the correct method depends very much on what style of application you want to create. I'll demonstrate two methods that will require minimal changes from your supplied example:
Method 1
Set the DataContext to stu and bind to the Name property.
XAML.cs
private student stu;
public Window0()
{
InitializeComponent();
stu = new student();
DataContext = stu;
}
XAML code
<TextBox Text="{Binding Path=Name, Mode=TwoWay}"/>
Method 2
Generally you will set the DataContext to some object other than the Window (e.g. the ViewModel if you are following the MVVM pattern), but sometimes you may need to bind a control to some property of the Window. In this case the DataContext can't be used, but you can still bind to a property of the Window by using RelativeSource. See below:
XAML.cs
// note this must be a property, not a field
public student stu { get; set; }
public Window0()
{
InitializeComponent();
stu = new student();
}
XAML code
<TextBox Text="{Binding Path=stu.Name, Mode=TwoWay,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>
Hint: if you are having trouble with WPF data binding, then it often helps to look at the debugger output window to see the binding trace messages. And debugging can be further enhanced by adding this namespace to the Window element
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
and then setting the TraceLevel e.g.
<TextBox Text="{Binding Source=stu, diag:PresentationTraceSources.TraceLevel=High}"/>
Basically you need to set DataContext property to your Window.
For example:
public MainWindow()
{
DataContext=new YourViewModel();
}
DataContext of Window is a way to communicate between View(XAML) and ViewModel(C# code)
In addition, you can add DataContext in xaml:
<Window.DataContext>
<local:YourViewModel/>
</Window.DataContext>
Also, instead of handling Click event, you should use Command property of Button. Example can be seen here.

why is it displaying class name instead of property?

I have the following combobox in the xaml:
<ComboBox x:Name="cmbCharacters1" HorizontalAlignment="Left" Margin="18,21,0,0" VerticalAlignment="Top" Width="136" SelectedIndex="0" Height="32" RenderTransformOrigin="1.53,-1.281"
ItemsSource="{Binding CharacterEntity}" SelectedItem="{Binding Name}" SelectedValue="{Binding Tag}"/>
and the following class and binding code
public class CharacterEntity
{
public string Name { get; set; }
public string Tag { get; set; }
}
....
cmbCharacters1.ItemsSource = characters;//it is a List<CharacterEntity>
when I run it displays the class name instead of the content of Name property, what am I doing wrong?
I think you forgot to use this: DisplayMemberPath="Tag" Or "Name" whatever you wish to display!
You need to set the DisplayMemberPath in your ComboBox XAML.
This isn't a binding, since the ItemsSource is already bound - you just reference the field name, like so:
<ComboBox DisplayMemberPath="Name" ...
In the XAML you are setting the ItemsSource to a class CharacterEntity instead of List<CharacterEntity>, since you are setting the Itemssource in the code-behind remove it from the XAML and try. Also, you need to set DisplayMemberPath="Name" and set either SelectedItem or SelectedValue not both, if you are using SelectedValue then also use SelectedValuePath="Name"
<ComboBox x:Name="cmbCharacters1" SelectedItem="{Binding someCharacter}" DisplayMemberPath="Name" />
See also this answer: https://stackoverflow.com/a/3797074/424129
C#
public class CharacterEntity
{
public string Name { get; set; }
public string Tag { get; set; }
}
// Look up how to implement INotifyPropertyChanged, I didn't bother here
public class MyViewModel : INotifyPropertyChanged {
public MyViewModel(IEnumerable<CharacterEntity> chars)
{
CharacterEntities = new List<CharacterEntity>(chars);
}
private IEnumerable<CharacterEntity> _characterEntities;
public IEnumerable<CharacterEntity> CharacterEntities {
get { return _characterEntities; }
set { _characterEntities = value;
OnPropertyChanged("CharacterEntities"); }
}
private CharacterEntity _characterEntity
public CharacterEntity SelectedCharacterEntity
}
XAML
ItemsSource is the source for the items. Your binding made no sense. You want to give it a list of CharacterEntity, but you bind to the CharacterEntity class? What list are you talking about? And don't set it in code behind. XAML makes much more sense if you use a viewmodel.
Now, somehow the above MyViewModel class needs to be made the DataContext of some control that owns the ComboBox.
<ComboBox HorizontalAlignment="Left" Margin="18,21,0,0"
VerticalAlignment="Top" Width="136" SelectedIndex="0" Height="32"
RenderTransformOrigin="1.53,-1.281"
ItemsSource="{Binding CharacterEntities}"
SelectedItem="{Binding SelectedCharacterEntity}"
DisplayMemberPath="Name"
/>
When you have it like this:
SelectedItem="{Binding Path=Name}"
it will use what ever is now selected in combobox, that class property of Name is being used as Selected. Without Path, you are binding to that combobox Name object. BUT anyways, this shouldn't yet work in your case with Path. So to have it work as you want it to, try this:
Have a SelectedItem binded to CharacterEntity class:
SelectedItem="{Binding SelectedEntity}" // Class instance of CharacterEntity
And then you have a Text binded to that selected entity class property of Name:
Text="{Binding Path=Name}" // Binded to property of Name
SelectedValue="{Binding Path=Tag}" // Binded to property of Tag
This way it should work. You should have a combobox binded to viewmodel and that viewmodel should have a property(class instance of CharacterEntity) of SelectedEntity. Hopefully this helps:
public class CharacterViewModel
{
public CharacterEntity SelectedEntity {get;set;}
public List<CharacterEntity> characters {get;set;} // use ObservableCollection insteand of List(Automatically update UI if list changes)
}
And XAML:
<ComboBox x:Name="cmbCharacters1" HorizontalAlignment="Left" Margin="18,21,0,0" VerticalAlignment="Top" Width="136" SelectedIndex="0" Height="32" RenderTransformOrigin="1.53,-1.281"
ItemsSource="{Binding characters}" Text="{Binding Path=Name}" SelectedItem="{Binding SelectedEntity}" SelectedValue="{Binding Path=Tag}"/>
Also has in codebehind e.g in constructor:
CharacterViewModel charViewModel = new CharacterViewModel();
cmdCharacters1.DataContext = charViewModel;
cmdCharacters1.ItemsSource= charViewModel.characters;
I'm terrible at explaining this, but I hope it makes sense with my code.

WPF binding automatically to the first collection item

Is it the intended behavior that a binding to a collection automatically uses the first item as source?
Example Xaml:
<Window x:Class="ListSelection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<TextBlock Text="{Binding ColContent}" />
<TextBlock Text="{Binding ItemContent}" />
</StackPanel>
</Window>
and Code:
using System.Collections.Generic;
using System.Windows;
namespace ListSelection
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyCol("col 1")
{
new MyItem("item 1"),
new MyItem("item 2")
};
}
}
public class MyItem
{
public string ItemContent { get; set; }
public MyItem(string content)
{
ItemContent = content;
}
}
public class MyCol : List<MyItem>
{
public string ColContent { get; set; }
public MyCol(string content)
{
ColContent = content;
}
}
}
The UI shows up with:
col 1
item 1
The second binding took implicitly the first collection item as source! So bug, feature or intended?
EDIT: .net 4.5, VS2012, corrections
EDIT 2:
I further investigated the problem together with a mate and got closer to the solution:
<Window x:Class="ListSelection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<ListView ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemContent}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBlock Text="{Binding ItemContent}" />
</StackPanel>
</Window>
The - lets call it - magic binding seems to exist for master detail views. By default any collection that is bound gets a CollectionView - which provides a selected item property (and other cool stuff like sorting). This selected item can be used shortcutted for the detailed view. If the IsSynchronizedWithCurrentItem is set to true the shortcutted binding reacts to changed selections. The problem in the whole thing: the selected item of the CollectionView is alway set to the first item which leads to the magic binding... I would call that a bug and it should only work explicitly, e.g. by binding the collection to a Selector with the IsSynchronizedWithCurrentItem set.

WPF ItemsSource Binding

I have a Datagrid control in my WPF application and I am trying to bind that control to an ObservableCollection property in my Main Window's class. The property I'm trying to bind to is defined as:
private ObservableCollection<RequestResult> m_SentRequests = new ObservableCollection<RequestResult>();
public ObservableCollection<RequestResult> SentRequests { get { return m_SentRequests; } }
My datagrid is in a group by which has the datacontext set to the MainWindow:
<GroupBox Header="Results" Height="275" HorizontalAlignment="Stretch" Margin="0,305,0,0" Name="grpResults" VerticalAlignment="Top" Width="712" DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:MainWindow, AncestorLevel=1}}">
<Grid>
<DataGrid AutoGenerateColumns="False" Height="246" HorizontalAlignment="Stretch" Margin="6,6,6,0" Name="dgResults" VerticalAlignment="Top" ItemsSource="{Binding Path=SentRequests}" DataContext="{Binding}" IsSynchronizedWithCurrentItem="True" />
</Grid>
</GroupBox>
The problem that I'm having is that in the properties window, after I select SentRequests as my ItemsSource, I still can't select the "Edit Property-Bound Columns" option. I get a "You must set ItemsSource before you can perform this action" dialog. I get the same error when selecting "Generate Columns" and "Remove Columns". It's as if I haven't set anything in the ItemsSource property for my Dialog.
I can set AutoGenerateColumns to true though and I see my data get bound though (however, not with the columns I want to show).
I'm very new to WPF and I'm just writing this as a quick test app for testing a windows service.
Any one know what I'm doing wrong here?
I don't believe you need the Path parameter within your itemSource. You should be able to just set the binding as ItemsSource={Binding SentRequests}
You can also bind to the grid item source in code for example if I create a dummy collection:
public class People
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Age { get; set; }
public string address { get; set; }
public string zip { get; set; }
}
and then populate it
this.AllPeople = new ObservableCollection<People>();
private void FillData()
{
People p1 = new People();
p1.FirstName = "John";
p1.LastName = "Doe";
p1.Age = "24";
p1.address = "123 Main Street";
p1.zip = "11111";
People p2 = new People();
p2.FirstName = "Jane";
p2.LastName = "Smith";
p2.Age = "36";
p2.address = "456 Water Street";
p2.zip = "22222";
People p3 = new People();
p3.FirstName = "Larry";
p3.LastName = "Williams";
p3.Age = "24";
p3.address = "785 Water Street";
p3.zip = "33333";
this.AllPeople.Add(p1);
this.AllPeople.Add(p2);
this.AllPeople.Add(p3);
}
I could then set the items source in the mainpage contsructor or method as:
this.gridviewname.ItemsSource = "AllPeople";
This is probably a result of some of the trickery that the designer does to render without constantly compiling (like skipping code-behind constructors). Try moving your collection to a separate class and use an instance of that as your DataContext (like an MVVM ViewModel). The other class should be able to initialize normally and provide the bound property to the designer.
Have you tryed without the DataContext tags? Both in GroupBox and DataGrid.
EDIT
something like this:
<GroupBox Header="Results" Height="275" HorizontalAlignment="Stretch" >
<Grid>
<DataGrid AutoGenerateColumns="False" Height="246" HorizontalAlignment="Stretch" Name="dgResults" VerticalAlignment="Top" ItemsSource="{Binding Path=SentRequests}" IsSynchronizedWithCurrentItem="True" />
</Grid>
</GroupBox>

MVVM DataTemplate Binding Issue

Perhaps this is a case of too much cold medicine, but I just can't seem to get this Binding correct.
Here is the (simplified) Window, with the a DataTemplate for each ViewModel type, which should just show an associated View:
<Window ...>
<Window.Resources>
<DataTemplate DataType="{x:Type local:DefaultViewViewModel">
<local:DefaultView />
</DataTemplate>
<DataTemplate DataType="{x:Type other:AnotherViewModel">
<other:AnotherView />
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding CurrentViewModel}" />
</Grid>
</Window>
Here is some of the MainViewModel (the actual ShowABCView methods are Command functions that do more than is shown here, for brevity):
class MainViewModel : ViewModelBase
{
private Stack<ViewModelBase> mContentViewStack;
public MainViewModel()
{
mContentViewStack = new Stack<ViewModelBase>();
ShowDefaultView();
}
public ViewModelBase CurrentViewModel
{
get { return mContentViewStack.Peek(); }
}
private ShowDefaultView()
{
DefaultViewViewModel viewModel = new DefaultViewViewModel();
mContentViewStack.Push(viewModel);
NotifyPropertyChanged("CurrentViewModel");
}
private ShowAnotherView()
{
AnotherViewModel viewModel = new AnotherViewModel();
mContentViewStack.Push(viewModel);
NotifyPropertyChanged("CurrentViewModel");
}
}
And the MainWindow startup code:
public MainWindow()
{
this.DataContext = new MainViewModel();
}
When I run this, I get the error
System.Windows.Data.Error: 40:
BindingExpression path error:
'Content' property not found on
'object' 'DefaultViewViewModel'
I know I'm missing something obvious here, but the Nyquil and friends betray me...
*EDIT - DefaultViewViewModel and DefaultView *
DefaultViewViewModel:
// ViewModelBase is basically just a wrapper for INotifyPropertyChanged,
// plus some other common-to-my-project properties
// (NOT INCLUDING A Content PROPERTY)
class DefaultViewViewModel : ViewModelBase
{
public DefaultViewViewModel() : base()
{
}
}
DefaultView:
<UserControl ...>
<TextBlock Text="Some Hard Coded Text Formatted To My Liking" />
</UserControl>
Well you haven't shown the code for the DefaultViewViewModel yet
but my guess is you defined "Content" as a field and not as a property.
to make sure that it will fix it, go ahead and overkill it by making Content a dependency property
hope that helps
Found the answer upstream from where I was looking. There was an incorrect binding (used regular Binding without RelativeSource of the TemplatedParent) in the base View control that all of our Views use.
No more Nyquil for me...

Resources