I want to have a combo box with a button that looks like this:
As I want to use this so that items can be selected and added to a ListView.
Issues:
I don't know how to get and icon in the button like shown
How do you get them to line up really well or is there a way to combine the two elements that I am unaware of?
Here's a working example.
Let's suppose your user control has two controls; a ComboBox and a Button. You want to be able to bind something from your main (parent) to the user control. Then upon selecting something and clicking the button, you want user control to notify to the parent of the event occurrence, and also pass the selected value.
The UserControl XAML:
<UserControl ...
d:DesignHeight="40" d:DesignWidth="200">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="40"/>
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="0" Margin="4" Name="ItemsComboBox"
ItemsSource="{Binding Source, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<Button Grid.Column="1" Margin="4" Content="+"
Click="Button_Click"/>
</Grid>
</UserControl>
The following binding will allow you to bind a list of data to the combo box, form the parent:
ItemsSource="{Binding Source, RelativeSource={RelativeSource AncestorType=UserControl}}"
From your MainWindow, you'll use the control like so:
<Grid>
<local:UCComboButton Grid.Row="0" Width="200" Height="40" x:Name="MyUC"
Source="{Binding Names}"/>
</Grid>
And in the UserControls code behind:
public partial class UCComboButton : UserControl
{
public UCComboButton()
{
InitializeComponent();
}
// We use this dependency property to bind a list to the combo box.
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(IEnumerable), typeof(UCComboButton), new PropertyMetadata(null));
public IEnumerable Source
{
get { return (IEnumerable)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
// This is to send the occurred event, in this case button click, to the parent, along with the selected data.
public class SelectedItemEventArgs : EventArgs
{
public string SelectedChoice { get; set; }
}
public event EventHandler<SelectedItemEventArgs> ItemHasBeenSelected;
private void Button_Click(object sender, RoutedEventArgs e)
{
var selected = ItemsComboBox.SelectedValue;
ItemHasBeenSelected?.Invoke(this, new SelectedItemEventArgs { SelectedChoice = selected.ToString() });
}
}
Now in the MainWindow.xaml.cs:
public MainWindow()
{
InitializeComponent();
// Subscribe to the item selected event
MyUC.ItemHasBeenSelected += UCButtonClicked;
Names = new List<string>
{
"A",
"B",
"C"
};
DataContext = this;
}
void UCButtonClicked(object sender, UCComboButton.SelectedItemEventArgs e)
{
var value = e.SelectedChoice;
// Do something with the value
}
Note that the above Names list is what's bound to the user control from the main window XAML.
Related
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 :)
I have two textboxes inside an expander. I am validating the textbox for text value when a button is clicked. If the textbox is empty the textbox is given foucs.
What I want to achieve is for the expander to expand automatically when one of these texboxes gets focus.
I could not find any ways to do that on the internet. Is it possible to do so?
xaml :
<Grid>
<Expander Header="Textbox Expander">
<StackPanel Orientation="Vertical">
<TextBox Name="txtName" Height="30" Width="100" Background="WhiteSmoke" />
<TextBox Name="txtAge" Height="30" Width="100" Background="WhiteSmoke" />
</StackPanel>
</Expander>
<Button Name="btnDone" Content="Done" Width="50" Height="50" Click="btnDone_Click"/>
</Grid>
c# :
using System.Windows;
using System.Windows.Controls;
namespace TestExpanderFocus
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btnDone_Click(object sender, RoutedEventArgs e)
{
Validation validation = new Validation();
if(validation.validate(ref txtName) && validation.validate(ref txtAge))
{
//Do Something.
}
}
}
}
EDIT : Since this validation class is in another application I cannot edit this.
//Seperate class in another application which cannot be edited
public class Validation
{
public bool validate(ref TextBox txtobj)
{
if (string.IsNullOrWhiteSpace(txtobj.Text))
{
MessageBox.Show("Please Enter Data..!");
txtobj.Focus();
return false;
}
return true;
}
}
What you wanted to achieve is actually pretty simple.
First give the Expander a name
<Expander x:Name="MyExpander" ... />
Second, in your validation, before you focus on the textbox, simply expand the Expander
MyExpander.IsExpanded = true;
...
-- EDIT to satisy the new requirement --
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btnDone_Click(object sender, RoutedEventArgs e)
{
Validation validation = new Validation();
// since you know that the text will be focused when the validation fails
var result1 = validation.validate(ref txtName);
var result2 = validation.validate(ref txtAge);
MyExpander.IsExpanded = !result1 || !result2;
if(result1 && result2)
{
//Do Something.
}
}
}
But I must admit, this is not the nicest solution. There should be an easier way to just add Trigger to the Expander Style directly. (I will leave it to other people, since I don't have more time)
I have a WPF DataGrid in XAML and C # and I want to select a row and display the row in the text box, it is not a DataGridView
x:Name="dtGConsultas" ItemsSource= "{Binding }"
HorizontalAlignment="Left" Margin="12,3,0,0" Grid.Row="6" VerticalAlignment="Top"
Grid.ColumnSpan="5" Height="111" Width="598" Grid.RowSpan="3"
SelectionChanged="dtGConsultas_SelectionChanged"/>
This can be done in several ways:
You can bind SelectedItem to some property and then display it
You can bind TextBox value to the DataGrid's SelectedItem
You can set the TextBox value on each call of SelectionChanged method
If You would be using MVVM pattern, You should pick 1st option.
Other 2nd an 3rd options are useful for You, but in bigger (complex) applications this solutions would cause issues to read the code easily & maintain it. Not recommended.
Examples:
MVVM approach
ViewModel file:
using using System.Collections.ObjectModel;
public class MyViewModel
{
//add implementation of INotifyPropertyChange & propfull
public ObservableCollection<MyItem> MySrcList { get; set; }
//add implementation of INotifyPropertyChange & propfull
public MyItem SelectedItem { get; set; }
}
View:
<UserControl ...
xmlns:local="clr-namespace:MyProject">
<UserControl.DataContext>
<local:MyProject />
</UserControl.DataContext>
...
<DataGrid
ItemsSource="{Binding MySrcList}"
SelectedItem="{Binding SelectedItem}"/>
Binding TB value to value of DataGrid's SelectedItem
Xaml file:
<Grid>
<DataGrid
x:Name="dtGConsultas"
ItemsSource="{Binding MySrcList}"/>
<TextBox Text="{Binding dtGConsultas.SelectedItem, Mode=OneWay}"/>
</Grid>
Code-behind (C# file):
public class MyUserControl
{
public MyUserControl()
{
this.InitializeComponent();
this.DataContext = this;
}
public List<MyItem> MySrcList = new List<MyItem>();
}
Update in method (Code-behind):
Xaml file:
<Grid>
<DataGrid
x:Name="dtGConsultas"
ItemsSource="{Binding MySrcList}"
SelectionChanged="dtGConsultas_SelectionChanged"/>
<TextBox x:Name="MyTbx"/>
</Grid>
Code-Behind (C# file):
public class MyUserControl
{
public MyUserControl()
{
this.InitializeComponent();
this.DataContext = this;
}
public List<MyItem> MySrcList = new List<MyItem>();
private void dtGConsultas_SelectionChanged( /* args */)
{
MyTbx.Text = dtGConsultas.SelectedItem.ToString();
}
}
You can also add a column that contains a checkbox and you bind it. Then juste check if (Your_List.element.CheckBox==true). you can get a list whit your checked elements
I have UserControl with ItemsControl binded to ObservableCollection. DataTemplate in this ItemsControl is a Grid containing TextBox and Button.
Here is some code (Updated):
<UserControl.Resources>
<entities:SeparatingCard x:Key="IdDataSource"/>
</UserControl.Resources>
<ItemsControl ItemsSource="{Binding Cards}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Text="{Binding Id, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" GotFocus="TextBox_GotFocus" Grid.Row="0" Grid.Column="0"/>
<Button DataContext="{Binding Source={StaticResource IdDataSource}}" Command="{Binding Accept}" Grid.Row="0" Grid.Column="1">Accept</Button>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In model file:
public ObservableCollection<SeparatingCard> Cards { get; set; }
Card class:
class SeparatingCard : INotifyPropertyChanged
{
private string _id;
public string Id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("Id");
}
}
public ActionCommand Accept { get; }
public SeparatingCard()
{
Accept = new ActionCommand(AcceptCommandExecute);
}
private void AcceptCommandExecute(object obj)
{
MessageBox.Show(Id);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Cards are added in runtime and I dynamically get a new textbox-button pair in my UserControl. Now in each pair I need to do the folowing things:
- Be able to check if the text in textbox is correct and disable/enable apropriate button.
- On button click get the text from apropriate textbox and process it.
I'd like all of this done via MVVM. But I only came to solution that directly have access to UI and implements only the second task:
private void Button_Click(object sender, RoutedEventArgs e)
{
var text = (((sender as Button).Parent as Grid).Children
.Cast<UIElement>()
.First(x => Grid.GetRow(x) == 0 && Grid.GetColumn(x) == 0) as TextBox).Text;
MessageBox.Show(text);
}
Update
As was suggested I tried to move ICommand logic to SeparatingCard class. Now it's always return null and I can't check what object of SeparatingCard class my command refers to. Updates are in the code above.
Instead of using Button.Click, Use Button.Command, which you can bind to some command in SeparatingCard.
Please have a look in this tutorial:
http://www.codeproject.com/Tips/813345/Basic-MVVM-and-ICommand-Usage-Example
Then, SeparatingCard ViewModel will contain an ICommand object which you can bind to Button.Command.
So if the user clicks the button, the event will be directed to the corresponding SeparatingCard object's command.
I've got a WPF TextBox with TwoWay binding to a ViewModel property. I also have a ToolBar with a Button. When the Button is clicked, it executes a command on the same ViewModel that will do something with the property the TextBox is bound to.
Unfortunately it looks like the Binding only sends the text back to the binding target when the TextBox loses focus. The Button on the Toolbar however does not take focus when clicked. The upshot being that when the Command executes it does not have the text from the textbox, but rather the last value that was bound.
The Xaml looks like so:
<DockPanel LastChildFill="True" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" >
<ToolBarTray Background="White" DockPanel.Dock="Top">
<ToolBar Band="1" BandIndex="1">
<Button Command="{Binding QueryCommand}">
<Image Source="images\media_play_green.png" />
</Button>
</ToolBar>
</ToolBarTray>
<DataGrid VerticalAlignment="Top" DockPanel.Dock="Top" Height="450" AutoGenerateColumns="True"
ItemsSource="{Binding}" DataContext="{Binding Results}" DataContextChanged="DataGrid_DataContextChanged"/>
<TextBox DockPanel.Dock="Bottom" Text="{Binding Sql, Mode=TwoWay}"
AcceptsReturn="True" AcceptsTab="True" AutoWordSelection="True" TextWrapping="WrapWithOverflow"/>
</DockPanel>
How do I get the TextBox's Text binding to update the ViewModel when the ToolBar button is pressed. There is nothing fancy going on in the ViewModel which looks like so:
public class MainViewModel : ViewModelBase
{
private readonly IMusicDatabase _database;
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel(IMusicDatabase database)
{
_database = database;
QueryCommand = new RelayCommand(Query);
}
public RelayCommand QueryCommand { get; private set; }
private async Task QueryAndSetResults()
{
Results = await _database.Query(Sql);
}
private void Query()
{
QueryAndSetResults();
}
private IEnumerable<object> _results;
public IEnumerable<object> Results
{
get
{
return _results;
}
private set
{
Set<IEnumerable<object>>("Results", ref _results, value);
}
}
private string _sql = "SELECT * FROM this WHERE JoinedComposers = 'Traditional'";
public string Sql
{
get { return _sql; }
set
{
Set<string>("Sql", ref _sql, value);
}
}
}
You can use the UpdateSourceTrigger property of the binding, setting it to PropertyChanged makes the TextBox refresh the binding every time the text changes, not just when losing focus:
<TextBox DockPanel.Dock="Bottom"
Text="{Binding Sql, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
AcceptsReturn="True"
AcceptsTab="True"
AutoWordSelection="True"
TextWrapping="WrapWithOverflow"/>
More info at MSDN.