I'm getting started with WPF and finding it difficult to get even the most simple binding working. Here's some givens...
I have an object that queries an existing database and returns a "datatable" object, the data comes back (and for test purposes, only a single row and single column called "MyTextColumn")
The data table is not "Strongly typed" as I've read in other places trying to force the issue of strongly typed objects. I want to understand the underlying mechanisms from the code-behind perspective, AND not from the XAML perspective. From reading, apparently you can't bind directly to a data table, but you can to the "DefaultView" of a DataTable object (makes no sense to me since they point to same record (or set of records, with exception of say a filter of some type).
So, in the XAML portion of the window,
<src:MyWindow blah, blah >
<Grid Name="grdTesting">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Name="lblMyTextColumn"
Content="Just the label"
Grid.Row="0" Grid.Column="0 "/>
<TextBox Name="txtMyTextColumn"
Grid.Row="0" Grid.Column="1"
Width="120" />
</Grid>
</src:MyWindow>
So now, I'm in the code-behind, what I've read is you have to have a BindingListCollectionView oView;
public partial class MyWindow : Window
{
BindingListCollectionView oView;
MyQueryingManager oTestMgr;
public MyWindow()
{
InitializeComponent();
oTestMgr = new MyQueryingManager();
DataTable oResults = oTestMgr.GetTheData();
// Data context for the form bound to the Table retrieved
oView = new BindingListCollectionView( oResults.DefaultView );
// One place said the Window should get the binding context
DataContext = oView;
// another indicated the grid... Just need to know which I SHOULD be using
grdTesting.DataContext = oView;
// Now, for my binding preparation...
Binding bindMyColumn = new Binding();
bindMyColumn.Source = oView;
bindMyColumn.Path = new PropertyPath("MyTextColumn");
txtMyTextColumn.SetBinding( TextBox.TextProperty, bindMyColumn );
}
}
So... what am I missing here... Should be simple, nothing complex, I have a data table, with a record, that has a value. Run the form (no matter binding context to the Window or the Grid), and the record value does not show in the textbox control. Once I understand the behavior on a single textbox, I can go on with all the other elements (validation, input mask, formatting, etc), but am stuck right at the gate on this one.
Thanks
First, you can bind to a DataTable but you can also use the default view (which is a DataView) etc.
Usually, you bind a DataTable to an ItemsControl or a control that derives from it, such as ListBox, DataGrid etc. Then each container will get a DataRow (or DataRowView) and the binding will be easy.
Since you are binding it directly to a TextBox inside a Grid you would have to specify both Row and Column in the binding. The correct path to bind to the column named "MyTextColumn" in the first row is Rows[0][MyTextColumn]
Try this
Binding bindMyColumn = new Binding();
bindMyColumn.Source = oResults;
bindMyColumn.Path = new PropertyPath("Rows[0][MyTextColumn]");
txtMyTextColumn.SetBinding(TextBox.TextProperty, bindMyColumn);
A problem if you're binding directly to the DataTable is that it doesn't implement INotifyPropertyChanged so the UI won't know that the value has changed if it is changed from some other source. In this case, you can use a DataView instead. The binding syntax will be a little different here since you access the DataRowViews directly with the index operator.
dataView = oResults.DefaultView;
// Now, for my binding preparation...
Binding bindMyColumn = new Binding();
bindMyColumn.Source = dataView;
bindMyColumn.Path = new PropertyPath("[0][MyTextColumn]");
txtMyTextColumn.SetBinding(TextBox.TextProperty, bindMyColumn);
A Grid is simply a layout panel (think spreadsheet). You have to manually place controls into the cells.
What you are looking for is probably a DataGrid, which has the ability to generate columns automatically using reflection (or you can define them yourself). The WPF Toolkit from Microsoft has one you can use if you don't want to go with one of the many 3rd party data grid controls.
You are using a Grid which is a layout control, which means that it has visual elements as children, not items. You should use an ItemsControl like Listbox for example, and bind the ItemsSource property to your collection.
Related
In a project I have a very tricky requirement I don't know how to solve:
I have several datagrids in a single wpf window (I use MVVM) all binded to some collection in the linked ViewModel.
The customer wants to edit each of these grids, either within the grid or in a common textbox (like in excel).
I'm banging the head on how to do the latter. What I would do is bind the textbox with a property in the viewmodel, but when the value is changed there, I need to change the value in the original property binded with the datagrid cell accordingly. In other words, I need to know what collection and which property of that collection I need to change with the data in the textbox accordingly .
I tried several ways but with no luck.
Reflection? DependencyProperty? What else?
Any help?
Thank you
Assuming that you're using the built-in WPF DataGrid, you'll need to setup your grid similarly:
<DataGrid SelectionUnit="Cell" SelectionMode="Single" ItemsSource="{Binding Data}" SelectedCellsChanged="DataGrid_OnSelectedCellsChanged">
...
</DataGrid>
Also give your TextBox a name:
<TextBox x:Name="textBox" DockPanel.Dock="Top" />
In the code-behind, you'll need to manually wire up this event, since apparently the DataGrid doesn't allow you to bind to the selected item/cell/value when using SelectionUnit="Cell":
private void DataGrid_OnSelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
if (e.AddedCells.Count == 0)
this.textBox.SetBinding(TextBox.TextProperty, (string) null);
else
{
var selectedCell = e.AddedCells.First();
// Assumes your header is the same name as the field it's bound to
var binding = new Binding(selectedCell.Column.Header.ToString())
{
Mode = BindingMode.TwoWay,
Source = selectedCell.Item,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
this.textBox.SetBinding(TextBox.TextProperty, binding);
}
}
I tried getting this done without code-behind but after looking around it didn't seem like this was possible.
In addition to tencntraze answer I used this code to get te property bound to the cell
var property = (selectedCell.Column.ClipboardContentBinding as Binding).Path.Path;
I'm totally new to C# and WPF and I'm trying to do my best with the data binding. I have a MyClass which implements INotifyPropertyChanged; so everytime I change a property value, this is updated in my UI. Then I have bound the DataContext of a stackpanel to an object of MyClass. Like this:
<StackPanel Name="stackPanel1" DataContext="{Binding}">
<TextBlock Name="textBlock1" Text="{Binding Path=Title, Mode=OneWay}" />
</StackPanel>
In code behind I do this:
item = new MyClass();
stackPanel1.DataContext = item;
and the binding is working fine. If I replace my current binding source object with another one, I have to manually set this by typing again the datacontext binding:
item = new MyClass();
stackPanel1.DataContext = item;
item1 = new MyClass();
.
. //manipulate item1
.
if (item1 is ok)
item=item1;
stackPanel1.DataContext = item;
Is there a better way to replace my source object and have all the associated bindings updated?
When you say stackPanel1.DataContext = item;, you are setting the property, not binding it.
When you set the property, you are setting it equal to an instance of the object. When you bind it, you are telling it it will be getting its value from some other location, so look in that location anytime it needs to get the value.
Providing your class that contains the bound properties implements INotifyPropertyChanged, then the UI will be alerted anytime a bound property changes, which causes the binding to get reevaluated.
For example, if you had set the DataContext initially with
MyWindow.DataContext = this;
where this was your Window, and your Window had a propety of type MyClass called Item, then you could bind the DataContext using the following
<StackPanel DataContext="{Binding Item}" ...>
and anytime you updated the property Item, your StackPanel's DataContext would also update (providing you implement INotifyPropertyChanged).
If you're interested, I like to blog about beginner concepts in WPF, and you may be interested in checking out my article What is this "DataContext" you speak of?, which is a very simple explanation of what the DataContext is and how it's used.
To summarize, WPF has two layers: the UI layer and the Data Layer. The DataContext is the data layer, and when you write {Binding SomeProperty}, you are actually binding to the data layer. Typically you set the data layer (DataContext) once in your code behind, and then use Bindings in your XAML to make your UI layer display information from the data layer.
(You may also be interested in checking out my Simple MVVM Example, which contains a very simple working code sample, and illustrates some examples of how INotifyPropertyChanged is implemented and how the UI layers and Data layers can be completely separate)
You may add a CurrentItem property in your MainWindow (or UserControl or whatever it is) and also implement INotifyPropertyChange for that property. Then set
DataContext = this;
in the MainWindow's constructor and bind like this:
Text="{Binding Path=CurrentItem.Title}"
Now whenever you set
var item = new MyClass();
...
CurrentItem = item;
the binding will be updated.
DataContext="{Binding}"
and
stackPanel1.DataContext = item;
Both do basically the same thing. The difference being that one is done in XAML and the other is in code. While the first example would allow binding to be updated given a binding parent the second one must be updated every time you want to change what the stackpanel is attached to. IMHO you should create a common binding parent to bind against. This would allow you to change the child bindings without having to set the context everytime.
<StackPanel Name="parentPanel">
<StackPanel Name="stackPanel1" DataContext="{Binding Path=Child}">
<TextBlock Name="textBlock1" Text="{Binding Path=Title, Mode=OneWay}" />
</StackPanel>
</StackPanel>
parent = new ParentClass();
parent.Child= new MyClass();
parentPanel.DataContext = parent ;
Now if notify property changed was created on ParentClass correctly you can changing the binding for the child stack panel
parent.Child= new NewClass();
I have a basic project in WPF.
All it does it retrieve / update products.
As shown in the image below, the user enters an ID, the data is then displayed according to it, and the user is able to change the data and click 'Save Product' to save it to the database.
The GetProduct(int id) function retrieves a product by the ID provided.
The SaveProduct() function saves the changed fields.
Also, there are two DataTemplates:
1) For the ProductModel - includes 3 textboxes: ProductId, ProductName, UnitPrice.
2) For the ProductViewModel - includes the save/get buttons + a textbox for the user to enter the id of the desired product.
What I'm trying to do is get the changed data when a user clicks the 'Save Product' button.
The most ideal way in my opinion, is to use Binding.
Each textbox is already binded, but I have no idea how to get the binded data.
Here is an example of a binded textbox in the FIRST DataType (ProductModel):
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding ProductId}" Margin="5" Width="150" />
There is one for each of the following properties: ProductId, ProductName and UnitPrice.
IMPORTANT!: The Get/SaveProduct() functions are in the ProductViewModel class, while the actual product class is - you guessed it - ProductModel. The ProductViewModel class holds a variable that contains the current product displayed.
This is the button that's used to save the info - it is written in the SECOND DataType (ProductViewModel):
<Button Content="Save Product" DockPanel.Dock="Right" Margin="10,2" VerticalAlignment="Center" Command="{Binding Path=SaveProductCommand}" Width="100" />
The SaveProductCommand command simply fires the SaveProduct() function.
I have a few questions regarding this whole subject:
What does it mean when a binding is used like this : {Binding ProductId} ?
The default binding mode for textboxes is TwoWay as far as I remember. But in this case, ProductId/Name + UnitPrice are not dependency properties, therefore is it right that the binded values do not update/sent back when the text in the textboxes is changed? (Since there isn't an event attached to it...)
A data context was never configured in my project, but all of the "binding tags" in my XAML pages don't seem to have a defined source. Could it be that the source is actually the DataType in the DataTemplate that includes the binded objects?
The SECOND DataTemplate (the ProductViewModel one) has this ContentControl tag: <ContentControl Margin="10" Content="{Binding Path=CurrentProduct}" />.
What is it's purpose?
If a TwoWay binding were/does occur, how do I get the values from within the SaveProduct() function? Do I just refer to, say CurrentProduct.ProductName to get the changed name?
Much thanks to everyone who takes their time to answer - I appreciate it so much!
What does it mean when a binding is used like this : {Binding
ProductId} ?
The specific control property you have this binding set on is going to look for the ProductId property on the object set as the DataContext and set the propertys value in the control accordingly.
The default binding mode for textboxes is TwoWay as far as I remember.
But in this case, ProductId/Name + UnitPrice are not dependency
properties, therefore is it right that the binded values do not
update/sent back when the text in the textboxes is changed? (Since
there isn't an event attached to it...)
You do not need to make the properties within your object a DependencyProperty for TwoWay binding to occur.
A data context was never configured in my project, but all of the
"binding tags" in my XAML pages don't seem to have a defined source.
Could it be that the source is actually the DataType in the
DataTemplate that includes the binded objects?
The bindings being set within your XAML will use the object stored within the DataContext, thus if you do not explicitly set the DataContext of the view, it will be null. You should note however that the DataContext is inherited from its parent. If you are in fact setting the content by using say, CurrentProduct, then all the properties will be available to bind to per your Product type.
The SECOND DataTemplate (the ProductViewModel one) has this
ContentControl tag:
<ContentControl Margin="10" Content="{Binding Path=CurrentProduct}" />
What is it's purpose?
It is acting as the container of your CurrentProduct, which can contain one and only one item.
If a TwoWay binding were/does occur, how do I get the values from
within the SaveProduct() function? Do I just refer to, say
CurrentProduct.ProductName to get the changed name?
Without seeing the entire application, my guess is that the ContentControl is being set to the CurrentProduct and your TextBox, etc.. are all bound to the respective properties, such as CurrentProduct.ProductId, etc... The product which you want to save is in fact the CurrentProduct. When you call save within your ViewModel, you simply access the CurrentProduct and persist it as needed, where CurrentProduct.PropertyName will contain the changes which were propagated from the UI.
Question
Basically I would like to do the following but it seems that I cannot:
UserControl myControl = new UserControl();
DataTemplate template = new DataTemplate(myControl);
The question: Is it possible to construct a DataTemplate from UserControl instance? If not, are there any other possible solutions?
Real problem
I'm working on a project where majority of UI views are simple static Word-like documents (e.g some text fields and maybe some images, nothing too fancy). Because most of persons working on this project are not coders we have designed very simple in-house markup language for UI generation. An example of markup of simple view is following:
First name: [Person.FirstName]
Last name: [Person.LastName]
Address: [Person.Address.Street], [Person.Address.City]
Now these templates are loaded at runtime and usercontrols are created based on them. In this case one usercontrol would be created and it would contain simply couple of stack panels and text blocks so that resulting control would look a bit like text document. XAML equivalent would be something like:
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="First name: "/>
<TextBlock Text={Binding Person.FirstName}
</StackPanel>
<StackPanel>
...
</StackPanel>
...
</StackPanel>
Then, I started to implement support for lists but couldn't think of a way how to do that. In theory it is simple and I came up with following syntax (+ XAML equivalent):
[List Customers]
First name: [Person.FirstName]
Last name: [Person.LastName]
Address: [Person.Address.Street], [Person.Address.City]
[EndList]
->
<StackPanel>
<ItemsControl ItemsSource="{Binding Customers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
[Insert code from previous XAML example here]
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
But I can't do that because it seems that I cannot construct DataTemplate directly from UserControl instance. There would be no problems if my UserControls were types, but they are not.
One possible solution is that I could bind ItemsControl.Items directly to list of UserControls (instead of binding to ItemsSource and ItemTemplate) but this is sub-optimal in our case for couple of reasons. I would be willing to try some other solutions first!
Update
For clarification: all I have is plain instance of UserControl class which contains the content I need. E.g.
UserControl control = new UserControl();
var panel = new StackPanel();
panel.Children.Add(...);
panel.Children.Add(...);
control.Content = panel;
// How to use that control as ItemTemplate for ItemsControl?
// It seems that it is not possible directly but I want to
// know what my options are.
I don't have class for it because I'm constructing it at run-time and I don't want to create new type dynamically by emiting IL code because it is way too painful.
Creating a datatemplate from Code behind goes like this:
FrameworkElementFactory factory = new FrameworkElementFactory(MyUserControl.GetType());
DataTemplate dt = new DataTemplate();
dt.VisualTree = factory;
yourItemsControlInstance.ItemTemplate = dt;
A datatemplate is a definition of controls to be built at runtime, that is way this construction with a ElementFactory. You do not want the same instance of the UserControl for every item in your ItemsControl.
Ah I understand your problem now. I don't think there is an easy way (one or two lines of code) to create a datatemplate from a UserControl instance.
But to solve your problem I see two directions:
At the point where an usercontrol is created, create a datatemplate instead and use that. It will be cumbersome, with nested FrameworkElementFactories. I have never done that, and the MSDN documentation says that you may encounter some limitations you cannnot do compared to datatemplates in Xaml. But if it is simple it must be doable. There used to be a codeproject article by Sacha Barber you could use as a guidance (if needed).
You pack the creation of the UserControl in a method called private UserControl createMyUserControl(){}
And do something like this:
ItemsControl itemsControl = new ItemsControl();
foreach (var customer in Customers)
{
var usercontrol = createMyUserControl(...);
usercontrol.DataContext = customer;
itemsControl.Items.Add(usercontrol);
}
Second option is less elegant in my opinion, so I would check out the option 1 first.
WPF: How to create Styles in code/and magical Content (see section at the end for extensive sample of a DataTemplate in Code behind)
I think you can replace UserControl with the ContentControl.
Just set the content of the ContentControl to the desired template and use it as ItemTemplate for the ItemsControl.
i have generated a Linq to Sql class which looks like this.
so i have 3 querys which gets my data.
private IQueryable<Gesellschaft> loadedGesellschaft;
private IQueryable<Anschrift> loadedGesellschaftAnschrift;
private IQueryable<Email> loadedGesellschaftEmail;
private lgDataContext completeGesellschaft;
private void Button_Click(object sender, RoutedEventArgs e)
{
completeGesellschaft = new lgDatacontext();
loadedGesellschaft = completeGesellschaft.Gesellschaft.Where(gid => gid.GID == 2);
loadedGesellschaftAnschrift = completeGesellschaft.Anschrift.Where(FK_GID => FK_GID.FK_GesellschaftId == loadedGesellschaft.First().GID);
loadedGesellschaftEmail = completeGesellschaft.Email.Where(FK_GID => FK_GID.FK_AnschriftId == loadedHauptanschrift.First().idAnschrift);
}
After this i want to put these 3 on my page. The Result is something like this there one Office(loadedGesellschaft) and that has maybe more than one Adress(loadedGesellschaftAnschrift) and has maybe more than one Email(loadedGesellschaftEmail)
so i have on my window some textboxes which contain the fields from loadedGesellschaft and Adresses and Emails are stored in comboboxes.
do i always have to bind the itemsource of one Control e.g.
<ComboBox Name="CBox_GDEmail" />
CBoxGDEmail.Itemsource = loadedGesellschaftEmail;
or is there an possibility to put all three objects together to the datacontext of the window ?
First, create three classes: Gesellschaft, Anschrift, and Email. These classes are view models; they expose any property whose value you want to see in the view. Make Gesellschaft expose an Anschriften property of type IEnumerable<Anschrift>, and Anschrift expose an Emails property of type IEnumerable<Email>. (I'm just sort of guessing at what the plural of Anschrift is; pretty much all of my knowledge of German comes from board games.)
In your XAML, create three DataTemplates, e.g.:
<DataTemplate DataType="{x:Type local:Gesellschaft}">
<WrapPanel>
<Label>Name</Label>
<TextBlock Text="{Binding Gesellschaftname}"/>
...
<ListBox ItemsSource="{Binding Anschriften}"/>
</WrapPanel>
</DataTemplate>
Obviously you'll want to use a saner layout than sticking a bunch of controls in a WrapPanel; this is just a proof of concept. The DataTemplate for Anschrift should similarly have a ListBox whose ItemsSource is bound to Emails.
Once you've done this, all you need in your XAML is to set the DataContext of a ContentPresenter to an instance of Gesellschaft. It will be rendered using the DataTemplate that you've defined for that type. Its ListBox will contain an item for each Anschrift, rendered using that type's template. U.s.w.
Congratulations, you're now using the MVVM pattern just like all the cool kids. There's a lot more to learn about than just this, but this is a good start.
Combine your three objects together in a ViewModel object.