How to get value of XAML Control from DataTemplate - c#

I try to get a value from my TextBox on a button click event which is defined in my XAML data template as the following:
<GridView.ItemTemplate>
<DataTemplate x:DataType="data:Ausstattung">
<Grid Height="40" Width="Auto" Background="LightSlateGray" >
<TextBlock Grid.Column="0" Foreground="White" FontSize="14" Text="{x:Bind Beschreibung}" HorizontalAlignment="Center" VerticalAlignment="Center" TextTrimming="CharacterEllipsis"/>
<TextBlock Grid.Column="1" Foreground="White" FontSize="14" Text="{x:Bind Ausgabedatum}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock Grid.Column="2" Foreground="White" FontSize="14" Text="{x:Bind Rückgabedatum}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBox Grid.Column="3" Foreground="White" FontSize="14" x:Name="txtAnzahl" PlaceholderText="{x:Bind Anzahl}" TextChanged="TextBox_OnTextChanged" Width="50" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<StackPanel Orientation="Horizontal" Grid.Column="4" Margin="-25">
//here I want to get the value from the TextBox named "txtAnzahl"
<Button Height="30" Width="30" Margin="0,10,10,10" Padding="0" Click="ButtonBase_OnClick">
<TextBlock FontFamily="Segoe MDL2 Assets" Foreground="LimeGreen" Text="" FontSize="20"/>
</Button>
<Button Height="30" Width="30" Margin="0,10,10,10" Padding="0">
<TextBlock FontFamily="Segoe MDL2 Assets" Foreground="DarkRed" Text="" FontSize="16"/>
</Button>
</StackPanel>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
So I try to get the value from the Textbox "txtanzahl" on the Button OnClick event.
Here is what it looks like in the live visual tree:
I tried to accomplish it with the VisualTreeHelper but I only found examples of GetChild or GetParent but in this case, it is not a child nor a parent.
Also I cannot get the control with the given name "txtAnzahl" like this:
var anzahl = txtAnzahl.Text;
It says that he didn´t know this element.

