How to bind a variable with a textblock - wpf

I was wondering how I would be able to bind a text block to a variable within my C# class.
Basically I have a "cart" variable in my .cs file. Within that Cart class I have access to the different totals.
The following is what I have for binding, but it does not seem to bind to the variable...
<StackPanel
Width="Auto"
Height="Auto"
Grid.ColumnSpan="2"
Grid.Row="5"
HorizontalAlignment="Right">
<TextBlock
Name="Subtotal"
FontFamily="Resources/#Charlemagne Std"
FontSize="20"
Text="{Binding ElementName=cart, Path=SubTotal}">
</TextBlock>
<TextBlock
Name="Tax"
FontFamily="Resources/#Charlemagne Std"
FontSize="20"
Text="{Binding ElementName=cart, Path=Tax}">
</TextBlock>
<TextBlock
Name="Total"
FontFamily="Resources/#Charlemagne Std"
FontSize="20"
Text="{Binding ElementName=cart, Path=Total}">
</TextBlock>
</StackPanel>
What is the correct way of doing it? Thanks again for the help!

If you further want the TextBoxes to update automatically when your cart class changes, your class must implement the INotifyPropertyChanged interface:
class Cart : INotifyPropertyChanged
{
// property changed event
public event PropertyChangedEventHandler PropertyChanged;
private int _subTotal;
private int _total;
private int _tax;
private void OnPropertyChanged(String property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public int SubTotal
{
get
{
return _subTotal;
}
set
{
_subTotal = value;
OnPropertyChanged("SubTotal");
}
}
public int Total
{
get
{
return _total;
}
set
{
_total = value;
OnPropertyChanged("Total");
}
}
public int Tax
{
get
{
return _tax;
}
set
{
_tax = value;
OnPropertyChanged("Tax");
}
}
}

ElementName in binding is used to reference other controls, not variables in code behind. To reference variables in code behind, you need to assign that variable to a Control's DataContext property.
Replace every occurrence of following line of code :
<TextBlock Name="Subtotal" FontFamily="Resources/#Charlemagne Std" FontSize="20" Text="{Binding ElementName=cart, Path=SubTotal}"></TextBlock>
with :
<TextBlock Name="Subtotal" FontFamily="Resources/#Charlemagne Std" FontSize="20" Text="{Binding Path=SubTotal}"></TextBlock>
And in your Window's constructor or Load event, write following code :
this.DataContext = cart;

Two solutions..
First solution:
Put the cart as DataSource in your code behind:
DataSource = cart;
And bind to it as follows:
{Binding Path=PropertyOfCart}
Second solution:
Bind to your root control with an ElementName binding, and get the cart through a property on this control:
Name your root/parent control where cart is a propery:
<UserControl .....snip..... x:Name="Root">
Bind to it like this:
{Binding ElementName=Root, Path=Cart.PropertyOfCart}
Please note that Cart must be a property of your UserControl, and not a field

You need to set your class as the data source for your form. See also this question.

Related

Caliburn.Micro - Passing combobox items to a button

I'm trying to pass items from my Combobox (which is binded to my Model object's lists) to my button. My problem is that I'm new to Caliburn.Micro + WPF and not quite sure how to subscribe/pass the desired values to my button (like sending strings of a PropetyName to a Button(string propetyName)).
ViewModel code:
class ShellViewModel : Screen
{
private DataModel _fileInFolder;
private BindableCollection<DataModel> _data;
public ShellViewModel()
{
// .GetData() preforms the objects' initialization
DataModel dataOutput = new DataModel();
Data = new BindableCollection<DataModel>(dataOutput.GetData());
}
public BindableCollection<DataModel> Data
{
get
{
return _data;
}
set
{
_data = value;
NotifyOfPropertyChange(() => Data);
}
}
public DataModel FileInFolder
{
get { return _fileInFolder; }
set
{
_fileInFolder = value;
NotifyOfPropertyChange(() => FileInFolder);
}
}
//This is where the items will be passed to.
public void OpenFile()
{
}
}
XAML code:
<Grid>
<!-- Folders -->
<ComboBox ItemsSource="{Binding Data}" SelectedItem="{Binding FileInFolder}"
HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="250">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Folders}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!-- Files -->
<ComboBox x:Name="FileInFolder_Files"
HorizontalAlignment="Left" Margin="280,10,0,0" VerticalAlignment="Top" Width="250"/>
<!-- Open File -->
<Button x:Name="OpenFile"
Content="Open File" HorizontalAlignment="Left" Margin="560,10,0,0" VerticalAlignment="Top" Width="90">
</Button>
</Grid>
Sorry if my description is vague/missing more clarification, I'm a new user here!
FileInFolder is the selected item of the combo.
OpenFile is in the same class and can therefore reference FileInFolder.
public void OpenFile()
{
var whatever = FileInFolder.SomeProperty;
// etc
}
You probably want some null checking in there.

