Change column style depending of source - c#

I am trying to populate a datagrid with one column as a combobox but I need that when the collection binded to the combobox is empty the column becomes a textbox column. I have defined the columns as below:
Binding binding = new Binding("DataContext.Prices");
binding.RelativeSource = new RelativeSource(RelativeSurceMode.FindAncestor, typeof(UserControl),1);
DataGridComboBoxColumn productPrices = new DataGridComboBoxColumn()
{
ElementSyle = new Style
{
TargetType = typeof(ComboBox),
Setters =
{
new Setter
{
Property=ComboBox.ItemsSourceProperty,
Value= binding
}
}
},
EditingElementSyle = new Style
{
TargetType = typeof(ComboBox),
Setters =
{
new Setter
{
Property=ComboBox.ItemsSourceProperty,
Value= binding
}
}
},
DisplayMemberPath = new Binding("Price");
SelectedValuePath = new Bindnt("Price");
};
myDataGrid.Columns.Add(productPrices);
myDataGrid.Columns.Add(new DataGridTextColumn(){ Header="Name", Binding=new Binding("Name")});
And I defined myDataGrid:
<DataGrid Name="myDataGrid" ItemsSource="{Binding Products}" />
In my viewmodel I create a
var products = new List<Product>
{
new Product
{
Name="Prod 1",
Price="12.5"
}
}
var prices = new List<PriceL>
{
new PriceL
{
Price="12.5"
},
new PriceL
{
Price="10"
}
}
ICollectionView Products = CollectionViewSource.GetDefaultView(products);
ICollectionView Prices = CollectionViewSource.GetDefaultView(prices);
I need that when "Prices" is empty the column become in a textbox I am working with MVVM and I tried with elementStyle but I cannot see any event in Combobox that let me verify it's data source. Could any body help me ??

I just found one way to do that
<UserControl.Resources>
<DataGrid ItemsSource={binding} x:Key="DataGrid1">
<DataGrid.Columns>
<DataGridTextColumn Binding="{ID}"/>
<DataGridTextColumn Binding="{Name}"/>
</DataGrid.Columns>
</DataGrid>
<DataGrid ItemsSource={binding} x:Key="DataGrid2">
<DataGrid.Columns>
<DataGridTextColumn Binding="{ID}"/>
<DataGridCheckBoxColumn Binding="{Accepted}"/>
</DataGrid.Columns>
</DataGrid>
</UserControl.Resources>
<Grid>
<ContentControl Content="{StaticResource DataGrid1}" DataContext="{Binding MyTable}" Name="myContent"/}
</Grid>
Y por codigo puede cambiarse el content
myContent.Content = this.FindResource("DataGrid2");

Have you looked into behaviors to trigger the needed visual changes such as the DataStateBehavior? You may want to put in a boolean property in your VM which detects whether the source is valid or not which the behavior can trigger off of.

Related

No data flows from data grid to Observable Collection after hitting a button