You can get the Button's parent (i.e. StackPanel), and then its parent's parent (i.e. Grid), and then go down and find the TextBox.
But... Don't do this. What if you changed the hierarchy, or the type of the Panel?
Since you already know the type (i.e. Ausstattung) of the DataContext of your data template, you should create another property say TextValue and have it two-way bound with the TextBox. Then, you can either get its value from a CommandParameter if you use Button's Command, or in code-behind -
private void ButtonBase_Click(object sender, RoutedEventArgs e)
{
var button = (ButtonBase)sender;
var dataContext = (Ausstattung)button.DataContext;
var value = dataContext.TextValue;
}
Your class needs to implement INotifyPropertyChanged. After that, create a new property like this -
using System.ComponentModel;
using System.Runtime.CompilerServices;
using App1.Annotations;
namespace App1
{
public class Ausstattung : INotifyPropertyChanged
{
private string _textValue;
public string TextValue
{
get => _textValue;
set
{
_textValue = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In your xaml, do this -
<TextBox Text="{x:Bind TextValue, Mode=TwoWay}" Grid.Column="3" Foreground="White" FontSize="14" x:Name="txtAnzahl" PlaceholderText="{x:Bind Anzahl}" TextChanged="TextBox_OnTextChanged" Width="50" HorizontalAlignment="Center" VerticalAlignment="Center"/>

The XAML inside a DataTemplate is a bit special; while it's only written once, at runtime it will be copied and used for each item in the list. This makes it hard to reference the correct element inside the data template. If you have 20 items in the list txtAnzahl might refer to any of them.
The best solution is to put the content inside the data template in a separate UserControl. That way you can reference elements and bind as you normally would.
You can read a bit about UserControl in the official docs.

Related

Add Controls in MVVM way but don't declare control type straight

Point-black question.
Is it possible to add/generate controls in MVVM way without control type declaration in XAML code ? Well it's hard to put that in a words so let's take an example:
There is Controls table in SQL Server, which has columns like this:
1. ControlName
2. ControlType
3. ControlBinding (?)
Now, in my ViewModel I'm declaring an ObservableCollection<T> which would be collection of those controls I would like to have, and populating it from Controls table. By binding to ItemsSource of course.
As far as I know it's simple to add controls dynamically, but only when we declare type straight e.g :
<StackPanel>
<ItemsControl ItemsSource="{Binding SomeCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=.}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
Is there any way to do same thing, but taking control type from database ?
I had to create DataTemplates and implement DataTemplateSelector.
<DataTemplate x:Key="TextBoxDataTemplate">
<Grid VerticalAlignment="Top" HorizontalAlignment="Left">
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Width="200" FontSize="15" FontWeight="Bold" Text="{Binding ControlName}" TextWrapping="Wrap"/>
<TextBox VerticalAlignment="Top" HorizontalAlignment="Left" Margin="205,0,0,0" Width="616" Height="35" FontSize="12" Text="{Binding ControlValue,UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="ComboBoxDataTemplate">
<Grid VerticalAlignment="Top" HorizontalAlignment="Left">
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Width="200" FontSize="15" FontWeight="Bold" Text="{Binding ControlName}" TextWrapping="Wrap"/>
<ComboBox VerticalAlignment="Top" HorizontalAlignment="Left" Margin="205,0,0,0" Width="616" Height="35" FontSize="15"/>
</Grid>
</DataTemplate>
And ControlTemplateSelector
public class ControlTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var element = container as FrameworkElement;
var control = item as ControlModel;
if (element != null & control != null)
{
if (control.ControlType.Equals("TextBox"))
{
return element.FindResource("TextBoxDataTemplate") as DataTemplate;
}
else
{
return element.FindResource("ComboBoxDatatemplate") as DataTemplate;
}
}
return null;
}
}
Then I just bound ItemsSource and ItemTemplateSelector in my ItemsControl just like this:
<ItemsControl ItemsSource="{Binding ControlsCollection}" ItemTemplateSelector="{StaticResource ControlTemplateSelector}"/>
ControlsCollection is populated from database.

DependencyProperty in Windows Runtime, XAML values appear empty

I have a simple UserControl in a XAML Windows RT app. The DependencyProperty appears to be set just fine, and breakpoints in the code show the target property of 'TwitterMessage' gets the correct object at runtime, after the 'IsLoaded' event. The problem is, the binding values inside the UserControl's child controls are empty when the XAML is rendered. It's like its ignoring the values of the TwitterMessage property. Any ideas? Intellisense seems to think I'm doing it right as it autocompletes the bindings in the markup.
The Usercontrol is given a simple invocation like so:
<local:TwitterMessageUserControl TwitterMessage="{Binding Message}" Grid.Column="2" />
UserControl Markup is as follows:
<UserControl
x:Class="TwitterMessageUserControl"
...
x:Name="root">
<Border Height="85" Width="400" BorderBrush="{ThemeResource ApplicationSecondaryForegroundThemeBrush}" BorderThickness="1" >
<StackPanel Background="{ThemeResource AppBarItemForegroundThemeBrush}" Orientation="Horizontal">
<Image HorizontalAlignment="Left" Height="73" x:Name="Avatar" VerticalAlignment="Top" Width="73" Margin="10,5,0,0" Source="{Binding ElementName=root, Path=TwitterMessage.Tweet.AuthorAvatarUrl}"/>
<StackPanel Margin="15,0,0,0">
<TextBlock HorizontalAlignment="Left" x:Name="TweetText" TextWrapping="Wrap" Text="{Binding ElementName=root, Path=TwitterMessage.Tweet.Message, Mode=OneWay}" VerticalAlignment="Top" Foreground="Black" Width="300" Margin="0,10,0,0" FontSize="12"/>
<TextBlock TextWrapping="Wrap" Text="— #someone via Twitter" x:Name="TweetFromText" Foreground="Black" Margin="0,5,0,0"/>
</StackPanel>
</StackPanel>
</Border>
And codebehind is as follows:
public sealed partial class TwitterMessageUserControl : UserControl
{
public static readonly DependencyProperty TwitterMessageProperty =
DependencyProperty.Register("TwitterMessage", typeof(TweetMessage), typeof(TwitterMessageUserControl), new PropertyMetadata(null));
public TweetMessage TwitterMessage
{
get { return (TweetMessage)GetValue(TwitterMessageProperty); }
set { SetValue(TwitterMessageProperty, value); }
}
public TwitterMessageUserControl()
{
this.InitializeComponent();
this.Loaded += TwitterMessageUserControl_Loaded;
}
void TwitterMessageUserControl_Loaded(object sender, RoutedEventArgs e)
{
DataContext = this;
}
}
I recreated the app quickly. When running the app I noticed this error in the debug output:
Error: BindingExpression path error: 'Message' property not found on
'App4.TwitterMessageUserControl'. BindingExpression: Path='Message'
DataItem='App4.TwitterMessageUserControl'; target element is
'App4.TwitterMessageUserControl' (Name='root'); target property is
'TwitterMessage' (type 'TweetMessage')
The problem is that when you do DataContext = this; in the TwitterMessageUserControl_Loaded it will look for a Message property on itself instead of the DataContext set in your view. Comment out this line and it should work fine:
Additionally if you want to simplify the bindings further in the UserControl XAML you can bind to the root in the Border, and then just bind without specifying the ElementName for every binding inside that, like this:
<Border DataContext="{Binding ElementName=root}" Height="85" Width="400" BorderBrush="{ThemeResource ApplicationSecondaryForegroundThemeBrush}" BorderThickness="1" >
<StackPanel Background="{ThemeResource AppBarItemForegroundThemeBrush}" Orientation="Horizontal">
<Image HorizontalAlignment="Left" Height="73" x:Name="Avatar" VerticalAlignment="Top" Width="73" Margin="10,5,0,0" Source="{Binding TwitterMessage.Tweet.AuthorAvatarUrl}"/>
<StackPanel Margin="15,0,0,0">
<TextBlock HorizontalAlignment="Left" x:Name="TweetText" TextWrapping="Wrap" Text="{Binding TwitterMessage.Tweet.Message}" VerticalAlignment="Top" Foreground="Black" Width="300" Margin="0,10,0,0" FontSize="12"/>
<TextBlock TextWrapping="Wrap" Text="— #someone via Twitter" x:Name="TweetFromText" Foreground="Black" Margin="0,5,0,0"/>
</StackPanel>
</StackPanel>
</Border>

Dynamically control the number of buttons in WPF

I'm working with WPF recently. Now, i'm facing a problem.
I have a button "ADD", every time click on this will add a new row with some contents. Those contents are shown below-
<TextBox Text="{Binding Name}" Margin="10,10" Height="20" ></TextBox>
<TextBox Text="{Binding City}" Margin="10,10" Height="20" ></TextBox>
<TextBox Text="{Binding Age}" Margin="10,10" Height="20"></TextBox>
<TextBox Text="{Binding Count}" Margin="10,10" Height="20" ></TextBox>
<Button Content="M1" Margin="10,10" Height="20"/>
<Button Content="M2" Margin="10,10" Height="20"/>
<Button Content="M3" Margin="10,10" Height="20"/>
</WrapPanel>
</Grid>
Here, at the end there are three buttons M1,M2,M3. But, I don't need this all three buttons every time. I may need only M1 or only M2 or only M3 or M1,M2 etc.
How can I do this in c#?
Actually i don't even know, am i in the right way?
Thanks in advance.
I would highly recommend looking into the MVVM design pattern when working with WPF
That said, I would bind my XAML to an ObservableCollection<SomeObject>, and clicking the AddButton would add a new SomeObject to the ObservableCollection. This would make the UI automatically add the new row when the collection gets updated, and SomeObject could have properties for IsM1Visible, IsM2Visible, and IsM3Visible which determines which buttons are visible.
For example,
Class SomeObject would have
string Name;
string City;
int Age;
int Count;
bool IsM1Visible;
bool IsM2Visible;
bool IsM3Visible;
The XAML would look something like this:
<ItemsControl ItemsSource="{Binding SomeCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBox Text="{Binding Name}" Margin="10,10" Height="20" ></TextBox>
<TextBox Text="{Binding City}" Margin="10,10" Height="20" ></TextBox>
<TextBox Text="{Binding Age}" Margin="10,10" Height="20"></TextBox>
<TextBox Text="{Binding Count}" Margin="10,10" Height="20" ></TextBox>
<Button Content="M1" Visibility="{Binding IsM1Visible, Converter="{StaticResource BooleanToVisibilityConverter}" Margin="10,10" Height="20"/>
<Button Content="M2" Visibility="{Binding IsM2Visible, Converter="{StaticResource BooleanToVisibilityConverter}" Margin="10,10" Height="20"/>
<Button Content="M3" Visibility="{Binding IsM3Visible, Converter="{StaticResource BooleanToVisibilityConverter}" Margin="10,10" Height="20"/>
</WrapPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And the Add Button's Click event would look something like this:
void AddButton_Click(object sender, EventArgs e)
{
var newItem = new SomeItem
{
Name = "Something",
City = "Something",
Age = 30,
Count = 2,
IsM1Visible = true,
IsM2Visible = false,
IsM3Visible = true
};
SomeCollection.Add(newItem);
}
You could use DataBinding if the amount of buttons is dependent on the size of a list/collection.
<ItemsControl ItemsSource={Binding Ms}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}" Command={Binding ThingToDoWhenClickedCommand}/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This will generate exactly the amount of buttons that is in the Ms collection/List that is in the DataContext.
You can just add the buttons through your code-behind files and put an if structure around the buttons to determine what button should be loaded.
Are you using MVVM?
If so then this is easy. Just create a ButtonViewModel object that represents one of your buttons and expose an ObservableCollection of them from your main ViewModel.
In your view just have a ListView, or just an ItemsControl bound to the collection, and a DataTemplate that turns your ButtonViewModel into a button.
When the user clicks the Add button, add a new ButtonViewModel to your collection and the view will update itself to match.
First, let create a view model for a row. In that model you will have 3 bool properties, IsButton1Visible, IsButton2Visible, IsButton3Visible, or something like that with all properties you need to do binding on your row.
Second, your scenario is that when you click Add, new row will be added. So you have a list of Row_View_Model. On AddCommand, you will at a new Row_View_Model into the list. Here you have full control on what button you want to show.

Getting Listbox Value WP7

how to get value from the textblock , which is present in listbox ....
here is the code xaml :
<ListBox Height="707" HorizontalAlignment="Left" Margin="12,0,0,0" Name="listBox1" VerticalAlignment="Top" Width="456" Background="White" Foreground="#FF09090C" ItemsSource="{Binding}" SelectionChanged="listBox1_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Name="textBlock3" FontSize="18" Foreground="Blue" Margin="2" Text="{Binding Title.Text}" TextWrapping="Wrap" />
<TextBlock FontSize="16" Foreground="Gray" Margin="2" Text="{Binding Summary.Text}" TextWrapping="Wrap" />
<TextBlock FontSize="1" Foreground="Gray" Margin="2" Text="{Binding Id}" TextWrapping="Wrap" Visibility="Collapsed" />
<Button Name="h1" Content="Press" Height="10" Width="40"></Button>
<TextBlock Foreground="Gray" Margin="2" Text="________________________________________________________________________________________" FontSize="8"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You don't get the value directly from the TextBlock.
Instead what you do is bind the SelectedItem to a property on your viewmodel. To get the value of the TextBlock directly would violate the priciples of MVVM (if you are using that pattern). The viewmodel presents the data, it has no clue how that data is being rendered to the UI. IOW it has no idea there are three TextBlocks.
<ListBox Height="707" SelectedItem={Binding MyViewModelProperty} >
... etc ...
This means that every time the selected item is changed, the new value will be populated into the bound property on the viewmodel. All you have to do then is access that object - it's as easy as that. This means you may also possibly be able to get rid of your SelectionChanged event hookup, depending on what it is doing.
However if you insist on getting the instance of the template used to present any particular data item in a list control, then this is the way to do it programmatically:
myListBox.ItemContainerGenerator.ContainerFromItem(myDataItem);
This will return you the StackPanel and its contents, you can then either use FindName() or just enumerate the child controls to find the one you are interested in.
FrameworkElement element = myListBox.ItemContainerGenerator.ContainerFromItem(myDataItem) as FrameworkElement;
if (element != null)
FrameworkElement child = element.FindName("myChildName");

Accessing other listbox item controls and properties on button click

I'm currently binding an xml file to a listbox in C# WPF. Within the itemtemplate I added controls. as follows:
<DataTemplate x:Key="SinglecueTemplate">
<Grid Height="30" Width="425" Margin="3,3,0,3">
<Button Content="{Binding XPath=nr}" Width="30" Style="{DynamicResource CUEStyle_Button_Inhoudknopje}" Template="{DynamicResource CUEStyle_singlecueknopnummer}" Height="Auto" HorizontalAlignment="Left" Background="#FFABCCED" Foreground="White" IsEnabled="False"/>
<TextBlock x:Name="name" Margin="54,0,114.667,0" Width="Auto" VerticalAlignment="Center" FontSize="16" Foreground="Gray" Text="{Binding XPath=Name}"/>
<Button x:Name="playbutton" Width="30" Style="{DynamicResource CUEStyle_Button_Groot}" Template="{DynamicResource CUEStyle_Rondknopje}" Height="Auto" HorizontalAlignment="Right" Margin="0,0.55,74.737,-0.55" Content="u" FontFamily="Wingdings 3" Foreground="#FF0178D3" Opacity="1" BorderBrush="#FF0178D3" Click="playcue"/>
<Button Width="30" Style="{DynamicResource CUEStyle_Button_Groot}" Template="{DynamicResource CUEStyle_Rondknopje}" Height="Auto" HorizontalAlignment="Right" Margin="0,0.55,37.071,-0.55" Content="¢" FontFamily="Wingdings 2" Foreground="Gray" Opacity="0.4"/>
<Button Width="15" Style="{DynamicResource CUEStyle_Button_Groot}" Template="{DynamicResource CUEStyle_kleinloopje}" Height="15" HorizontalAlignment="Left" Margin="15,0,0,-5.5" Content="Q" FontFamily="Wingdings 3" Foreground="White" FontWeight="Bold" FontSize="10.667" Opacity="1" VerticalAlignment="Bottom" BorderBrush="{x:Null}" Background="{x:Null}" IsEnabled="{Binding XPath=Loop}"/>
<TextBlock Margin="0,0,114.667,0" Width="81.07" VerticalAlignment="Center" FontSize="16" Foreground="Gray" Text="03:02:11" HorizontalAlignment="Right"/>
</Grid>
</DataTemplate>
Now when a the playbutton is clicked I want to acces other properties from that listboxitem, for example the text from the text block. I looked up on how to do this and came up with the following:
private void playcue(object sender, System.Windows.RoutedEventArgs e)
{
Button playcue = (Button)sender;
Textbox titel = (Textbox)playcue.DataContext;
MessageBox.Show(titel);
}
But the code above gives me an error stating that the type Textbox is unknown. Do I have to do something with the datatemplate so that I can acces other items within the template? Or is it possible to access sibling nodes from the datasource?
UPDATE
Answer found. Now for future reference:
private void playcue(object sender, System.Windows.RoutedEventArgs e)
{
Button playcue = (Button)sender;
XmlElement name = (XmlElement)playcue.DataContext;
MessageBox.Show(name.InnerText);
}
Returns all sibling values from the item
If you want to access a specific sibling you can do so:
MessageBox.Show(name.SelectSingleNode("Name").InnerXml);
First problem is that it's TextBox not Textbox (C# is case sensitive.)
Second, it's unlikely that your DataContext is a TextBox. It should be an item in your ItemsSource collection. So, if the user clicks the third button in the list, you should be looking at the 3rd item in your ItemsSource collection. Since it appears you are using XML as a data source, I would guess that the item should be an XElement or an XmlElement.
I suggest you change Textbox titel = (Textbox)playcue.DataContext; to the following:
XElement element = (XElement)playcue.DataContext;
or
XmlElement element = (XmlElement)playcue.DataContext;
You will also need to change your MessageBox statement as well.

Categories