On start of application the ComboBox with a valid Text Property incorrectly shows a placeholder text rather than the bound text value - until the user clicks the ComboBox.
The click causes the correct bound Text to show and stay shown. The related TextBox linked to same property correctly shows the bound value at all times.
If a Combo List item is selected it works as expected indicating Binding is correct.
It looks like the default template: (generic.xaml from C:\Program Files (x86)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\10.0.18362.0\Generic.xaml)
is likely incorrect as the Text should show when a value exists, rather than the Placeholder text. If this is the cause, then I do not yet have the skill to redefine the visual states correctly and would benefit from a hint.
Can anyone point me in the right direction so I can show the Text property at initial start without a click required.
The user control code used is:
Demonstrated with a WinUI3 usercontrol containing TextBox and a ComboBox - Text of each bound to the same property in two way mode so each will update with property changed.
Using current nuget package of CommunityToolkit 7.1.2 which uses
.net5.0-windows10.0.18362
Xaml Code ComboBoxTest.xaml:
<UserControl
x:Class="mvvmTry1.Views.ComboBoxTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:mvvmTry1.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel Orientation="Horizontal" Spacing="20" Background="AntiqueWhite">
<TextBox Header="Current Value" Text="{x:Bind MyText,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
MinWidth="60"
Height="Auto" />
<ComboBox
Header=" ComboBox"
IsEditable="True"
ItemsSource="{x:Bind MyListItems, Mode=TwoWay}"
MinWidth="100"
PlaceholderText="PlaceHolder Text"
PlaceholderForeground="Green"
Text="{x:Bind MyText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
/>
</StackPanel>
</UserControl>
CodeBehind ComboBoxTest.xaml.cs:
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Xaml.Controls;
using System.Collections.ObjectModel;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace mvvmTry1.Views
{
[ObservableObject]
public partial class ComboBoxTest : UserControl
{
[ObservableProperty]
private string myText ="MyText value";
public ObservableCollection<string> MyListItems = new();
public ComboBoxTest()
{
this.InitializeComponent();
MyListItems.Add("Item 1");
MyListItems.Add("Item 2");
MyListItems.Add("Item 3");
}
}
}
Related
I have an application that displays a datagrid. However the data has gotten big and I want to incorporate filters to some of the rows. I've gotten as far as creating a DataTemplate for my headers:
<DataGrid>
<DataGrid.Resources>
...
<DataTemplate x:Key="HeaderTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentControl Content="{Binding}" VerticalAlignment="Center"/>
<ToggleButton Name="FilterButton" Grid.Column="1" Content="▼" Margin="2, 1, 1, 1" Padding="1, 0"/>
<Popup IsOpen="{Binding ElementName=FilterButton, Path=IsChecked}" PlacementTarget="{Binding ElementName=FilterButton}" StaysOpen="False">
<Border Background="White" Padding="3">
<TextBox Text={Binding PetNameFilterSearchBox, Mode=TwoWay} Width="300"/> <!--The Text Box I want to bind-->
</Border>
</Popup>
</Grid>
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Width="6*" Header="Pet Name" Binding="{Binding PetName}" ElementStyle="{DynamicResource DataGridTextColumnWrap}" HeaderTemplate="{StaticResource HeaderTemplate}"/>
</DataGrid.Columns>
</DataGrid>
So far what it does is show a button next to the header text and when you click on it a small popup window appears containing a text box. The desired effect is that the user can type in the text box and data will be filtered according to what was typed.
In my view model I already have my filter text box property that I want to use for binding:
public string PetNameFilterSearchBox
{
get
{
return _petNameFilterSearchBox;
}
set
{
_petNameFilterSearchBox = value;
RaisePropertyChanged(nameof(PetNameFilterSearchBox));
FilterData(); //As you're writing
}
}
private string _petNameFilterSearchBox = string.Empty;
public CollectionView PetDataFilterView { get; set; }
public bool OnFilterTriggered(object item)
{
if (item is AvailablePetInfo petInfo)
{
var pet_name = PetNameFilterSearchBox;
if (pet_name != string.Empty)
return (petInfo.DisplayName.Contains(pet_name));
}
return true;
}
public void FilterData()
{
CollectionViewSource.GetDefaultView(AvailablePetInfo).Refresh();
}
//Constructor
public PetInfoViewModel()
{
AvailablePetInfo = GetPetInfo();//gets the list
ContactFilterView = (CollectionView)CollectionViewSource.GetDefaultView(AvailablePetInfo);
ContactFilterView.Filter = OnFilterTriggered;
}
When I run my code I see the little button next to the header, I click on it and I see the textbox. But when I start typing I dont see my datagrid updating. I set some breakpoints in my PetNameFilterSearchBox and I find that when I start typing it's not getting hit. This tells me that there's something wrong with the binding. Can someone tell me what I'm doing wrong?
Your problem is one of DataContext.
I'll be assuming PetNameFilterSearchBox is a property of the Window hosting the DataGrid and that the appropriate DataContext is set at the Window level.
Normally, DataContext is inherited by child elements, so setting the DataContext for the Window would set it for all its children. But things change once you start using DataTemplates.
In a DataTemplate, the root DataContext is always the data object that's being displayed. In your case, that's the string "Pet Name". This is why you can put <ContentControl Content="{Binding}"/> inside the DataTemplate and have it display "Pet Name".
The downside is you can't put <TextBox Text="{Binding PetNameFilterSearchBox}"/> and expect it to bind to the Window, because that DataContext is being overridden by the DataTemplate.
Normally, you can get around the DataTemplate DataContext problem by using RelativeSource, which you can use walk up the visual tree and find another source to bind to. But this doesn't work inside a Popup because a Popup is not actually part of the Window's visual tree.
What will work is ElementName:
<TextBox Text="{Binding PetNameFilterSearchBox, Mode=TwoWay, ElementName=W}" Width="300"/>
In the above example, I set on my Window Name="W".
I'm having problems with creating a suitable watermark in my TextBox.
I use MahApps in my project, and as long as I don't bind 'Text' to my custom property, all works fine.
But I need to trace the changes in the TextBox, and so I bind the Text property like this:
<TextBox controls:TextboxHelper.Watermark="Enter text here..." Text="{Binding Path=MyProperty}" />
In this case, the watermark property stops working, the watermark text doesn't disappear when I start typing.
How can it be helped?
Thanks!
UPDATE Here's the window of the sample made by har07.
In the lower unbound TextBox the watermark works as expected. However, when I try to type smth in the first TextBox, the watermark is still there.
UPDATE2 Just in case someone will make the same mistake - appeared I was indeed setting my property in code while initializing the window, and that was the reason the watermark wasn't working. Now, thanks to har07, all is fine.
As I said in comment, there shouldn't be any problem setting watermark along with Text binding. I made simple test using 2 textboxes, one with Text property bound and the other not bound. Both are showing same behavior : watermark text replaced by typed text, and get dimmed upon textbox lost focus.
//View
<Controls:MetroWindow x:Class="WpfMahApps.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:local="clr-namespace:WpfMahApps"
Title="StackOverflow" Height="500" Width="625"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.DataContext>
<local:Person/>
</Grid.DataContext>
<StackPanel>
<TextBox Text="{Binding Name}" Controls:TextboxHelper.Watermark="Enter text here...."/>
<TextBox Controls:TextboxHelper.Watermark="Enter text here too...."/>
</StackPanel>
</Grid>
</Controls:MetroWindow>
//Model (I'm using MvvmLight for implementation of INPC)
public class Person : ObservableObject
{
private string _name = "Default Name";
public String Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged(() => Name);
}
}
}
Download test project
I am attempting to make a WPF application. The application needs to use a "list view" to show results of queries to the database. I have been able to successfully create the application (GUI, database, LINQ, etc.), however, the display of my query results appear more "gridlike".
The specifications for the project below show that each record that appears in the results needs to have a green circle icon next to it. I have removed the actual results from the images below to keep the contents of the database private.
I don't have enough Reputation Points to post images, so I posted pictures so a sample/testing domain that I use. You can see screenshots here of the WPF app and code here:
http://digitalworkzone.com/WPF.html
What am I doing incorrectly? Is there something I need to add or modify to my code to be able to get the green circles and more of a "list" style to display my query results?
Understand the WPF content model. http://msdn.microsoft.com/en-us/library/bb613548.aspx
Anything that has a 'Content' property basically behaves in two ways. If the 'Content' is set to something that derives from UIElement, then the class will manage it's own presentation. Anything else, however, will just get .ToString() called, and it's text displayed instead.
What this means in the long run is that everything in WPF can display anything. If you want to show a button in a button, you can. For example:
<Button>
<Button.Content>
<Button Content="This will show as text" />
</Button.Content>
</Button>
The inner button will have text, but the outer button will show a Button because Button derives from UIElement and therefore will handle its own presentation.
In your picture examples above, you have ListBoxes/DataGrids that you want to fill in with graphical information. Try this out:
<ListBox HorizontalContentAlignment="Stretch">
<ListBox.Items>
<Button Content="One"/>
<Button Content="Two"/>
<Button Content="Three"/>
<Button Content="Four"/>
</ListBox.Items>
</ListBox>
Now you have a ListBox that shows Buttons instead of Text. You can take this a step further and contain the items in a stackpanel, for example:
<ListBox HorizontalContentAlignment="Stretch">
<ListBox.Items>
<StackPanel Orientation="Horizontal">
<Button Content="A button"/>
<Label Content="Some text" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="A button"/>
<Label Content="Some text" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="A button"/>
<Label Content="Some text" />
</StackPanel>
</ListBox.Items>
</ListBox>
Now we have items that contain a layout container (StackPanels, which then contains other elements).
However, if you set the ItemsSource elsewhere, you can actually use a DataTemplate to display the contents. A DataTemplate in effect targets a particular class and lays out it's contents as defined in XAML. Consider:
Code Behind:
public partial class MyWindow : UserControl {
public MyWindow() {
InitializeComponent();
MyListBox.ItemsSource = new List<Person> {
new Person("Sam", "Smith"),
new Person("Jim", "Henson"),
new Person("Betty", "White"),
};
}
XAML:
<ListBox HorizontalContentAlignment="Stretch" x:Name="MyListBox" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<Label Content="{Binding FirstName}"/>
<Label Content="{Binding LastName}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now when the Listbox displays, it will cycle through each of the items in the ItemsSource property, and then lay them out using the DataTemplate. It's possible to have the DataTemplate target specific classes by using the DataType property if you're using polymorphism (as in different types of people such as 'Cusomters' or 'Employees' which all derive from 'Person).
The problem with this approach is that you are setting the value of the items directly, which is bad form. It's better to define a class that handles all of the data for your view separately. Consider:
public class ViewModel {
// WPF will automatically read these properties using reflection.
public List<Person> People {
get {
return new List<Person> {
new Person("Sam", "Smith"),
new Person("Jim", "Henson"),
new Person("Betty", "White")
};
}
}
}
That will hold all the data for the view, now let's add it to the actual window. First we need to reference the namespace ('xmlns' means xml namespace):
<Window x:Class="Sharp.MyWindow"
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:lol="clr-namespace:Sharp">
The namespace is Sharp (the namespace where my stuff lives), and the alias we'll give it is lol. Now we attach our ViewModel class to the window by setting it to the DataContext property, as in:
<Window>
<Window.DataContext>
<lol:ViewModel />
</Window.DataContext>
</Window>
This makes all of the public properties on the ViewModel class available to the Window. This way, if we want to read the Persons information into our ListBox, we simply say:
<ListBox HorizontalContentAlignment="Stretch" ItemsSource="{Binding People}" >
...
</ListBox>
Notice that we say ItemsSource={Binding People}, which means 'scan the ViewModel for any public properties called 'People' and then retrieve those results. This is essentially the fundamentals behind the MVVM approach. You might have all of your business logic in one or many classes which handle the main application operation in a Model, but then you have a ViewModel which interacts with the Model and exposes the results as public properties. WPF automatically binds to those properties and presents them for your. The information just flows, rather than setting the values by force.
To really understand how WPF is supposed to work, you should take some time to understand the basics of MVVM. WPF was really designed with MVVM in mind, and so to really get how WPF is supposed to work, you really should take the time to get your head around it. Take a look at:
http://agilewarrior.wordpress.com/2011/01/11/simple-mvvm-walkthrough-part-i/ .
<ListBox ItemsSource="{Binding QueryResults}">
<ListBox.ItemsTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImageSource}"/>
<TextBlock Text="{Binding TextSource}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemsTemplate>
</ListBox>
Will work if you have a list of objects named QueryResults in your code behind. Each object needs to have an string property named ImageSource and a string property named TextSource.
However, since you only need to display a green circle icon for each of the items, you can hardcode the image source. The above will work if you want to have a different icon for each, though.
Also note that in order for this to work, you need to set the DataContext of the window to DataContext="{Binding RelativeSource={RelativeSource Self}}"
My problem is similar to the one described in this question:
WPF MVVM Button Control Binding in DataTemplate
Here is my XAML:
<Window x:Class="MissileSharp.Launcher.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MissileSharp Launcher" Height="350" Width="525">
<Grid>
<!-- when I put the button here (outside the list), the binding works -->
<!--<Button Content="test" Command="{Binding Path=FireCommand}" />-->
<ListBox ItemsSource="{Binding CommandSets}">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- I need the button here (inside the list), and here the binding does NOT work -->
<Button Content="{Binding}" Command="{Binding Path=FireCommand}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
It's just a ListBox, bound to an ObservableCollection<string> named CommandSets (which is in the ViewModel).
This binding works (it displays a button for each item in the collection).
Now I want to bind the button to a command (FireCommand), which is also in the ViewModel.
Here's the relevant part of the ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
public ICommand FireCommand { get; set; }
public ObservableCollection<string> CommandSets { get; set; }
public MainWindowViewModel()
{
this.FireCommand = new RelayCommand(new Action<object>(this.FireMissile));
}
private void FireMissile(Object obj)
{
System.Windows.MessageBox.Show("fire");
}
}
The binding of this button does NOT work.
From what I've understood from the question I linked above, the binding doesn't work because:
(correct me if I'm wrong)
The button is inside the ListBox, so it only "knows" the binding of the ListBox (the ObservableCollection, in this case), but not the binding of the main window
I'm trying to bind to a command in the main ViewModel of the main window (which the button doesn't "know")
The command itself is definitely correct, because when I put the button outside the ListBox (see the XAML above for an example), the binding works and the command is executed.
Apparently, I "just" need to tell the button to bind to the main ViewModel of the form.
But I'm not able to figure out the right XAML syntax.
I tried several approaches that I found after some googling, but none of them worked for me:
<Button Content="{Binding}" Command="{Binding RelativeSource={RelativeSource Window}, Path=DataContext.FireCommand}" />
<Button Content="{Binding}" Command="{Binding Path=FireCommand, Source={StaticResource MainWindow}}" />
<Button Content="{Binding}" Command="{Binding Path=FireCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
Could someone please:
give me the proper XAML to bind the button inside the ListBox to a command in the form's MainViewModel?
point me to a link where this advanced binding stuff is explained in a way that a WPF/MVVM beginner can understand?
I'm feeling like I'm just copying and pasting arcane XAML incantations, and so far I don't have any clue (and can't find any good documentation) how I would figure out by myself in which cases I'd need RelativeSource or StaticResource or whatever instead of a "normal" binding.
It's:
{Binding DataContext.FireCommand,
RelativeSource={RelativeSource AncestorType=ListBox}}
No need to walk up to the root unless you actually change the DataContext along the way, but as the ListBox seems to bind to a property on the main VM this should be enough.
The only thing i recommend reading is the Data Binding Overview, and the Binding class documentation (including its properties).
Also here is a short explanation on how bindings are constructed: A binding consists of a source and a Path relative to that source, by default the source is the current DataContext. Sources that can be set explicitly are: Source, ElementName & RelativeSource. Setting any of those will override the DataContext as source.
So if you use a source like RelativeSource and want to access something in the DataContext on that level the DataContext needs to appear in the Path.
This may be considered unrelated by most, but this search is only 1 of 3 results that you'll find searching for data binding commands to controls inside a data template--as it relates to Xamarin Forms. So, maybe it'll help someone now-a-days.
Like me you may wonder how to bind commands inside a BindableLayout. Credit jesulink2514 for answering this at Xamarin Forums, where it's probably overlooked by many because of all the comments. Here's his solution, but I'm including the link below:
<ContenPage x:Name="MainPage">
<ListView Grid.Row="1"
ItemsSource="{Binding Customers}"
VerticalOptions="Fill"
x:Name="ListviewCustomer">
<ListView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding Property}"/>
<Button Command="{Binding BindingContext.ItemCommand, Source={x:Reference MainPage}}"
CommandParameter="{Binding .}">Click me</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
https://forums.xamarin.com/discussion/comment/217355/#Comment_217355
I have a ComboBox in WPF which is databound, and has data template which controls how each of the items is displayed. I have made it so that each item is displayed with two bits of text (for the Name and Path properties) and one image (for the Icon property).
At the moment when I select an item from the ComboBox the textbox bit of the ComboBox just changes to say "TestWPF.Result" which is the name of the class which I have populated the ComboBox with.
I'm interested in one (or both) of two things:
How do I change it so that it displays the value of one of the fields there (eg. so it shows the value of the Name field rather than the name of the class)?
Is it possible get it to use the same DataTemplate there as in the list of items, so that once I have selected an item it displays in the closed ComboBox the same way as it looks in the list of items. Basically I've got a DataTemplate called ShowResults and a ComboBox which uses that template. I've also added in a separate ContentControl which I've got to show the details of the selected item in the ComboBox, but I want to get that to replace the textbox in the ComboBox.
Update:
Thanks for the first answer. I've tried using a separate ContentControl, as you've described, and it works fine. The question now is how to replace the textbox part of the ComboBox with this ContentControl. Any hints on that would be most welcome.
Also, is it possible to replace the textbox bit of the ComboBox control with a mixture of the ContentControl and a textbox, so that I can still type in the textbox to help select items from the ComboBox, but then when I close the dropdown the rest ContentControl bit will be populated with the rest of the text and the icon. Hope that makes sense - ask questions if it doesn't!
Code:
I've been asked to post my code - so here it is. I've tried to remove things that I know are definitely not relevant, but I'm not sure exactly what is relevant so when in doubt I've left things in.
<Window x:Class="TestWPF.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:TestWPF"
Title="Window1" Height="300" Width="843" Loaded="Window_Loaded">
<Window.Resources>
<DataTemplate x:Key="ShowResult" DataType="TestWPF.Result">
<StackPanel Margin="5" Orientation="Horizontal">
<Image Width="32" Height="32" Source="{Binding Path=Image}"/>
<StackPanel Margin="5">
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}"/>
<TextBlock Text="{Binding Path=Path}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid Width="786">
<Button Height="23" HorizontalAlignment="Right" Margin="0,24,166,0" Name="btnTest" VerticalAlignment="Top" Width="75" Click="btnTest_Click">Add</Button>
<ComboBox StaysOpenOnEdit="True" DropDownClosed="comboBox1_DropDownClosed" PreviewTextInput="comboBox1_PreviewTextInput" SelectionChanged="comboBox1_SelectionChanged" ItemTemplate="{StaticResource ShowResult}" Margin="259,109,22,89" Name="comboBox1" IsEditable="True" />
<ContentControl Height="50" Margin="268,0,22,21" Name="contentControl1" VerticalAlignment="Bottom" Content="{Binding ElementName=comboBox1,Path=SelectedValue}" ContentTemplate="{StaticResource ShowResult}"/>
</Grid>
You got the binding part right - binding to the data and using a DataTemplate to display the source the way you want to.
As to your second question, a way to do it would be to use a ComboBox with IsEditable="True" as you have, and withing the TextChanged event handler check if the comboBox.Items contains the new value, if not check use Linq to seach for a match:
if (comboBox.Items.Contains(e.NewValue))
return;
var matches =
with comboBox.Items
select item
where item.BeginsWith(e.NewValue);
if (matches.Count > 0)
comboBox.SelectedItem = matches.First();
Just place the Property Binding expression to the textBox,You dont need to apply template.
Another way to get exact Data template, Place a ContentControl in the place of textBox and assign the same DataTemplate (say x:Name="robinTemplate")
<ContentControl Content="{Binding ElementName=cmbBox,Path=SelectedValue}" ContentTemplate="{StaticResource robinTemplate}"/>
For making the Selected content display in the same way :
Create a copy of the combobox control template and you will find a ContentPresenter there. Replace that with the ContentControl.. This is not the right solution though.