WPF dynamic binding to property

I have an ItemsControl that should display the values of some properties of an object.
The ItemsSource of the ItemsControl is an object with two properties: Instance and PropertyName.
What I am trying to do is displaying all the property values of the Instance object, but I do not find a way to set the Path of the binding to the PropertyName value:
<ItemsControl ItemsSource={Binding Path=InstanceProperties}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=PropertyName, Mode=OneWay}"/>
<TextBlock Text=": "/>
<TextBlock Text="{Binding Source=??{Binding Path=Instance}??, Path=??PropertyName??, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
the question marks are the points where I don't know how to create the binding.
I initially tried with a MultiValueConverter:
<TextBlock Grid.Column="1" Text="{Binding}">
<TextBlock.DataContext>
<MultiBinding Converter="{StaticResource getPropertyValue}">
<Binding Path="Instance" Mode="OneWay"/>
<Binding Path="PropertyName" Mode="OneWay"/>
</MultiBinding>
</TextBlock.DataContext>
</TextBlock>
The MultiValueConverter uses Reflection to look through the Instance and returns the value of the property.
But if the property value changes, this change is not notified and the displayed value remains unchanged.
I am looking for a way to do it with XAML only, if possible, if not I will have to write a wrapper class to for the items of the ItemsSource collection, and I know how to do it, but, since it will be a recurring task in my project, it will be quite expensive.
Edit:
For those who asked, InstanceProperties is a property on the ViewModel which exposes a collection of objects like this:
public class InstanceProperty : INotifyPropertyChanged
{
//[.... INotifyPropertyChanged implementation ....]
public INotifyPropertyChanged Instance { get; set; }
public string PropertyName { get; set; }
}
Obviously the two properties notify theirs value is changing through INotifyPropertyChanged, I don't include the OnPropertyChanged event handling for simplicity.
The collection is populated with a limited set of properties which I must present to the user, and I can't use a PropertyGrid because I need to filter the properties that I have to show, and these properties must be presented in a graphically richer way.
Thanks
Ok, thanks to #GazTheDestroyer comment:
#GazTheDestroyer wrote: I cannot think of any way to dynamically iterate and bind to an arbitrary object's properties in XAML only. You need to write a VM or behaviour to do this so you can watch for change notifications, but do it in a generic way using reflection you can just reuse it throughout your project
I found a solution: editing the ViewModel class InstanceProperty like this
added a PropertyValue property
listen to PropertyChanged event on Instance and when the PropertyName value changed is fired, raise PropertyChanged on PropertyValue
When Instance or PropertyName changes, save a reference to Reflection's PropertyInfo that will be used by PropertyValue to read the value
here is the new, complete, ViewModel class:
public class InstanceProperty : INotifyPropertyChanged
{
#region Properties and events
public event PropertyChangedEventHandler PropertyChanged;
private INotifyPropertyChanged FInstance = null;
public INotifyPropertyChanged Instance
{
get { return this.FInstance; }
set
{
if (this.FInstance != null) this.FInstance.PropertyChanged -= Instance_PropertyChanged;
this.FInstance = value;
if (this.FInstance != null) this.FInstance.PropertyChanged += Instance_PropertyChanged;
this.CheckProperty();
}
}
private string FPropertyName = null;
public string PropertyName
{
get { return this.FPropertyName; }
set
{
this.FPropertyName = value;
this.CheckProperty();
}
}
private System.Reflection.PropertyInfo Property = null;
public object PropertyValue
{
get { return this.Property?.GetValue(this.Instance, null); }
}
#endregion
#region Private methods
private void CheckProperty()
{
if (this.Instance == null || string.IsNullOrEmpty(this.PropertyName))
{
this.Property = null;
}
else
{
this.Property = this.Instance.GetType().GetProperty(this.PropertyName);
}
this.RaisePropertyChanged(nameof(PropertyValue));
}
private void Instance_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == this.PropertyName)
{
this.RaisePropertyChanged(nameof(PropertyValue));
}
}
private void RaisePropertyChanged(string propertyname)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
#endregion
}
and here is the XAML:
<ItemsControl ItemsSource={Binding Path=InstanceProperties}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=PropertyName, Mode=OneWay}"/>
<TextBlock Text=": "/>
<TextBlock Text="{Binding Path=PropertyValue, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

