How to access controls (buttons) inside DataTemplate in c# XAML - c#

I'm trying to make a funny comic/meme app like funnyjunk.com, which contains a dislike and a like buttons on every comic, like this : http://s9.postimg.org/ikiyo7iy7/funnyjunk.png
My problem is: I can't get access to code the dislike/like buttons inside the DataTemplate from code behind. Is there any way to access buttons inside DataTemplate?
I'm using ListView in this case, and here is how my DataTemplate looks like :
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Vertical">
<StackPanel>
<Image Source="{Binding Image}" Height="600" Width="800" Stretch="UniformToFill"/>
</StackPanel>
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<Button x:Name="blike" Content="L" FontSize="70" HorizontalAlignment="Center" VerticalAlignment="Center" Click="blike_Click"/>
<TextBlock x:Name="tblrate" Text="0" FontSize="70" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Button x:Name="bdislike" Content="D" FontSize="70" HorizontalAlignment="Center" VerticalAlignment="Center" Click="bdislike_Click"/>
</StackPanel>
</StackPanel>
and Here is the codes for the buttons:
private void blike_Click(object sender, RoutedEventArgs e)
{
int rate = Convert.ToInt16(tblrate.Text);
rate += 1;
tblrate.Text = Convert.ToString(rate);
}
private void bdislike_Click(object sender, RoutedEventArgs e)
{
int rate = Convert.ToInt16(tblrate.Text);
rate -= 1;
tblrate.Text = Convert.ToString(rate);
}
Here's my DataContext (I named it "DataSource"):
public class DataSource
{
public int ID { get; set; }
public string Title { get; set; }
public string Image { get; set; }
public DataSource(int _ID, string _Image, string _Title)
{
ID = _ID;
Image = _Image;
Title = _Title;
}
}
public class DataFill
{
public List<DataSource> Comics= new List<DataSource>();
public void MainPageComics()
{
Comics.Add(new DataSource(1, "/Assets/Comic1.jpg", "Jokur and Botmon"));
Comics.Add(new DataSource(2, "/Assets/Comic2.jpg", "Jokur and Botmon2"));
}
}
What I'm trying to achieve is:
The dislike/like buttons to work in EACH and every comic that is BOUND inside the ListView, so it will change the value of the dislike/like RATE of the comic.
What I have tried:
the 1st answer Jerry Nixon's tutorial (it only works for textbox not textblock, i don't understand why)
I'm still trying to understand to use the Command property, which is very complex.
I'm still looking for tutorials to use ICommands, if you have a tutorial video that would be very helpful.
I'm new to WPF programming, thank you in advance.

Related

Retrieve object from tapped stackpanel element

As the title suggests, I am struggling with retrieving an element from a stackpanel list when tapping it in a simple UWP application. The stackpanel has its itemsource connected to a list of "Customers" which I am then displaying
ObservableCollection<Customer> customers = new ObservableCollection<Customer>();
// Create a new ListView (or GridView) for the UI, add content by setting ItemsSource
ListView customersLV = new ListView();
customersLV.ItemsSource = customers;
// Add the ListView to a parent container in the visual tree (that you created in the corresponding XAML file)
customerPanel.Children.Add(customersLV);
The XAML-code looks like this (Added a scrollviewer for longer lists):
<ScrollViewer VerticalScrollBarVisibility="auto" HorizontalScrollBarVisibility="auto" Margin="63,341,1043,368" >
<StackPanel x:Name="customerPanel" Height="441" Width="394" DoubleTapped="customerPanel_DoubleTapped" ></StackPanel>
</ScrollViewer>
While adding and removing items from the list works great, I cannot seem to access any particular Customer-object from the listpanel when double tapping it.
Here is my doubletap event function:
private void customerPanel_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
testText.Text = e.OriginalSource.ToString();
}
This seems to only print a reference to the whole stackpanel and not to the specific object that I double-tapped. How can I access the tapped Customer-object if I, for example, wanted to call its ToString-method?
Thank you for your time :)
It seems that you just want to get the item value when double click the ListViewItem, so you just need to write a ListView, instead of using ScrollViewer, StackPanel, etc.
So we can write xaml code like:
<Grid>
<ListView x:Name="listView" DoubleTapped="listView_DoubleTapped">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Customer">
<StackPanel Margin="0 10" Orientation="Horizontal">
<Image Width="50" Height="50" Source="{x:Bind Head, Mode=OneWay}"/>
<TextBlock Margin="30 0 0 0" FontSize="25" Text="{x:Bind Name, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Then we define a Customer class.
public class Customer
{
public string Head { get; set; }
public string Name { get; set; }
}
OK, we have done half. Next we are going to create a data collection, and give it to ListView's ItemSource.
public MainPage()
{
this.InitializeComponent();
this.Loaded += MainPage_Loaded;
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
ObservableCollection<Customer> list = new ObservableCollection<Customer>();
for (int i = 0; i < 100; i++)
{
Customer p = new Customer()
{
Head = "https://learn.microsoft.com/zh-cn/visualstudio/releases/2019/media/2019_rc_logo.png",
Name = i.ToString()
};
list.Add(p);
}
listView.ItemsSource = list;
}
Final step is to complete the DoubleTapped event.
private void listView_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
var customer = listView.SelectedItem as Customer;
Debug.WriteLine(customer.Name);
}
Done!!!
Solved it now I think!
I simply put a listView within my StackPanel like this.
<ScrollViewer VerticalScrollBarVisibility="auto" HorizontalScrollBarVisibility="auto" Margin="63,341,1043,368" >
<StackPanel x:Name="customerPanel" Height="441" Width="394">
<ListView x:Name="customersLV" Tapped="listView_Tapped">
</ListView>
</StackPanel>
</ScrollViewer>
The CS code looked like this:
ObservableCollection<Customer> customers;
public MainPage()
{
this.InitializeComponent();
customers = new ObservableCollection<Customer>();
customersLV.ItemsSource = customers;
}
private void listView_Tapped(object sender, TappedRoutedEventArgs e)
{
testBox.Text = customersLV.SelectedItem.ToString();
}
This will print out the string representation of the actual Customer object in my testBox textblock :)