I am using Editable Datagrid in WPF, when ever i edit the grid like creating new records and hitting a button say for example create button, Then data from my data grid flows properly to the Observable Collection in code behind .cs file. But when i load data to my grid with an excel upload and clicked the create button, then there is no data available in the Observable Collection in code behind. Its weird. Need some help. Find my code samples below
Data grid in XAML:
<DataGrid x:Name="bulkGroupCreationDataGrid" ItemsSource="{Binding BulkGroupCreationModel}" ContextMenuOpening="TheGrid_ContextMenuOpening"
Height="300" CanUserAddRows="True" CanUserDeleteRows="True" CanUserResizeColumns="True" IsManipulationEnabled="True" AutoGenerateColumns="False" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" SelectionChanged="bulkGroupCreationDataGrid_SelectionChanged_1">
<DataGrid.Columns>
<DataGridTextColumn Width="100" Header="Group" Binding="{Binding Group, ValidatesOnExceptions=True}" />
<DataGridComboBoxColumn Header="Scope" Width="100" x:Name="Scope"
SelectedValueBinding="{Binding Scope, Mode=TwoWay}"
DisplayMemberPath="{Binding Scope}"/>
<DataGridComboBoxColumn Header="Type" Width="100" x:Name="GroupType"
SelectedValueBinding="{Binding GroupType, Mode=TwoWay}"
DisplayMemberPath="{Binding GroupType}"/>
<DataGridTextColumn Width="100" Header="Description" Binding="{Binding Description, ValidatesOnExceptions=True}" />
<DataGridTextColumn Width="70" Header="OU" Binding="{Binding OU, ValidatesOnExceptions=True}" />
<DataGridTextColumn Width="70" Header="Sub-OU" Binding="{Binding SubOU, ValidatesOnExceptions=True}" />
</DataGrid.Columns>
<DataGrid.CommandBindings>
<CommandBinding Command="{x:Static ApplicationCommands.Paste}"
CanExecute="CanPaste" Executed="Paste"/>
<CommandBinding Command="{x:Static ApplicationCommands.New}"
CanExecute="CanAddNew" Executed="AddNew"/>
</DataGrid.CommandBindings>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Command="{x:Static ApplicationCommands.Paste}" Header="Paste"/>
<MenuItem Command="{x:Static ApplicationCommands.New}" Header="New row"/>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
<Button x:Name="Create"
Content="Create"
Click="Create_Click" Foreground="White" FontWeight="Bold" Width="100" Height="25" Background="#5cb85c" RenderTransformOrigin="0.67,0.219" />
Code Behind:
public partial class BulkGroupCreationUserControl : UserControl
{
BulkGroupCreationClass bulkGroupCreationObj = new BulkGroupCreationClass();
private ObservableCollection<BulkGroupCreationModel> _bulkGroupCreationCollection;
public ObservableCollection<BulkGroupCreationModel> BulkGroupCreationCollection
{
get { return _bulkGroupCreationCollection ?? (_bulkGroupCreationCollection = new ObservableCollection<BulkGroupCreationModel>()); }
set { _bulkGroupCreationCollection = value; }
}
//constructor
public BulkGroupCreationUserControl()
{
InitializeComponent();
bulkGroupCreationDataGrid.ItemsSource = BulkGroupCreationCollection;
}
buttonClick Code
private async void Create_Click(object sender, System.Windows.RoutedEventArgs e)
{
//Just checking whether i receive data here, I receive it when data manually typed in grid, but not when uploaded from excel
BulkGroupCreationCollection.Count;
}
Excel to data grid data upload
private void Upload_Click(object sender, RoutedEventArgs e)
{
//Paste here
OpenFileDialog openfile = new OpenFileDialog();
openfile.DefaultExt = ".xlsx";
openfile.Filter = "(.xlsx)|*.xlsx";
//openfile.ShowDialog();
var browsefile = openfile.ShowDialog();
if (browsefile == true)
{
txtFileUpload.Text = openfile.FileName;
Microsoft.Office.Interop.Excel.Application excelApp = new Microsoft.Office.Interop.Excel.Application();
//Static File From Base Path...........
//Microsoft.Office.Interop.Excel.Workbook excelBook = excelApp.Workbooks.Open(AppDomain.CurrentDomain.BaseDirectory + "TestExcel.xlsx", 0, true, 5, "", "", true, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "\t", false, false, 0, true, 1, 0);
//Dynamic File Using Uploader...........
Microsoft.Office.Interop.Excel.Workbook excelBook = excelApp.Workbooks.Open(txtFileUpload.Text.ToString(), 0, true, 5, "", "", true, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "\t", false, false, 0, true, 1, 0);
Microsoft.Office.Interop.Excel.Worksheet excelSheet = (Microsoft.Office.Interop.Excel.Worksheet)excelBook.Worksheets.get_Item(1); ;
Microsoft.Office.Interop.Excel.Range excelRange = excelSheet.UsedRange;
string strCellData = "";
double douCellData;
int rowCnt = 0;
int colCnt = 0;
System.Data.DataTable dt = new System.Data.DataTable();
for (colCnt = 1; colCnt <= excelRange.Columns.Count; colCnt++)
{
string strColumn = "";
strColumn = (string)(excelRange.Cells[1, colCnt] as Microsoft.Office.Interop.Excel.Range).Value2;
dt.Columns.Add(strColumn, typeof(string));
}
for (rowCnt = 2; rowCnt <= excelRange.Rows.Count; rowCnt++)
{
string strData = "";
for (colCnt = 1; colCnt <= excelRange.Columns.Count; colCnt++)
{
try
{
strCellData = (string)(excelRange.Cells[rowCnt, colCnt] as Microsoft.Office.Interop.Excel.Range).Value2;
strData += strCellData + "|";
}
catch (Exception ex)
{
douCellData = (excelRange.Cells[rowCnt, colCnt] as Microsoft.Office.Interop.Excel.Range).Value2;
strData += douCellData.ToString() + "|";
}
}
strData = strData.Remove(strData.Length - 1, 1);
dt.Rows.Add(strData.Split('|'));
}
bulkGroupCreationDataGrid.ItemsSource = dt.DefaultView;
excelBook.Close(true, null, null);
excelApp.Quit();
}
}
}
Look at this line in your Upload_Click method:
bulkGroupCreationDataGrid.ItemsSource = dt.DefaultView;
What are you doing here? You are setting the DataGrid's ItemsSource to dt.DefaultView. In other words, when this line is being executed the DataGrid's ItemsSource is not the ObservableCollection anymore, it's dt.DefaultView. Additionally, the Upload_Click method reads the data from the Excel sheet into the DataTable, not into the ObservableCollection. (As a side note, even if your were to set your ItemsSource binding to be in TwoWay mode in an attempt to pass the new ItemsSource value back to the bound (View)Model property, it would quite likely not work without some changes in the respective (View)Model and possibly elsewhere in your code because a DataTable.DefaultView is not convertible to an ObservableCollection.)
Consequently, when Upload_Click is executed, the ObservableCollection provided by the (View)Model used as DataContext remains untouched (because of the Excel data not going into the ObservableCollection, and the DataGrid not using the ObservableCollection as its ItemsSource anymore).
Instead of swapping the ItemsSource of the DataGrid, i suggest you choose either DataTable or ObservableCollection as the type of your DataGrid's ItemsSource and stick with it. Do not manipulate the DataGrid's ItemsSource property directly. If you need to load (or change) content of the DataGrid programmatically (for example as part of the Upload_Click method), alter the DataTable / ObservableCollection instance that is already bound to the DataGrid and is part of the (View)Model that serves as the DataContext for the DataGrid's ItemsSource binding.
If you really need to change the bound DataTable/ObservableCollection to a different DataTable/ObservableCollection instance, change the value of the BulkGroupCreationModel property in the respective (View)Model that serves as DataContext, instead of manipulating the DataGrid's ItemsSource directly in the code-behind.