WPF - Dependency Property of Custom Control lost Binding at 2 way mode

I have this Custom Control
XAML:
<UserControl x:Class="WpfApplication1.UC"
...
x:Name="uc">
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Horizontal">
<TextBox Text="{Binding Test, ElementName=uc}" Width="50" HorizontalAlignment="Left"/>
</StackPanel>
</UserControl>
C#
public partial class UC : UserControl
{
public static readonly DependencyProperty TestProperty;
public string Test
{
get
{
return (string)GetValue(TestProperty);
}
set
{
SetValue(TestProperty, value);
}
}
static UC()
{
TestProperty = DependencyProperty.Register("Test",typeof(string),
typeof(UC), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
}
public UC()
{
InitializeComponent();
}
}
And this is how i used that custom control:
<DockPanel>
<ItemsControl ItemsSource="{Binding Path=DataList}"
DockPanel.Dock="Left">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" CommandParameter="{Binding}" Click="Button_Click"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<local:UC Test="{Binding SelectedString, Mode=OneWay}"/>
</DockPanel>
--
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
private ObservableCollection<string> _dataList;
public ObservableCollection<string> DataList
{
get { return _dataList; }
set
{
_dataList = value;
OnPropertyChanged("DataList");
}
}
private string _selectedString;
public string SelectedString
{
get { return _selectedString; }
set
{
_selectedString = value;
OnPropertyChanged("SelectedString");
}
}
public MainWindow()
{
InitializeComponent();
this.DataList = new ObservableCollection<string>();
this.DataList.Add("1111");
this.DataList.Add("2222");
this.DataList.Add("3333");
this.DataList.Add("4444");
this.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.SelectedString = (sender as Button).CommandParameter.ToString();
}
}
If I do not change text of UC, everything is ok. When I click each button in the left panel, button's content is displayed on UC.
But when I change text of UC (ex: to 9999), Test property lost binding. When I click each button in the left panel, text of UC is the same that was changed (9999). In debug I see that SelectedString is changed by each button click but UC's text is not.
I can 'fix' this problem by using this <TextBox Text="{Binding Test, ElementName=uc, Mode=OneWay}" Width="50" HorizontalAlignment="Left"/> in the UC.
But I just want to understand the problem, can someone help me to explain it please.
Setting the value of the target of a OneWay binding clears the binding. The binding <TextBox Text="{Binding Test, ElementName=uc}" is two way, and when the text changes it updates the Test property as well. But the Test property is the Target of a OneWay binding, and that binding is cleared.
Your 'fix' works because as a OneWay binding, it never updates Test and the binding is never cleared. Depending on what you want, you could also change the UC binding to <local:UC Test="{Binding SelectedString, Mode=TwoWay}"/> Two Way bindings are not cleared when the source or target is updated through another method.
The issue is with below line
<local:UC Test="{Binding SelectedString, Mode=OneWay}"/>
The mode is set as oneway for SelectString binding so text will be updated when the value from code base changes. To change either the source property or the target property to automatically update the binding source as TwoWay.
<local:UC Test="{Binding SelectedString, Mode=TwoWay}"/>

WPF: Visibility binding, event = null