Data Binding does not Update on Property Change (UWP)

I am developing an application using c# and the Universal Windows Platform (UWP) and am struggling with creating a one-way data-bind between a layout control and an observable class. Currently, when the observable class property is changed, it does not update the UI element. I think it has something to do with the fact that I am binding a DataTemplate ListViewItem rather than a static layout element, but I am not sure if this is the problem or how to solve it. Any help would be appreciated. The code for the UI element and backend code is shown.
DataTemplate (XAML) (Styling is removed for readability)
<DataTemplate x:Key="variableTemplate"
x:DataType="local:VariableNode">
<Border>
<StackPanel Orientation="Vertical">
<Border>
<Grid>
<TextBlock Text="{Binding Name}" />
<StackPanel Orientation="Horizontal" >
<Button Tag="{Binding Description}"/>
<Button Tag="{Binding}"/>
</StackPanel>
</Grid>
</Border>
<Grid Margin="0, 10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Border >
<Grid Grid.Column="0">
<Button Click="Choose_Measurement"
Tag="{Binding}">
<StackPanel Orientation="Vertical">
<TextBlock Text="{x:Bind Path=Measurement_Name, Mode=TwoWay}"
Foreground="{x:Bind MF}" />
<TextBlock Foreground="{x:Bind MF}" />
</StackPanel>
</Button>
</Grid>
</Border>
<Grid Grid.Column="1">
<Button Foreground="{Binding UF}"
Tag="{Binding}"
IsEnabled="{Binding Unit_Exists}"
Click="Choose_Unit">
<StackPanel Orientation="Vertical">
<TextBlock Text="{x:Bind Path=Unit_Name, Mode=OneWay}"
Foreground="{Binding UF}" />
<TextBlock Foreground="{Binding UF}" />
</StackPanel>
</Button>
</Grid>
</Grid>
</StackPanel>
</Border>
</DataTemplate>
C# Observable Class VariableNode (Irrelevant properties removed)
public class VariableNode : ExperimentNode
{
public VariableNode() { }
public VariableNode(VariableType type)
{
Type = type;
Name = name_ref[(int)Type];
Category = "Problem";
Unit = -1;
}
private string[] name_ref = { "Independent Variable", "Dependent Variable", "Controlled Variable" };
public enum VariableType { Independent, Dependent, Controlled };
public VariableType Type { get; set; }
public Measurement Measure { get; set; }
public int Unit { get; set; }
[XmlIgnoreAttribute]
public Measurement MeasureSource
{
get { return this.Measure; }
set
{
this.Measure = value;
OnPropertyChanged("Measurement_Name");
}
}
[XmlIgnoreAttribute]
public string Measurement_Name
{
get
{
if (Measure == null) { return "Select a Measurement"; }
else { return Measure.Name; }
}
set
{
if (Measure != null)
{
Measure.Name = value;
OnPropertyChanged();
}
}
}
[XmlIgnoreAttribute]
public string Unit_Name
{
get
{
if (Measure == null) { return "No measurement"; }
else if (Unit < 0) { return "Select a unit"; }
else { return Measure.Unit[Unit]; }
}
}
[XmlIgnoreAttribute]
public bool Unit_Exists
{
get { return Measure != null; }
}
}
C# XAML.CS code calling the property change
public void Choose_Measurement (object sender, RoutedEventArgs e)
{
Button butt = sender as Button
VariableNode sel = butt.Tag as VariableNode;
sel.Measurement_Name = "New Name";
}
Again thanks for the help, I know its a lot of code, and I appreciate the help in debugging / learning.
Ok, so I ended up finding the answer, and I think that it may help others trying to replicate what I am trying to do:
Basically, the class that one is trying to make observable must extend the class INotifyPropertyChanged. So, I ended up making a base class from which to extend all of my observable classes from:
public class BaseClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged(this, e);
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}

