How Can I Bind an ObservableCollection or IEnumerable property in my ViewModel to a Table Defined by an Inline FlowDocument? I'm not sure whether it is the correct approach. I want to print an Invoice from my view model(I thought FlowDocument might be an easy method to print).
Suppose I have a Property Items in my ViewModel
private ObservableCollection<ItemViewModel> _Items;
public ObservableCollection<ItemViewModel> Items
{
get
{
return _Items;
}
set
{
_Items = value;
RaisePropertyChanged("Items");
}
}
In Xaml: MainWindow.xaml
....
<FlowDocument>
<Paragraph>
<Run Text="Sample text"/>
</Paragraph>
...
<Table <!-- I cannot find ItemsSource Or anything similar --> >
<!-- How can i dynamically generate rows and add them here from Items in ViewModel
Something like Datagrid's DataGridTextColumn ?
-->
</Table>
</FlowDocument>
FlowDocument is not a seperate file or an xps. It is embedded inside my MainWindow.xaml file
I'm a beginner and not sure how to do it.
Any help appreciated.
thank you
Had to get back to my history, yeah we experienced this before we found this article which says "there is no support for data binding in flow documents". So we decided to use itemscontrol instead and didn't use FlowDocument at all.
But here is a sample workaround,
MainWindow.xaml
<Window x:Class="WpfApp3.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:vm="clr-namespace:WpfApp3.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<FlowDocument>
<Paragraph>
<Run Text="Sample text"/>
</Paragraph>
<Table x:Name="Table1">
</Table>
</FlowDocument>
</Window>
MainWindow.xaml.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using WpfApp3.ViewModel;
namespace WpfApp3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainViewModel VM => (MainViewModel) DataContext;
public MainWindow()
{
InitializeComponent();
BuildTable();
}
private void BuildTable()
{
foreach (ItemViewModel item in VM.Items)
{
TableRow nameRow = BuildRow(item.Name);
TableRowGroup group = new TableRowGroup();
group.Rows.Add(nameRow);
Table1.RowGroups.Add(group);
}
}
private static TableRow BuildRow(string content)
{
TextBlock textBlock = new TextBlock
{
Text = content
};
Block block1 = new BlockUIContainer(textBlock);
TableCell cell = new TableCell();
cell.Blocks.Add(block1);
TableRow row = new TableRow();
row.Cells.Add(cell);
return row;
}
}
}
ViewModel->MainViewModel.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfApp3.ViewModel
{
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
PopulateData();
}
private ObservableCollection<ItemViewModel> _Items;
public ObservableCollection<ItemViewModel> Items
{
get => _Items;
set
{
_Items = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#region Stub
private void PopulateData()
{
Items = new ObservableCollection<ItemViewModel>
{
new ItemViewModel
{
Name = "Item 1",
},
new ItemViewModel
{
Name = "Item 2",
}
};
}
#endregion
}
}
ViewModel->ItemViewModel.cs
namespace WpfApp3.ViewModel
{
public class ItemViewModel
{
public string Name { get; set; }
}
}
Output:
Related
I have a WPF window that contains multiple user controls, some of which are invisible (Visibility = Hidden). One of these controls has a ComboBox that has an ItemsSource binding, and I want to preset its selected item while the window/control is loading.
However, it seems like the binding is not applied until the combobox is visible. When I go to set the SelectedItem property and I hit a breakpoint in the debugger, I notice that ItemsSource is null at that moment. Is there a way to force WPF to apply the data binding and populate the combobox while it stays invisible?
Reproducible Example:
MainWindow.xaml
<Window x:Class="HiddenComboBoxBinding.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:HiddenComboBoxBinding"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Border x:Name="comboboxParent" Visibility="Collapsed">
<ComboBox x:Name="cmbSearchType" SelectedIndex="0" ItemsSource="{Binding SearchTypeOptions}" DisplayMemberPath="Name" SelectionChanged="cmbSearchType_SelectionChanged" />
</Border>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace HiddenComboBoxBinding
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ViewModel viewModel { get; set; } = new ViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = viewModel;
// Add some or all of our search types - in the real code, there's some business logic here
foreach (var searchType in SearchType.AllSearchTypes)
{
viewModel.SearchTypeOptions.Add(searchType);
}
// Pre-select the last option, which should be "Bar"
cmbSearchType.SelectedItem = SearchType.AllSearchTypes.Last();
}
private void cmbSearchType_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
}
}
ViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HiddenComboBoxBinding
{
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<SearchType> SearchTypeOptions { get; set; } = new ObservableCollection<SearchType>();
#region INotifyPropertyChanged Members
private void NotifyPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class SearchType
{
// Source list of Search Types
private static List<SearchType> _AllSearchTypes;
public static List<SearchType> AllSearchTypes
{
get
{
if(_AllSearchTypes == null)
{
_AllSearchTypes = new List<SearchType>();
_AllSearchTypes.Add(new SearchType() { Name = "Foo" });
_AllSearchTypes.Add(new SearchType() { Name = "Bar" });
}
return _AllSearchTypes;
}
}
// Instance properties - for the purposes of a minimal, complete, verifiable example, just one property
public string Name { get; set; }
}
}
I was able to figure out the issue. Setting the SelectedItem actually did work (even though ItemsSource was null at that time) but in the XAML, the ComboBox had SelectedIndex="0" and it was taking precedence over the SelectedItem being set in the code-behind.
I'm trying to reorder listView items with a mouse and I'm getting this mysterious Parameter is not valid ArgumentException either when I'm starting to drag or when I'm dropping item. There are no other details, no stack trace. Crashes entire app.
It works fine when I'm reordering ObservableCollection<string> but keeps crashing on ObservableCollection<MyControl>
MyControl is just a simple UserControl with a TextBlock inside.
I tried CollectionViewSource approach and it's the same.
Any ideas?
MainPage.xaml
<Page
x:Class="App3.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App3"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<ListView ItemsSource="{Binding Items}" CanReorderItems="True" AllowDrop="True"/>
</Page>
MainPage.xaml.cs
using Windows.UI.Xaml.Controls;
namespace App3
{
public sealed partial class MainPage : Page
{
private MainPageViewModel mainPageViewModel;
public MainPage()
{
this.InitializeComponent();
mainPageViewModel = new MainPageViewModel();
DataContext = mainPageViewModel;
// THIS IS NOT WORKING
MyControl myControl1 = new MyControl("Hello1");
MyControl myControl2 = new MyControl("Hello2");
MyControl myControl3 = new MyControl("Hello3");
mainPageViewModel.Items.Add(myControl1);
mainPageViewModel.Items.Add(myControl2);
mainPageViewModel.Items.Add(myControl3);
// THIS IS WORKING
//string s1 = "h1";
//string s2 = "h2";
//string s3 = "h3";
//mainPageViewModel.Items.Add(s1);
//mainPageViewModel.Items.Add(s2);
//mainPageViewModel.Items.Add(s3);
}
}
}
MainPageViewModel.cs
using System.Collections.ObjectModel;
namespace App3
{
public class MainPageViewModel : BaseModel
{
// THIS IS NOT WORKING
private ObservableCollection<MyControl> items = new ObservableCollection<MyControl>();
public ObservableCollection<MyControl> Items
{
get { return items; }
set
{
items = value;
OnPropertyChanged();
}
}
// THIS IS WORKING
//private ObservableCollection<string> items = new ObservableCollection<string>();
//public ObservableCollection<string> Items
//{
// get { return items; }
// set
// {
// items = value;
// OnPropertyChanged();
// }
//}
}
}
MyControl.xaml
<UserControl
x:Class="App3.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App3"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<TextBlock Name="tb"/>
</UserControl>
MyControl.cs
using Windows.UI.Xaml.Controls;
namespace App3
{
public sealed partial class MyControl : UserControl
{
public MyControl(string sName)
{
this.InitializeComponent();
tb.Text = sName;
}
}
}
BaseModel.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace App3
{
public class BaseModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName]string name = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
EDIT
Looks like there's also no problem with reordering simple objects, so I guess there is something wrong with reordering UserControl.
namespace App3
{
public class SampleClass
{
private string sName;
public SampleClass(string sName)
{
this.sName = sName;
}
public override string ToString()
{
return sName;
}
}
}
EDIT 2
Reordering objects that derive from CheckBox crashes exactly like MyControl.
using Windows.UI.Xaml.Controls;
namespace App3
{
public class MyCheckBox : CheckBox
{
public MyCheckBox(string sName)
{
Content = sName;
}
}
}
Same for standard checkBoxes
CheckBox checkBox1 = new CheckBox() { Content = "hello1" };
CheckBox checkBox2 = new CheckBox() { Content = "hello2" };
CheckBox checkBox3 = new CheckBox() { Content = "hello3" };
mainPageViewModel.Items.Add(checkBox1);
mainPageViewModel.Items.Add(checkBox2);
mainPageViewModel.Items.Add(checkBox3);
EDIT 3
Seems like the only solution is to use simple class for holding data
using Windows.UI.Xaml.Controls;
namespace App3
{
public class MyCheckBox : BaseModel
{
private string sName;
public string Name
{
get { return sName; }
set
{
sName = value;
OnPropertyChanged();
}
}
public MyCheckBox(string sName)
{
Name = sName;
}
}
}
and DataTemplate for ListViewItem to display it
<Page
x:Class="App3.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App3"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<ListView ItemsSource="{Binding Items}" CanReorderItems="True" AllowDrop="True">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Page>
instead of adding UserControl directly to ListView items.
It looks like the reordering of Visuals is not supported entirely. When reorganizing items in a listview, a lot happens underneath, as for example dynamically creating and destroying containers that hold your content, etc.
In order to display the resorted element, some items need to be appended elsewhere in the visual tree.
The drag/drop tries to attach an already attached Visual (your UserControl) which might cause the exception - a Visual can not be attached to multiple parents.
Your approach of using POCOs / ViewModels with Datatemplates should be sufficient to get the work done, as the de- and attach semantics are considered by ListView already.
There should be more exception details, though.
This is my scenario: My DataTemplate of a ListView contains a TextBox and some buttons, one of the buttons is used to select and highlight all of the text in the TextBox. I can find many solutions for select and highlight text in TextBox from code behind, but none of them define the TextBox and the Button in DataTemplate. Anyone can help?
Thanks
You can do something like this below :
XAML :
<Window x:Class="SOWPF.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>
<ListView Width="200" Height="300" ItemsSource="{Binding FriendList}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Width="100" Margin="2" Text="{Binding Name}"></TextBox>
<Button Content="Select" Click="Button_Click"></Button>
<Button Content="Delete"></Button>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Code Behind :
using System.Windows;
using System.Windows.Controls;
namespace SOWPF
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var friendViewModel = new FriendViewModel();
friendViewModel.AddFriends();
this.DataContext = friendViewModel;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var parent = (StackPanel)((sender as Button).Parent);
var children = parent.Children;
foreach(var child in children)
{
if (child.GetType().Equals(typeof(TextBox)))
{
var tb = child as TextBox;
tb.Focus();
tb.SelectAll();
break;
}
}
}
}
}
ViewModel :
using System.Collections.ObjectModel;
namespace SOWPF
{
public class FriendViewModel
{
public ObservableCollection<Friend> FriendList
{ get; set; }
public void AddFriends()
{
FriendList = new ObservableCollection<Friend>();
FriendList.Add(new Friend() { Name = "Arpan" });
FriendList.Add(new Friend() { Name = "Nrup" });
FriendList.Add(new Friend() { Name = "Deba" });
}
}
public class Friend
{
public string Name { get; set; }
}
}
Probably would be a good using an Attached property set on the button, and in the attached code using a code something like written by cvraman.
Using this way you absolutely avoid the code behind structure, and better way to using mvvm
I am new in WPF. I used to work in Winforms.
In Winforms I had the DataGridView that allows me to change, when I want a cell value.
Simply using:
dataGridView[columnIndex, rowIndex].Value = "New Value";
It works.
How can I accomplish this using DataGrid from WPF?
I was looking thorught stack over flow and could figure out an easy way to do this.
Thank you
Ok the simplest way to handle DataGrid is by binding to an ItemSource.
The example below shows how to bind your list and how changes upadte the DataGrid.
public partial class MainWindow : Window
{
private ObservableCollection<ConnectionItem> _connectionitems = new ObservableCollection<ConnectionItem>();
public MainWindow()
{
InitializeComponent();
ConnectionItems.Add(new ConnectionItem { Name = "Item1", Ping = "150ms" });
ConnectionItems.Add(new ConnectionItem { Name = "Item2", Ping = "122ms" });
}
public ObservableCollection<ConnectionItem> ConnectionItems
{
get { return _connectionitems; }
set { _connectionitems = value; }
}
private void button1_Click(object sender, RoutedEventArgs e)
{
// to change a value jus find the item you want in the list and change it
// because your ConnectionItem class implements INotifyPropertyChanged
// ite will automaticly update the dataGrid
// Example
ConnectionItems[0].Ping = "new ping :)";
}
}
public class ConnectionItem : INotifyPropertyChanged
{
private string _name;
private string _ping;
public string Name
{
get { return _name; }
set { _name = value; NotifyPropertyChanged("Name"); }
}
public string Ping
{
get { return _ping; }
set { _ping = value; NotifyPropertyChanged("Ping"); }
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notifies the property changed.
/// </summary>
/// <param name="property">The info.</param>
public void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Xaml:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication4"
xmlns:properties="clr-namespace:WpfApplication4.Properties"
Title="MainWindow" Height="300" Width="400" Name="UI" >
<Grid>
<DataGrid Name="dataGridView" ItemsSource="{Binding ElementName=UI,Path=ConnectionItems}" Margin="0,0,0,40" />
<Button Content="Change" Height="23" HorizontalAlignment="Left" Margin="5,0,0,12" Name="button1" VerticalAlignment="Bottom" Width="75" Click="button1_Click" />
</Grid>
</Window>
i added a button to show how the data updates when you change something in your list, The class ConnectionItem is where you will store all your info for the datagrid.
Hope this helps
I want to bind a datagrid view in a user control that is docking to a main WPF form. However everytime I try to bind the data it must pre exist and won't update. Is there a way to perform this in the XAML directly to know when an event is triggered to update the datagridview rather than do it in the code behind?
Partial code of XAML:
xmlns:c="clr-namespace:TestWPFMain"
<UserControl.Resources>
<c:GridData x:Key="dataforGrid"/>
</UserControl.Resources>
<Grid>
<DataGrid Grid.Row="2" x:Name="datagridMain" ItemsSource="{Binding Source={StaticResource dataforGrid}, Path=Results, Mode=TwoWay}" />
</Grid>
Code Behind for UserControl above:
public GridControl()
{
InitializeComponent();
GridData gd = new GridData();
gd.UpdateResults();
//datagridMain.ItemsSource = gd.Results;
-- This code above will work if I uncomment but I want it to be bound
directly and was curious as I thought the mode of 'two way' would
do this. I am not certain and most examples assume property is already
set up and not being created and updated.
}
Code Class for GridData:
class PersonName
{
public string Name { get; set; }
}
class GridData
{
public ObservableCollection<PersonName> Results { get; set; }
public void UpdateResults()
{
using (EntityDataModel be = new EntityDataModel())
{
var list = be.tePersons.Select(x => new PersonName { Name = x.FirstName });
Results = new ObservableCollection<PersonName>(list);
}
}
}
To use binding like this, you need to:
Set the DataContext correctly on the DataGrid (or on one of its parent)
Implement INotifyPropertyChanged on your model class, and raise PropertyChanged in the property setter.
1)
Set your window's DataContext to the GridData object:
public GridControl()
{
InitializeComponent();
GridData gd = new GridData();
gd.UpdateResults();
this.DataContext = gd;
}
2)
Implement INotifyPropertyChanged. This ensures that your view gets notified when the Results property gets updated:
public class GridData : INotifyPropertyChanged
{
private ObservableCollection<PersonName> _results;
public ObservableCollection<PersonName> Results
{
get { return _results; }
set
{
_results = value;
RaisePropertyChanged("GridData");
}
}
// ...
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
#endregion
}
Then you can simply bind to the path relative to the data context.
<DataGrid ItemsSource="{Binding Results}" />
Note that you don't need two-way binding in this case -- that's for propagating changes from the View back to your model (ie, most useful for when there's a UI control like a text box or checkbox).
Here is an example (I used Window, but it will work the same for UserControl)
Xaml:
<Window x:Class="WpfApplication4.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" Name="UI">
<Grid>
<DataGrid Grid.Row="2" x:Name="datagridMain" ItemsSource="{Binding ElementName=UI, Path=GridData.Results, Mode=TwoWay}" />
</Grid>
</Window>
or id you want the whole DataContext:
<Window x:Class="WpfApplication4.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" Name="UI">
<Grid>
<DataGrid Grid.Row="2" x:Name="datagridMain" DataContext="{Binding ElementName=UI, Path=GridData}" ItemsSource="{Binding Results}" />
</Grid>
</Window>
Code:
You will have to implement INotifyPropertyChanged so the xaml knows GridData has changed
The ObservableCollection inside GridData as this function built-in so anytime you add remove items they will update the DataGrid control
public partial class MainWindow : Window , INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
GridData = new GridData { Results = new ObservableCollection<PersonName>() };
GridData.Results.Add(new PersonName { Name = "Test1" });
GridData.Results.Add(new PersonName { Name = "Test2" });
}
private GridData _gridData;
public GridData GridData
{
get { return _gridData; }
set { _gridData = value; NotifyPropertyChanged("GridData"); }
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notifies the property changed.
/// </summary>
/// <param name="info">The info.</param>
public void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Classes:
I made a small change to the update method, so it just clears and updates the existing ObservableCollection, otherwise you would have to Implement INotifypropertyChanged to this class if you assign a new ObservableCollection.
public class PersonName
{
public string Name { get; set; }
}
public class GridData
{
public GridData()
{
Results = new ObservableCollection<PersonName>()
}
public ObservableCollection<PersonName> Results { get; set; }
public void UpdateResults()
{
using (EntityDataModel be = new EntityDataModel())
{
// Just update existing list, instead of creating a new one.
Results.Clear();
be.tePersons.Select(x => new PersonName { Name = x.FirstName }).ToList().ForEach(item => Results.Add(item);
}
}
}