I have a list of objects that I need to represent as a list of buttons.
These buttons should normally act as a regular Button; when the checkbox is checked, they should work as ToggleButtons and remain pressed. But I also need them to be mutually exclusive, like a RadioButton (only one can only be toggled at any time).
I tried using a RadioButton as the template for my ItemsControl, but they are not mutually exclusive (I guess that they are not actually children of the same control).
So I thought to use a ToggleButton as the template, manually uncheck it if the checkbox is not checked, and manually handle the mutual exclusion.
However, I can't find a way to retrieve the toggle buttons for the other items in the list to uncheck them.
Here's my XAML:
<Window x:Class="WpfApp9.MainWindow"
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"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<UniformGrid Rows="1">
<UniformGrid.Resources>
<DataTemplate x:Key="template">
<ToggleButton Name="Toggle"
Checked="ToggleButton_Checked"
Content="{Binding}"/>
</DataTemplate>
</UniformGrid.Resources>
<ItemsControl Name="lst" ItemTemplate="{StaticResource template}" />
<CheckBox Name="CheckToggle"
HorizontalAlignment="Center"
VerticalAlignment="Center">
TOGGLE
</CheckBox>
</UniformGrid>
</Window>
And this is my code-behind:
using System.Windows;
using System.Windows.Controls.Primitives;
namespace WpfApp9
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
lst.ItemsSource = new[] { "foo", "bar", "baz" };
}
private void ToggleButton_Checked(object sender, RoutedEventArgs e)
{
var toggle = (ToggleButton)sender;
// If the checkbox is not checked, release the button immediately
if (CheckToggle.IsChecked != true)
toggle.IsChecked = false;
// now how do I uncheck the other ToggleButtons?
}
}
}
I ended up solving the problem above in a different way.
In the question I said that
I tried using a RadioButton as the template for my ItemsControl, but they are not mutually exclusive (I guess that they are not actually children of the same control)
but I didn't realize I could use the GroupName property to force them into the same group. At this point the template can be this:
<DataTemplate x:Key="template">
<RadioButton Checked="RadioButton_Checked"
GroupName="SomeGroupName"
Content="{Binding}"/>
</DataTemplate>
And I get my mutually exclusive buttons without handling them manually.
Related
I'm building a WPF app with custom UserControls, and I'm trying to understand how property bindings are supposed to work. I can't get even the most basic binding to work, and it's simple enough to distill into a tiny example, so I figured someone with more WPF experience might be able to put me on the right track.
I've defined a custom UserControl called TestControl, which exposes a Foo property, which is intended to be set in XAML whenever a UserControl is placed.
TestControl.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace BindingTest
{
public partial class TestControl : UserControl
{
public static readonly DependencyProperty FooProperty = DependencyProperty.Register("Foo", typeof(string), typeof(TestControl));
public string Foo
{
get { return (string)GetValue(FooProperty); }
set { SetValue(FooProperty, value); }
}
public TestControl()
{
InitializeComponent();
}
}
}
The markup for TestControl just defines it as a control with a single button, whose label text displays the current value of the Foo property:
TestControl.xaml
<UserControl x:Class="BindingTest.TestControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:BindingTest"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Button Content="{Binding Foo}" />
</Grid>
</UserControl>
In my MainWindow class, I just place a single instance of TestControl with its Foo property set to "Hello".
MainWindow.xaml
<Window x:Class="BindingTest.MainWindow"
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"
xmlns:local="clr-namespace:BindingTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<local:TestControl Foo="Hello" />
</Grid>
</Window>
I would expect that when I build and launch this app, I'd see a window with a single button reading "Hello". However, the button is blank: the Binding doesn't seem to work.
If I add a click handler to the TestControl's button, I can verify that the value is being updated behind the scenes:
// Added to TestControl.xaml.cs:
private void Button_Click(object sender, RoutedEventArgs e)
{
Console.WriteLine("Button clicked; Foo is '{0}'", Foo);
}
// Updated in TestControl.xaml:
// <Button Content="{Binding Foo}" Click="Button_Click" />
When I click the button, I get Button clicked; Foo is 'Hello', but the GUI never updates. I've tried using Path=Foo, XPath=Foo, etc., as well as setting UpdateSourceTrigger=PropertyChanged and verifying updates with NotifyOnTargetUpdated=True... nothing seems to result in the text in the UI being updated to match the underlying property value, even though the property value seems to be getting updated just fine.
What am I doing wrong? I feel like there's just a simple and fundamental misunderstanding in how I'm approaching this.
edit:
Poking around a bit more and reading similar questions has led me to a potential fix: namely, adding a name to the root UserControl element in TestControl.xaml (x:Name="control"), and changing the binding to explicitly specify that control ({Binding Foo, ElementName=control}).
I'm guessing that by default, {Binding Foo} on the Button element just means "find a property named 'Foo' on this Button control", whereas I'd assumed it'd mean "find a property named 'Foo' in the context that this Button is being declared in, i.e. on the TestControl".
Is specifying an explicit ElementName the best fix here?
You have to set the source object of the Binding to the UserControl instance, e.g. like this:
<Button Content="{Binding Foo, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
or
<UserControl ... x:Name="theControl">
...
<Button Content="{Binding Foo, ElementName=theControl}"/>
If you have many such Bindings, you may also set the DataContext of the top level element in the UserControl's XAML to the UserControl instance:
<Grid DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}">
<Button Content="{Binding Foo}" />
<Button Content="{Binding Bar}" />
</Grid>
You must however avoid to set the DataContext of the UserControl (which is often recommend by "expert" bloggers), because that would break DataContext-based Bindings of the UserControl properties like
<local:TestControl Foo="{Binding SomeFoo}" />
I am trying to create a wpf application with several tabs in a TabControl, where one tab is a log. The button on the first tab adds a line to the log in the second tab. When the button is pressed, it also displays a message box
containing the length of the ItemsSource of the log.
Issue:
Press the button - message box comes up showing 1 item in the log
Switch to the log tab (shows one line)
Switch to the button, press it. The message box shows 2 items in the log.
Switch to the log tab, only one item is displayed.
Full Code:
MainWindow.xaml
<Window x:Class="WpfApp2.MainWindow"
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"
xmlns:local="clr-namespace:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TabControl HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,10,10,10">
<TabItem Header="Microcode Process" Name="MicrocodeProcessTab">
<Button Content="Single Step" Margin="0,0,10,0" Click="SingleStep" Name="SingleStepButton"/>
</TabItem>
<TabItem Header="Log">
<ScrollViewer>
<ItemsControl Name="LogList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</TabItem>
</TabControl>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Windows;
namespace WpfApp2 {
public partial class MainWindow : Window {
List<string> list = new List<string>();
public MainWindow() {
InitializeComponent();
LogList.ItemsSource = list;
}
private void SingleStep(object sender, RoutedEventArgs e) {
list.Add($"STEP");
MessageBox.Show((LogList.ItemsSource as List<string>).Count.ToString());
e.Handled = true;
}
}
}
The WPF technology does not magically update target whenever a change is made to the source. It keeps UI updated through some notification mechanisms such as DependencyObject, INotifyPropertyChanged or INotifyCollectionChanged.
The data type used for the collection (List<>) does not have a notification mechanism. So the UI gets updated only once upon its creation and never again, that's why you see the log item added only the first time you go to the second tab.
(not recommended) You can do this to manually reset the ItemsSource every time a change is made to it. It will however always re-create all UI elements in the visual tree of the ItemsControl.
private void SingleStep(object sender, RoutedEventArgs e)
{
list.Add("STEP");
LogList.ItemsSource = null;
LogList.ItemsSource = list;
}
(recommended) You can implement binding for your ItemsSource by using an ObservableCollection - which implements INotifyCollectionChanged - as follows:
ObservableCollection<string> _list = new ObservableCollection<string>();
public ObservableCollection<string> list { get { return _list; } }
Note that an ObservableCollection automatically notifies the target of any changes made within the collection.
So I'm working on a calculator, basically a copy of the Windows Version, as a training excercise. I have implemented a History of past calculations, and I was asked to transform this history from TextBox to Listview.
What I want to do is copy one of the past calculations back into the Calculator TextBox when I click on it, just like in the Windows Calculator.
My ListViewCode:
<ListView Grid.Column="0" Grid.Row="1" Foreground="#616161" Name="history" Background="Transparent"
HorizontalAlignment="Stretch" BorderThickness="0" Margin="10,10,10,0">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="MouseLeftButtonDown" Handler="RetrievePastCalculation" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
And this is the RetrievePastCalculation method, but it doesn't work, nothing happens when I click on a ListViewItem. I'm new to WPF by the way.
private void RetrievePastCalculation(object sender, MouseButtonEventArgs e)
{
innerTextBox.Text = history.SelectedItems.ToString();
}
This is where I add items to the ListView I think, it's the Equal button method:
private void ButtonEquals_Click(object sender, RoutedEventArgs e)
{
Calculator calculate = new Calculator();
textBox.Text = calculate.Calculate(innerTextBox.Text);
history.Items.Add(innerTextBox.Text + "=" + textBox.Text);
innerTextBox.Clear();
}
history.SelectedItems is a collection, so calling ToString on it won't give you anything other than the name of the type. If you try it in the debugger (which you should), you'll see that it returns System.Windows.Controls.SelectedItemCollection. Now, at this point you can either fix your issue one of two ways: you can continue to use your current event-based approach, or you can use binding.
Events
With events, you can hook a handler to the Selected event for each ListItem that you add to the list:
private void ButtonEquals_Click(object sender, RoutedEventArgs e)
{
Calculator calculate = new Calculator();
textBox.Text = calculate.Calculate(innerTextBox.Text);
var item = new ListViewItem();
item.Content = innerTextBox.Text + "=" + textBox.Text;
item.Selected += HistoryItem_Selected //hooks the handler to the 'Selected' event
history.Items.Add(item);
innerTextBox.Clear();
}
then define the handler itself:
private void HistoryItem_Selected(object sender, RoutedEventArgs e)
{
// here 'sender' will be the ListItem which you clicked on
// but since it's an object we need to cast it first
ListViewItem listItem = (ListViewItem)sender;
// now all that's left is getting the text and assigning it to the textbox
innerTextBox.Text = listItem.Content.ToString();
}
Binding
Binding is much simpler as far as the amount of code is concerned, but has a steeper learning curve. Here, instead of setting the TextBox.Text property directly, we will specify a binding expression. This means that the value will always be the same as that of the bound expression.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<ListView Grid.Column="0" Grid.Row="0" Name="history" />
<TextBox Text="{Binding ElementName=history, Path=SelectedItem.Content}" />
<Button Name="ButtonEquals" Content="equals" Click="ButtonEquals_Click"/>
</StackPanel>
</Grid>
</Window>
I've run this in a new WPF project and it works as expected: the text box displays whatever text is in the clicked item from the list.
One thing to note is that both solutions assume that you are assigning strings to the ListViewItem Content. As you may know, you can assign other controls or any object to the Content property of a UI Control (ListViewItem inherits from Control). That's why the ListViewItem.Add method takes an argument of type object and is not restricted to one of type string. If you assigned anything other than a string in your button click event handler, both of the two cases above would likely break.
You could bind the value of the TextBox to the SelectedItem of the ListView. Here's an example:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<ListView Grid.Column="0" Grid.Row="0" Foreground="#616161" Name="history" Background="Transparent"
HorizontalAlignment="Stretch" BorderThickness="0" Margin="10,10,10,0">
<ListViewItem>Calc1</ListViewItem>
<ListViewItem>Calc2</ListViewItem>
</ListView>
<TextBox Text="{Binding ElementName=history, Path=SelectedItem.Content}" />
</StackPanel>
</Page>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100"/>
<RowDefinition Height="100"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListView Grid.Column="0" Grid.Row="0" Foreground="#616161" Name="history" BorderThickness="1,1" Height="50" Width="200" SelectionChanged="history_SelectionChanged">
<ListViewItem>
<TextBlock> A ListView</TextBlock>
</ListViewItem>
<ListViewItem>
with several
</ListViewItem>
<ListViewItem>
items
</ListViewItem>
</ListView>
<TextBox Grid.Row="1" Text="{Binding ElementName=history,Path=SelectedValue.Content}"
BorderThickness="1,1" Height="50" Width="200" />
</Grid>
It's better if you do it using XAML code. try to select item 0 and 1 to see the difference and understand how listboxworks.
now replace the text of textbox binding with following:
Text="{Binding ElementName=history,Path=SelectedValue.Content.Text}"
and seee the output for item 0. Hopefully you'll achieve desired output with a lot less effort.
Now that you have explained the whole problem i think you need to implement a converter in the text binding of TextBox. like below text
Text="{Binding ElementName=history,Path=SelectedValue.Content.Text,Converter={StaticResource mytextconverter}}"
and write down a logic to extract a part of text on the basis of '=' char. It's very easy to write a converter class. to write a converter follow the below link:
WPF Converter example
I'm new to WPF and I'm trying to dynamically add a Button inside a ContentControl, which should fire a command when clicked. I'm using MVVMLight to handle the Commands.
Below I have an example with two buttons. The top button is placed directly into the StackPanel. This button fires off the Command as expected.
The second button is placed inside a ContentControl. It displays correctly, but the Command does not fire when the button is clicked.
I assumed this is because the Binding does not transfer down through the DataTemplate, but it seems to work if I use regular Commands instead of MVVMLight RelayCommands.
I don't want to remove the framework, so I'm wondering if anyone knows how to fix it? Thanks
<Window x:Class="ContentControlExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ContentControlExample.ViewModel">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Key="MyButton" >
<Button Content="SUBMIT" Command="{Binding MyCommand}" Width="200" Height="50"/>
</DataTemplate>
</Window.Resources>
<StackPanel>
<!--When this button is clicked, the Command executes as expected-->
<Button Content="SUBMIT" Command="{Binding MyCommand}" Width="200" Height="50"/>
<!--Nothing happens when this button is clicked-->
<ContentControl ContentTemplate="{StaticResource MyButton}"/>
</StackPanel>
</Window>
Here's the ViewModel with the command:
public class MainViewModel : ViewModelBase
{
public ICommand MyCommand { get; private set; }
public MainViewModel()
{
MyCommand = new RelayCommand(MyCommand_Executed, MyCommand_CanExecute);
}
private bool MyCommand_CanExecute()
{
return true;
}
private void MyCommand_Executed()
{
MessageBox.Show("The command executed");
}
}
The problem here is the implicit DataContext in ContentTemplate is the Content and this has not been set to anything. You need to set Content to some Binding to bridge the DataContext currently in the visual tree, something like this:
<ContentControl ContentTemplate="{StaticResource MyButton}" Content="{Binding}"/>
Another solution is to give your Window a name:
<Window x:Class="ContentControlExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ContentControlExample.ViewModel"
x:Name="_this">
Then bind via its context instead:
<Button Content="SUBMIT" Command="{Binding ElementName=_this, Path=DataContext.MyCommand}" Width="200" Height="50"/>
This is particularly handy for things like ListViews and ItemControls, as their DCs get set to the list elements. Keep in mind though that this will only work on members within the same visual tree, if that's not the case (e.g. popup menus etc) then you need to proxy a binding as described in this article.
Okay, so I'm fairly new to WPF and data binding, but I've searched and searched and can't seem to find an answer to this.
I have a database (called Inventory), and a dataset (called DevicesDataSet). What I'm trying to do is bind the dataset to a listbox, so that a specific device from the Devices table of the DevicesDataSet can be selected, and have its properties displayed in a TextBox for editing.
The following is the XAML I have so far:
<Window x:Class="Inventory.SignOutDevice"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SignOutDevice" Height="339" Width="392" xmlns:my="clr-namespace:Inventory" Loaded="Window_Loaded">
<Window.Resources>
<my:DevicesDataSet x:Key="devicesDataSet" />
<CollectionViewSource x:Key="devicesViewSource" Source="{Binding Path=Devices, Source={StaticResource devicesDataSet}}" />
</Window.Resources>
<Grid DataContext="{StaticResource devicesViewSource}">
<ListBox Height="237" HorizontalAlignment="Left" Margin="12,12,0,0" Name="listBox1"
VerticalAlignment="Top" Width="254" ItemsSource="{Binding}" SelectedValuePath="Selected">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Make}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox Margin="223,267,0,0" Text="{Binding Path=Make, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
Whenever a device is selected and a property is edited (I'm only displaying one property at the moment), the listbox is updated, but the dataset (database?) doesn't seem to be. That is, when I leave the form and then come back to it, the listbox returns to its original state.
So I guess the question is: how do I make these changes persist/write to the database?
Edit: Derp, here's the updated backend C#:
using System.Windows;
using System.Data;
namespace Inventory
{
public partial class SignOutDevice : Window
{
DevicesDataSet devicesDataSet = null;
Inventory.DevicesDataSetTableAdapters.DevicesTableAdapter devicesDataSetDevicesTableAdapter = null;
public SignOutDevice()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
devicesDataSet = ((Inventory.DevicesDataSet)(this.FindResource("devicesDataSet")));
devicesDataSetDevicesTableAdapter = new Inventory.DevicesDataSetTableAdapters.DevicesTableAdapter();
devicesDataSetDevicesTableAdapter.Fill(devicesDataSet.Devices);
System.Windows.Data.CollectionViewSource devicesViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("devicesViewSource")));
devicesViewSource.View.MoveCurrentToFirst();
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
devicesDataSet.AcceptChanges();
devicesDataSetDevicesTableAdapter.Update(devicesDataSet.Tables["Devices"].Select(null, null, DataViewRowState.Deleted));
devicesDataSetDevicesTableAdapter.Update(devicesDataSet.Tables["Devices"].Select(null, null, DataViewRowState.ModifiedCurrent));
devicesDataSetDevicesTableAdapter.Update(devicesDataSet.Tables["Devices"].Select(null, null, DataViewRowState.Added));
}
}
}
Have you looked in the Output window to check for binding errors? Because it looks to me like you have one. The TextBox is bound to the Make property, but its DataContext is the devicesViewSource resource. There's nothing associating it with the selected item in the ListBox.
A way to associate the two would be to assign the ListBox a name, and then set the binding on the TextBox to {Binding ElementName=MyListBox, Path=Make, Mode=TwoWay}.
Okay, figured it out. Turns out the auto-generated Update() method in devicesDataSetDevicesTableAdapter didn't actually do anything. Made a custom method with the Query Wizard and now all is well.