WPF Validating TextBox in an outside library

what I am trying to do is somewhat out there, and I have yet to really see an example of this.
I am trying to validate a textbox entry that is essentially a required field (it cannot be null or empty). However, I do not have any access to the code behind, only to the XAML and data binding for the form.
From searching for a couple of days, I found out this cannot be done strictly in XAML (which would have been preferred), and had to create my own resource library to check for this. That is what I have done, but failed to get it to work.
Is this even a possibility? Or what would I have to do to get this to work?
What I have done so far was create a usercontrol template of a textbox to then use in the XAML (residing in an outside library):
<UserControl.Resources>
<Style x:Key="TextBoxStyle" TargetType="TextBox">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder Name="MyAdorner"/>
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<DockPanel x:Name="dpMain" LastChildFill="True">
<Label/>
</DockPanel>
</Grid>
And the code behind:
namespace ClassLibrary.CustomControls
{
public partial class CssTextBox : UserControl
{
private TextBox _textbox = null;
private ObservableCollection<ValidationRule> _validationRules = null;
public CssTextBox()
{
InitializeComponent();
CreateControls();
ValidationRules = new ObservableCollection<ValidationRule>();
this.DataContextChanged += new DependencyPropertyChangedEventHandler(CssTextBoxDataChanged);
}
public ObservableCollection<ValidationRule> ValidationRules
{
get { return _validationRules; }
set { _validationRules = value; }
}
private void CreateControls()
{
_textbox = new TextBox() { Width = 100, Height = 20 };
_textbox.LostFocus += CssTextBoxLostFocus;
_textbox.Style = TextBoxErrorStyle;
}
public void CssTextBoxDataChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (_textbox != null)
{
var binding = new Binding();
binding.Source = this.DataContext;
binding.ValidatesOnDataErrors = true;
binding.ValidatesOnExceptions = true;
foreach (var rule in ValidationRules)
{
binding.ValidationRules.Add(rule);
}
binding.Path = new PropertyPath(BoundPropertyName);
_textbox.SetBinding(TextBox.TextProperty, binding);
dpMain.Children.Add(_textbox);
}
}
public void CssTextBoxLostFocus(object sender, RoutedEventArgs e)
{
var bindingExpression = _textbox.GetBindingExpression(TextBox.TextProperty);
if (bindingExpression != null)
bindingExpression.UpdateSource();
}
private Style TextBoxErrorStyle
{
get
{
return (Style)FindResource("TextBoxStyle");
}
}
public string TextBoxErrorStyleName { get; set; }
public string BoundPropertyName { get; set; }
public string ValidationExpression { get; set; }
public string Text
{
get
{
return _textbox.Text;
}
}
public string ErrorText { get; set; }
}
And how it is being used (currently being tested in a WPF Sandbox project and only being referenced via XAML):
xmlns:css="clr-namespace:WpfSandbox.CustomControls" <!--Reference to library that holds above--!>
<css:CssTextBox TextBoxErrorStyleName="TextBoxStyle" Grid.Column="0" Grid.Row="1" Width="100" Height="20" VerticalAlignment="Top" >
<css:CssTextBox.ValidationRules>
<validators:NotNullOrEmptyValidationRule ErrorMessage="Cannot be Empty!" />
</css:CssTextBox.ValidationRules>
</css:CssTextBox>
<TextBox Grid.Column="0" Grid.Row="2" Width="auto" Height="20" VerticalAlignment="Top" Background="White" IsEnabled="True"/>
My issue with what I have now, is that it shows the textbox in my designer window in my sandbox application, but I cannot click into it when I run. It's almost like it does not exist.
Thanks for any insight!
You should read about WPF Data validation.
This link will help you:
https://msdn.microsoft.com/fr-fr/library/system.componentmodel.idataerrorinfo(v=vs.95).aspx

