I am making the UIs for entering master data for various business entities (customer, etc). I run into this need to group together a TextBlock and a TextBox together frequently. i.e.
<Label Content="Civil Status:" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="3" Name="civilStatusTextBox" Text="{Binding Path=CivilStatus, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
<Label Content="Company:" Grid.Column="0" Grid.Row="2" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="3" Name="companyTextBox" Text="{Binding Path=Company, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
Is there any way to do this with less typing? i.e.
<custom:LabeledTextBox Label="Civil Status:" Text="{Binding Path=CivilStatus, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" ... />
Or possibly, any libs which offer something like this?
EDIT : Forget the container Grid for a moment and assume it is a StackPanel.
Here's a step-by-step solution that I managed to put together. To set the stage, I'm going to assume we've got a very simple UserControl that has the following XAML content.
<UserControl x:Class="WpfApplication2.UserControl1" [ ... auto gen code removed ... ] >
<TextBox MinWidth="50" x:Name="TBox" />
</UserControl>
From an XAML that uses our UserControl we'd essentially want to set a data binding for the Text property on TBox. Idealy we could use a plain syntax like:
<local:UserControl1 TBox.Text="{Binding ...}" />
unfortunately I don't know any XAML syntax that would allow targeting an sub-element's property, so the next best thing would be to introduce a "staging" property in the UserControl itself and bind through that.
Since Binding only works for Dependency properties, the property we'll introduce needs to be a DependencyProperty. We'll also bind the Text property of TBox straight to our DependencyProperty from code.
The code-behind for the UserControl looks like this:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
// Set binding from code
this.TBox.DataContext = this;
this.TBox.SetBinding(TextBox.TextProperty, new Binding { Path = new PropertyPath("TBValue"), Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
}
public static readonly DependencyProperty TBValueProperty = DependencyProperty.Register("TBValue", typeof(string), typeof(UserControl1));
public string TBValue
{
get { return this.GetValue(TBValueProperty) as string; }
set { this.SetValue(TBValueProperty, value); }
}
}
}
With this in place we can use the UserControl like this, binding to the TBValue property:
<local:UserControl1 TBValue="{Binding Path=Test, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
What you want to achieve (Master-Detail-Views) is actually well supported by Visual Studio out of the box. Open the following Menu structure : Project -> Add Data Source, then choose data source type Object. In the following, select the classes that you want to generate input fields for and finish the wizard.
Then, if not already open, open up your Data Sources tool window (Shift+Alt+D). You should see a DataSource of the type you just generated. To get a labelled field for each property of the object, open the source dropdown and click Details.
Note that the properties have such dropdowns as well, so that you can choose how you want to edit them (ComboBox, TextBox, Custom, no editor,...).
Now just drag the DataSource onto your window. You will get a Grid that's filled with all the labels and editors you desired. DataBinding and validation is also supported right away, so all you will have to do is set the generated Grid's DataContext.
Hope this saves you some work.
P.S. The screenshots are made in my german VS instance, still I thought they might help you identify the right dialogues / windows.
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 am using the MVVM pattern to write a WPF application. I created the UI using a TabControl. In one tab I have a list of clients in my company and in the other tab I have a form which is used to add new clients. I want two things:
Adding new client --> new position on the list when the tab with clients is pressed
Clearing form TextBoxes after adding a client
Neither of them works.
My DataGrid part:
<StackPanel DataContext="{StaticResource ClientsVM}">
<Image HorizontalAlignment="Left" VerticalAlignment="Top" Source="pack://application:,,,/Insurance company;component/Resources/logo.png" Height="100" Margin="5,15,0,0"/>
<DataGrid Name="ClientsGrid" ItemsSource="{Binding Clients, Mode=TwoWay}" IsReadOnly="True" Margin="130,0" AutoGenerateColumns="False" ColumnWidth="101">
<DataGrid.Columns>
<DataGridTextColumn Header="Client ID" Binding="{Binding ClientId}"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Surname" Binding="{Binding Surname}"/>
<DataGridTextColumn Header="PESEL" Binding="{Binding PESEL}" />
</DataGrid.Columns>
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding ClientsGridLeftDoubleClickCommand}" CommandParameter="{Binding ElementName=ClientsGrid, Path=SelectedItem}" />
</DataGrid.InputBindings>
</DataGrid>
</StackPanel>
Part of my ViewModel:
private ObservableCollection<ClientSet> _clients;
public ObservableCollection<ClientSet> Clients
{
get { return _clients; }
set
{
if (_clients != value)
{
_clients = value;
RaisePropertyChanged(() => "Clients");
}
}
}
Unfortunately, doing something like:
_clients = new ObservableCollection<ClientSet>(updatedListOfClientsHere);
doesn't work. Why not?
The other thing is with clearing the form. It looks like this:
<Label Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="100" FontSize="15">Surname</Label>
<TextBox Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="141" Name="Surname" Text="{Binding Client.Surname, Mode=TwoWay, ValidatesOnDataErrors=True}"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="100" FontSize="15">Name</Label>
<TextBox Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="141" Name="Name" Text="{Binding Client.Name, Mode=TwoWay, ValidatesOnDataErrors=True}" />
And the values typed by the user are properly reflected in the ViewModel class. But if I clear them in the ViewModel - nothing happens in the UI. Part of my ViewModel here:
private ClientSet _client;
public ClientSet Client
{
get { return _client; }
set
{
if (value != _client)
{
_client = value;
RaisePropertyChanged(() => "Client");
};
}
}
// Some code
// Clearing the form:
_client = new ClientSet(); // This shouldn't work?
Client.Name = string.empty; // This should work!!!
I am really out of ideas right now.
I have noticed a few problems from your question. First, calling the private member of any property in a view model should only be done when you don't want the UI to update. If you want the UI to update, then you have to use the public properties, because that is what notifies the INotifyPropertyChanged interface (through your RaisePropertyChanged method). So to clear the client list, just call:
Clients = new ObservableCollection<ClientSet>(updatedListOfClientsHere);
Next, I don't know for sure because you didn't show your ClientSet class, but I'm guessing that you didn't implement the INotifyPropertyChanged interface in it. However, you are still expecting property changes of that class to be updated... they won't unless you either implement it in that class, or wrap each property in your view model and call your RaisePropertyChanged method from there. Then, calling Client.Name = string.empty; will clear that field.
I'm not entirely sure what you're asking, but lets see:
First, with your observable collection, you don't need to set it again. If you create a new Client, all you need to do is :
Clients.Add(new_client_object);
That's all. Otherwise, if it takes a while to create the clients, your program will get slower (I've seen this happen, trust me ...)
If I understand correctly, you want to clear the items in the add new client after you add it. All you need to do here is make sure the content control (hopefully your add client view is a usercontrol or something like that) receives an object of type client, and after you add it, just set it to null, or put a new (empty) object, or set them manually to empty strings (Be sure to have a a backup in case you want to cancel, or that you don't overwrite your new client fields).
You could add some screenshots to clarify what you want/need.
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}}"
<Button x:Name="btn_binding" Content="Binding" HorizontalAlignment="Right" Height="44" Margin="0,127,63,0" VerticalAlignment="Top" Width="67"/>
<TextBox x:Name="txt_binding" Text="{Binding Content,ElementName=btn_binding}" Height="48" Margin="0,48,31,0" TextWrapping="Wrap" VerticalAlignment="Top" HorizontalAlignment="Right" Width="130"/>
it's result will be like this
Then i can get the same result by the below code
public partial class biding : Window
{
public biding()
{
this.InitializeComponent();
txt_binding.Text=btn_binding.Content.ToString();
}
}
please tell me what are the differences of both and i want to which one is best to use...
In first case you bind your TextBox.Text property to Button.Content property and it will be changed everytime the Content is changed. In second you just set Text property once in constructor and changing the Button.Content won't affect into it.
Hope it's clear.
In my point of view... Xaml binding will be reflected in the designer at the time when you typed... No need to wait to run the solution to see the output..
In case of code behind .. you need to run the solution to see the results..
I have been playing around and looking around on how to Bind a modelview to a view, but i cant seem to work it out.
I have a view called Search and I want to bind it to SearchModelView.
View has one button and one textbox and looks:
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<ComboBox Height="23" HorizontalAlignment="Left" Margin="12,40,0,0" Name="comboBox1" VerticalAlignment="Top" Width="174" />
<Label Content="Client:" Height="28" HorizontalAlignment="Left" Margin="0,12,0,0" Name="label1" VerticalAlignment="Top" Width="71" />
<Label Content="Client Reference:" Height="28" HorizontalAlignment="Left" Margin="0,69,0,0" Name="label2" VerticalAlignment="Top" Width="117" />
<TextBox
x:Name="clientRefTxt"
Text="{Binding Path=ClientRef, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"
Height="23"
HorizontalAlignment="Left"
Margin="12,103,0,0"
VerticalAlignment="Top"
Width="174" />
<Button
Content="Search Debtors"
Height="23"
HorizontalAlignment="Left"
Margin="12,140,0,0"
Name="button1"
VerticalAlignment="Top"
Width="89"
Command="{Binding Path=SearchCommand}"/>
</Grid>
And I want it to bind to SearchViewModel:
namespace Master.ViewModel
{
public class SearchViewModel:WorkspaceViewModel
{
RelayCommand _searchCommand;
readonly Search _search;
#region Search Properties
public string ClientRef
{
get { MessageBox.Show("GET CLIENTREF"); return _search.ClientRef; }
set
{
MessageBox.Show("SET CLIENTREF");
if (value == _search.ClientRef)
return;
_search.ClientRef = value;
base.OnPropertyChanged("ClientRef");
}
}
#endregion
public ICommand SearchCommand
{
get
{
MessageBox.Show("SEARCHCOMMAND");
if (_searchCommand == null)
{
_searchCommand = new RelayCommand(
param=> this.Search(),
param=> this.CanSearch
);
}
return _searchCommand;
}
}
public void Search()
{
MessageBox.Show("SEARCHING");
}
bool CanSearch
{
get { return true; }
}
}
}
I removed all the assemblies at the top but assume that they are all there. Also note that SearchViewModel is in a separate dll, not in the exe with the View.
Any help would be great or at least a pointer in the write direction, I have already read the msdn article on MVVM and that didnt help...I kinda need a better rundown on binding those too pieces.
Thanks in Advance.
P.S.
Some more details:
SearchViewModel belongs to Master.ViewModel
SearchView is part of GUI.View
I have and idea how the binded objects work, im not to sure on how to bind the view to the viewmodel
Is your View a Grid? I've only used UserControl or Window types as Views, but you may have success using a Grid.
Regardless, this is the cleanest way to instantiate the ViewModel with a UserControl View. Just replace the UserControl tags with Grid tags if you're using a Grid.
<UserControl ...(blah blah)
xmlns:viewmodel="clr-namespace:Master.ViewModel">
<UserControl.DataContext>
<viewmodel:SearchViewModel/>
</UserControl.DataContext>
I believe keeping out of the View's code unless necessary is the preferred pattern for MVVM - let the XAML wire things up for you when possible.
You need to set the view's DataContext to an instance of the view model. There are a variety of ways of doing this, including frameworks that wire it up automagically, but the easiest way to get started is to do it in the constructor of the view:
partial class Search : Window
{
public Search()
{
InitializeComponent(); // provided by Visual Studio
DataContext = new SearchViewModel(); // all-important!
}
}
Obviously you may need to provide other information to initialise the SearchViewModel but hopefully this is enough to get you on the right track.
Your will need to bootstrap your application like #itowlson suggests.
But if you have more than one ViewModel you should allow WPF to do it for you. The basic way to do this (which is easy to maintain until you start having more than a dozen views) is to create a DataTemplate to tie the View with your ModelView(which most people call ViewModel).
So the xaml you provided is probably in a UserControl(at least it should be) so you need to do several things
First create a ResourceDictionary
(fast way is to right-click your project and click Add -> Resource Dictionary
In that file(let's name it Resources.xaml) put this :
<DataTemplate DataType="{x:Type vm:SearchViewModel}">
<vw:SearchView>
</DataTemplate>
The above is assuming you put the namespaces vw and vm for View and ViewModel namespaces respectively
Go to your App.xaml and put this:
<Application.Resources>
<ResourceDictionary Source="Resources.xaml"/>
</Application.Resources>
The above will tell WPF that whenever it encounters an object of type SearchViewModel to:
Instantiate a SearchView object
Set it's DataContext to the SearchViewModel object
HTH