Adding button on WPF data grid rows from codebehind c# code

I want to add a button on every row of WPF grid which I am binding from code behind. I am very new to WPF any help is appreciated.
My current code for binding grid is:
DataGridTextColumn c1 = new DataGridTextColumn();
c1.Header = "Dummy column";
c1.Binding = new Binding("DummyColumn");
c1.IsReadOnly = true;
grdDummy.Columns.Add(c1);
foreach (DummyObject deal in AllDummyObjects)
{
ModelToBind dataModel = new ModelToBind()
//do some processing on dataModel
grdDummy.Items.Add(dataModel);
}
You can add another column with button like this:
DataGridTemplateColumn buttonColumn = new DataGridTemplateColumn();
DataTemplate buttonTemplate = new DataTemplate();
FrameworkElementFactory buttonFactory = new FrameworkElementFactory(typeof (Button));
buttonTemplate.VisualTree = buttonFactory;
//add handler or you can add binding to command if you want to handle click
buttonFactory.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(HandleClick));
buttonFactory.SetValue(ContentProperty, "Button");
buttonColumn.CellTemplate = buttonTemplate;
grdDummy.Columns.Add(buttonColumn);
Previously, an example was given of creating a Button using FrameworkElementFactory.
This class is not recommended.
Quote from the documentation:
This class is a deprecated way to programmatically create templates, which are subclasses of FrameworkTemplate such as ControlTemplate or DataTemplate; not all of the template functionality is available when you create a template using this class. The recommended way to programmatically create a template is to load XAML from a string or a memory stream using the Load method of the XamlReader class.
In this regard, I show the implementation code using the XamlReader.
The code is shown from the assumption that the ModelToBind class has a command-property named ButtonCommand and property ButtonTitle.
And this class is located in the local namespace "Febr20y" in the assembly of the same name.
DataGridTextColumn c1 = new DataGridTextColumn
{
Header = "Dummy column",
Binding = new Binding("DummyColumn"),
IsReadOnly = true
};
DataTemplate template = (DataTemplate)XamlReader.Parse(
#"<DataTemplate
xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Febr20y;assembly=Febr20y'
DataType ='{x:Type local:ModelToBind}'>
<Button Content='{Binding ButtonTitle, Mode=OneWay}'
Command='{Binding ButtonCommand, Mode=OneWay}'/>
</DataTemplate>");
DataGridTemplateColumn c2 = new DataGridTemplateColumn()
{
Header = "Buttons",
IsReadOnly = true,
CellTemplate=template
};
grdDummy.Columns.Add(c1);
grdDummy.Columns.Add(c2);
var listSource = AllDummyObjects
.Select(deal => new ModelToBind() { ButtonTitle = deal.Title.ToString()})
.ToList();
grdDummy.ItemsSource = listSource;
This is equivalent to this XAML code:
<DataGrid x:Name="grdDummy" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding DummyColumn}"
IsReadOnly="True"
Header="Dummy column"/>
<DataGridTemplateColumn Header="Buttons"
IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type local:ModelToBind}">
<Button Content="{Binding ButtonTitle, Mode=OneWay}"
Command="{Binding ButtonCommand, Mode=OneWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>

WPF Programmatically set the DataBinding for a DataGridTextColumn's DisplayIndex