Filling a ListBox from two TextBoxes with DataBinding

I have a ListBox I want to fill with data from two TextBoxesby clicking a Button. I think the problem comes from the differents textblock i have in my listbox. Here is what i want in image :
TheUI
The MainWindow.xaml of my listbox :
<ListBox x:Name="listBox"
ItemsSource="{Binding Issues}" Grid.Column="1" HorizontalAlignment="Left" Height="366" VerticalAlignment="Top" Width="453" Margin="0,0,-1,0">
<StackPanel Margin="3">
<DockPanel >
<TextBlock FontWeight="Bold" Text="Issue:"
DockPanel.Dock="Left"
Margin="5,0,10,0"/>
<TextBlock Text=" " />
<TextBlock Text="{Binding Issue}" Foreground="Green" FontWeight="Bold" />
</DockPanel>
<DockPanel >
<TextBlock FontWeight="Bold" Text="Comment:" Foreground ="DarkOrange"
DockPanel.Dock="Left"
Margin="5,0,5,0"/>
<TextBlock Text="{Binding Comment}" />
</DockPanel>
</StackPanel>
</ListBox>
My MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public sealed class ViewModel
{
public ObservableCollection<Issue> Issues { get; private set; }
public ViewModel()
{
Issues = new ObservableCollection<Issue>();
}
}
private void addIssue_Click(object sender, RoutedEventArgs e)
{
var vm = new ViewModel();
vm.Issues.Add(new Issue { Name = "Jon Skeet", Comment = "lolilol" });
DataContext = vm;
InitializeComponent();
}
}
My Issue.cs :
public sealed class Issue
{
public string Name { get; set; }
public string Comment { get; set; }
}
I follow this tutorial but i don't want to implement a Database :
Tuto
I also try to use this stackoverflow question
The error i have is 'System.InvalidOperationException' The Items collection must be empty to use ItemsSource
But not sure this is the heart of the problem.
Remove whatever you have inserted between <ListBox> and </ListBox>, as it is treated as part of Items collection.
Instead shift that content between <ListBox.ItemTemplate>...</ListBox.ItemTemplate>.
You don't need to update Context and InitializeComponent every time, atleast to your case.
public partial class MainWindow : Window
{
ViewModel vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = vm;
}
public sealed class ViewModel
{
public ObservableCollection<Issue> Issues { get; private set; }
public ViewModel()
{
Issues = new ObservableCollection<Issue>();
}
}
private void addIssue_Click(object sender, RoutedEventArgs e)
{
vm.Issues.Add(new Issue { Name = "Jon Skeet", Comment = "lolilol" });
}
}

How to bind events and properties of controls in UniformGrid dynamically?