I have the following code:
public event EventHandler LoadingControlVisibilityChanged;
public Visibility LoadingControlVisibility
{
get { return _LoadingControlVisibility; }
set
{
_LoadingControlVisibility = value;
if (LoadingControlVisibilityChanged != null)
LoadingControlVisibilityChanged(this, EventArgs.Empty);
}
}
<Label x:Name="loading" Visibility="{Binding Path=LoadingControlVisibility, Mode=OneWay}" Content="No Devices Detected!" FontFamily="{DynamicResource AppFont}" HorizontalAlignment="Left" Margin="110,0,0,0" FontSize="21.333" />
The first time the binding work, but after I change the LoadingControlVisibility nothing happens, after debug I notice that the event = null. Please help me solve this problem.
my text property works with no problems:
public event EventHandler UUidChanged;
public string UUid
{
get { return _uuid; }
set
{
_uuid = value;
if (UUidChanged != null) UUidChanged(this, EventArgs.Empty);
}
}
<TextBox Text="{Binding Path=UUid, Mode=OneWay}" Margin="122.48,11.26,9,0" TextWrapping="Wrap" VerticalAlignment="Top" FontSize="{DynamicResource MediumFontSize}" FontFamily="{DynamicResource AppFont}" Template="{DynamicResource TxtBoxTemplate}" Height="25" >
why is this different ?
The Binding statement will not look for the event you have defined. You must implement INotifyPropertyChanged instead.
I'm not quite certain what you're trying to accomplish. My understanding is that you are trying to bind the visibility of your label to a Property named LoadingControlVisibility that is defined in another class. If that is the case, then your path is wrong in the binding. Your binding should be as follows: Visibility="{Binding LoadingControlVisibility}"

Get name of selected listbox item (WPF C#)?

all. I have an app that scans a picture folder and displays the images along with their names in a listbox. Each image and image name (displayed in a textblock next to the image) is stored in a horizontal stackpanel inside the listbox.
I've been trying all afternoon to find a way of displaying the image name in a textbox when the user selects it in the listbox. Sounds very simple, and I'm sure it is, but I can't seem to get it to work.
Can anyone point me in the right direction as to the best way of doing this? Thanks.
Here is my xaml if it helps:
<Grid>
<ListBox ItemsSource="{Binding AllImages}" Margin="0,0,262,0" Name="listBox1" MouseLeftButtonDown="listBox1_MouseLeftButtonDown">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Image}" Width="50" Height="50" Margin="6"/>
<TextBlock Text="{Binding Name}" Margin="6" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox Height="23" HorizontalAlignment="Left" Margin="265,148,0,0" Name="textBox1" VerticalAlignment="Top" Width="198" />
</Grid>
And my code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public class MyImage
{
private ImageSource _image;
private string _name;
public MyImage(ImageSource image, string name)
{
_image = image;
_name = name;
}
public override string ToString()
{
return _name;
}
public ImageSource Image
{
get { return _image; }
}
public string Name
{
get { return _name; }
}
}
public List<MyImage> AllImages
{
get
{
List<MyImage> result = new List<MyImage>();
string filePath = #"D:\Projects\Visual Studio 2010\WpfApplication5\WpfApplication5\bin\Debug\ImageFolder";
string[] files = Directory.GetFiles(filePath);
foreach (string filename in files)
{
try
{
result.Add(
new MyImage(
new BitmapImage(
new Uri(filename)),
System.IO.Path.GetFileNameWithoutExtension(filename)));
}
catch { }
}
return result;
}
}
}
Take a look at this question.
How do I bind a Listview SelectedItem to a Textbox using the TwoWay mode?
In your case use
<TextBox Height="23"
HorizontalAlignment="Left"
Margin="265,148,0,0"
Name="textBox1"
VerticalAlignment="Top" Width="198"
Text="{Binding SelectedItem.Name, ElementName=listBox1}"/>
To retrieve the selected image from code, you have at least 3 options (I assume your images are represented by a class ImageItem)
Set IsSynchronizedWithCurrentItem to true on your ListBox, and use the following code to retrieve the selected item:
ICollectionView view = CollectionViewSource(AllImages);
ImageItem selectedImage = (ImageItem)view.CurrentItem;
Bind the SelectedItem of the ListBox to a property in your DataContext:
<ListBox .... SelectedItem="{Binding SelectedImage}">
Access the SelectedItem property directly from code-behind:
ImageItem selectedImage = (ImageItem)listBox1.SelectedItem;
Of course, if you just want to show the name in a TextBlock, you can use Russell Troywest's solution

Resources