I currently generate my DataGridTextColumns for my DataGrid programmatically. I set their databinding with textColumn.Binding = new Binding(BindingName);. This sets the databinding for the content of that column to a property within the array that the DataGrid's ItemsSource is bound to.
However I also want to bind the DisplayIndex property of the column to properties in my ViewModel.
I'm having a hard time figuring out how to go about setting up the databinding for the DataGridTextColumn's by itself. How do I go about this?
DataGrid:
<DataGrid x:Name="QuickStatsDataGrid"
AutoGenerateColumns="False"
ItemsSource="{Binding QuickStatistics.UserQuickStats}"
Grid.Row="1"
Grid.RowSpan="2"
DockPanel.Dock="Top">
<DataGrid.Columns>
</DataGrid.Columns>
</DataGrid>
The Method that creates the column:
private void AddColumn()
{
DataGridTextColumn textColumn = new DataGridTextColumn();
textColumn.Header = Name;
textColumn.Binding = new Binding(BindingName);
textColumn.MinWidth = 20;
TextColumn = textColumn;
CreatedColumn = true;
DataGridOwner.Columns.Add(textColumn); // Datagrid owner is the DataGrid
}
Edit: The appropriate properties are in my ViewModel. It doesn't seem necessary to post the ViewModel, lets assume there is a property named ColumnDisplayIndex in MyViewModel.
Edit: Addition to my method. The properties in the ViewModel don't seem to update when I change the index programmaticly or by dragging the column over to a new location.
private void AddColumn()
{
DataGridTextColumn textColumn = new DataGridTextColumn();
BindingOperations.SetBinding(textColumn, DataGridColumn.DisplayIndexProperty, new Binding(DisplayIndexBindingName) { Source = viewModel });
textColumn.DisplayIndex = Index;
textColumn.Header = Name;
textColumn.Binding = new Binding(BindingName);
textColumn.MinWidth = 20;
TextColumn = textColumn;
CreatedColumn = true;
DataGridOwner.Columns.Add(textColumn); // Datagrid owner is the DataGrid
}

DataContext to DataGrid in user control

I have a main window with tabcontrol.
Adding a new tabitem with a user control content.
In the xaml:
<Grid><DataGrid DataContext="{Binding Path=Patients, Mode=TwoWay}">
<DataGrid.Columns>
<DataGridTextColumn Header="Id"
Width="Auto"
Binding="{Binding Id}"/>
In code behind:
Opening context,
var query = from pp in context.Patients select pp;
var Patients = query.ToList();
TabItem patientsView = new TabItem(); // adding new tabitem
StackPanel header = new StackPanel
header.Children.Add(new TextBlock {Text="Patients"});
patientsView.Header = header;
patientsView.Content = new ViewDataPatients{DataContext = Patients};
It refuses to populate the bind data to the grid.
Any idea where I am doing this wrong?
can't understand why you need such approach, but still. You've set DataContext of your UserControl to Patiense, so DataGrid alreay have this data context, enought the following:
<DataGrid DataContext="{Binding}">
But this is redundant in your case. To populate DataGrid with data just set ItemsSource:
<DataGrid ItemsSource="{Binding}">
Hope this helps.
King regards, Nazar

nested xaml elements - rewrite it in code

I need to create a DataGridColumn from code.
The XAML equivalent would be:
<data:DataGridTemplateColumn Header="Name" Width="100">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" TextTrimming="WordEllipsis"></TextBlock>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
I've started like that:
DataGridTemplateColumn column = new DataGridTemplateColumn
{
Header = "Name",
Width = 100,
};
TextBlock inside = new TextBlock {TextTrimming = TextTrimming.CharacterEllipsis};
But I don't know how to 'merge' such puzzles. There are nested elements in XAML, how to achieve this from code?
A good way to do this is to pack the entire XAML snippet into a string and call XamlReader.Load() or XamlReader.Parse() on it. The bonus feature of this approach is that it'll work in Silverlight as well (with some fiddling), where you can't build DataTemplates in code.
Almost there, change your code to this and it should work:
DataGridTemplateColumn column = new DataGridTemplateColumn
{
Header = "Name",
Width = 100,
};
FrameworkElementFactory ftb = new FrameworkElementFactory(typeof(TextBlock));
Binding b = new Binding("Name");
ftb.SetValue(TextBlock.Text, b);
ftb.SetValue(TextBlock.TextTrimming, TextTrimming.CharacterEllipsis);
DataTemplate ct = new DataTemplate();
ct.VisualTree = ftb;
column.CellTemplate = ct;
Another method besides the above is to define your datatemplate in XAML within your resources then dynamically load it in the code:
XAML:
<Window.Resources>
<DataTemplate x:Key="myCellTemplate">
<TextBlock Text="{Binding Name}" TextTrimming="WordEllipsis" />
</DataTemplate>
</Window.Resources>
Code:
DataGridTemplateColumn column = new DataGridTemplateColumn
{
Header = "Name",
Width = 100,
};
column.CellTemplate = this.FindResource("myCellTemplate") as DataTemplate;

Categories

Resources