I am a newbie in templating Wpf controls. I use VS2013, WPF 4.5 and Caliburn Micro 2.0.2. In part of tasks I have I need to populate a grid with toggle buttons contained different images and its subtitle. I have solved it using UniformGrid. See my code below. They work but still don't have event and property binding since I don't know how I can bind the events and properties of toggle buttons to view model, since they are generated automatically and dynamically and the number of toggle buttons is uncertain (depends on the number of images in the image folder).
For example:
manually I could bind the Click event, IsChecked property and some other properties of toggle button 1 like following:
<ToggleButton x:Name="ToggleVehicle01" IsChecked={Binding SelectedVehicle01} Background="{Binding BackColorSelectedVehicle01}" ToolTip="{Binding VehicleName01}">
But now I can't do that anymore since the toggle buttons are generated automatically and their number is uncertain. Please help. Feel free to change my code below or give me examples code that works. Thank you in advance.
The View (MainView.xaml):
<UserControl x:Class="CMWpf02.Views.MainView"
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"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid Width="1024"
Height="768"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ShowGridLines="True">
<ItemsControl Name="ImageList"
Background="#FFFFFFFF"
BorderBrush="#FFA90606"
ItemsSource="{Binding Path=VehicleImages}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Margin="0,0,0,0" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Width="180"
Margin="10,10,10,10"
FontSize="10"
Style="{StaticResource {x:Static ToolBar.ToggleButtonStyleKey}}">
<!-- x:Name="ToggleVehicle01" -->
<!-- Background="{Binding BackColorSelectedVehicle01}" -->
<!-- IsChecked="{Binding SelectedVehicle01}" -->
<!-- ToolTip="{Binding Vehicle01Name}"> -->
<StackPanel Margin="0,5,0,5"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Image Width="140"
RenderOptions.BitmapScalingMode="Fant"
Source="{Binding Path=Image}" />
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding Path=Name}" />
</StackPanel>
</ToggleButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
The ViewModel (MainViewModel.cs):
using Caliburn.Micro;
using System;
using System.Collections.ObjectModel;
using System.IO;
namespace CMWpf02.ViewModels
{
public class MainViewModel : Screen, IHaveDisplayName
{
private String _path2Images = #"D:\tmp\Images";
public string DisplayName { get; set; }
public ObservableCollection<VehicleImage> VehicleImages { get; set; }
public MainViewModel()
{
DisplayName = "Main Window";
var vehicles = new ObservableCollection<String>();
vehicles = GetAllFilesFromFolder(_path2Images);
VehicleImages = new ObservableCollection<VehicleImage>();
foreach (var i in vehicles)
VehicleImages.Add(new VehicleImage(i));
}
public ObservableCollection<String> GetAllFilesFromFolder(String fullPathFolder)
{
string[] fileArray = Directory.GetFiles(fullPathFolder);
return new ObservableCollection<String>(fileArray);
}
}
public class VehicleImage
{
public String Image { get; private set; }
public String Name { get; private set; }
public VehicleImage(String image)
{
Image = image;
Name = Path.GetFileName(image);
}
}
//public void ToggleVehicle01()
//{
// var selectText = (SelectedVehicle01) ? " selected" : " unselected";
// MessageBox.Show(Vehicle01Name + selectText);
// BackColorSelectedVehicle01 = (SelectedVehicle01) ? _backColorSelectedVehicle : _defaultBackColorVehicle;
//}
//public Boolean SelectedVehicle02
//{
// get { return _selectedVehicle02; }
// set
// {
// _selectedVehicle02 = value;
// NotifyOfPropertyChange(() => SelectedVehicle02);
// }
//}
//public Brush BackColorSelectedVehicle02
//{
// get { return _backColorSelectedVehicle02; }
// set
// {
// _backColorSelectedVehicle02 = value;
// NotifyOfPropertyChange(() => BackColorSelectedVehicle02);
// }
//public String Vehicle01Name { get; private set; }
}
EDIT: Now I can bind the properties of generated ToggleButton with view model. I make the VehicleImage class to a view model (see modified code below). But I still have problem to bind Click-event of generated ToggleButton to view model.
The modified class to view model
public class VehicleImage : PropertyChangedBase
{
public String Image { get; private set; }
public String Name { get; private set; }
private Boolean _selectedVehicle;
public Boolean SelectedVehicle
{
get { return _selectedVehicle; }
set
{
_selectedVehicle = value;
BackColorSelectedVehicle = _selectedVehicle ? new SolidColorBrush(Color.FromArgb(255, 242, 103, 33)) : new SolidColorBrush(Colors.White);
}
}
private Brush _backColorSelectedVehicle;
public Brush BackColorSelectedVehicle
{
get { return _backColorSelectedVehicle; }
set
{
_backColorSelectedVehicle = value;
NotifyOfPropertyChange(() => BackColorSelectedVehicle);
}
}
// ToggleButton's Click-Event Handler, but it doesn't get event trigger from View.
// Therefore I set the BackColorSelectedVehicle fin setter of SelectedVehicle property.
public void ToggleSelection()
{
//BackColorSelectedVehicle = SelectedVehicle ? new SolidColorBrush(Color.FromArgb(255, 242, 103, 33)) : new SolidColorBrush(Colors.White);
}
public VehicleImage(String image)
{
Image = image;
Name = Path.GetFileName(image);
}
}
The modified view
<ToggleButton Width="180"
Margin="10,10,10,10"
Background="{Binding Path=BackColorSelectedVehicle}"
FontSize="10"
IsChecked="{Binding Path=SelectedVehicle}"
Style="{StaticResource {x:Static ToolBar.ToggleButtonStyleKey}}"
ToolTip="{Binding Path=Name}">
<!-- x:Name="ToggleSelection" -->
<StackPanel Margin="0,5,0,5"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Image Width="140"
RenderOptions.BitmapScalingMode="Fant"
Source="{Binding Path=Image}" />
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Path=Name}" />
</StackPanel>
</ToggleButton>

Categories