How to make a column with combobox in Avalonia Ui in DataGrid? - avaloniaui

Is it possible in Avalonia Ui DataGrid to implement a column with combobox editing cells, that is, to be able to edit a cell simply by opening this very combobox and the user can simply choose from the options provided ?. I tried to implement a DataGrid like this but ran into a problem. I connected the Nuget Avalonia.DataGrid library, connected the styles in App.axaml as it says. As a result, the combobox appears in the DataGrid, but there is no dropdown in it, although the same combobox works fine outside the DataGrid. What is wrong in this code?
//ViewModel
public List<int> Test3 {get;set;}
//View Window
<DataGrid Items="{Binding Tests}" SelectionMode="Single">
<DataGrid.Columns>
<DataGridTemplateColumn Width="*" Header="Route" IsReadOnly="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="combo" Items="{Binding Path=DataContext.Test3, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>

You forgot to add the property Tests to your ViewModel.
Also it would probably be good to have a conditional SelectedItem, but that's out of context...
I used this code:
//ViewModel
public List<int> Test3 { get; set; } = new List<int>(new int [] {1, 2, 3});
public List<int> Tests { get; set; } = new List<int>(new int[] { 1, 2 });
//XAML
<DataGrid Items="{Binding Tests}" SelectionMode="Single">
<DataGrid.Columns>
<DataGridTemplateColumn Width="*" Header="Route" IsReadOnly="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="combo" Items="{Binding Path=DataContext.Test3, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Which gives me that result:
In case you did all that and your bindings are working, but you still did not get my result, you would need to provide further context (e.g. which Themes you used, on which operating you run the program, etc.).

Another possible solution is to use the Tag Property according to code example below.
<DataGrid x:Name="myDataGrid"
Items="{CompiledBinding myDataGridItems}"
SelectedItem="{CompiledBinding mySelectedItem}"
Tag="{CompiledBinding myComboBoxItems}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Route">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Items="{Binding Tag, ElementName=myDataGrid}"
ItemTemplate="{StaticResource ComboBoxItemTemplate}" Background="Transparent" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>

Related

Dynamic ListView display based on Itemsource {Binding} to ObservableCollection<"this can change and dictates listview style"?>

Summary of the Question: What is the correct way to manipulate the same, single ObservableCollection in a xaml page's viewmodel (binded to the ListView of the page ), at runtime in order to show different sets of data, each variation of data providing its own ListView-Style via a StyleSelector?
Description:
I have a UWP xaml page with a single ListView, I want this listview to display all different possible data sets the user might want to see. e.g: A dataset could be between one to 15 columns of data, all with headers. The ListView's ItemSource will use binding to an ObservableCollection to populate it. The ObservableCollection can be populated manually or with one of many SQL sourced DataTable's.
<ListView x:Name="UserPageListView"
ItemsSource="{Binding MainListData, Mode=TwoWay}"
Grid.Column="1"
Width="auto"
Background="Gray"
ItemContainerStyleSelector="{StaticResource UserPage_StyleSelector}">
</ListView>
I have tried binding the ItemContainerStyleSelector to provide a xaml ListView Style (which is stored in a ResourceDictionary), based on the data type of the ObservableCollection, or at least that was the idea. I don't know whether the ObservableCollection's data type should be generic or a defined class per data set to view. The latter makes sense, since a StyleSelector would need it for logic to provide the relevant Style. I used StyleSelector instead of DataTemplateSelector since I want the Selector to include HeaderTemplate as well as ItemTemplate(headers of columns change with the different data sets):
public class UserPage_StyleSelector:StyleSelector
{
public Style WatchlistStyle { get; set; }
public Style UserDetailStyle { get; set; }
protected override Style SelectStyleCore(object item, DependencyObject container)
{
if (item is WatchlistData)
return WatchlistStyle;
if (item is UserDetailData)
return UserDetailStyle;
return base.SelectStyleCore(item, container);
}
}
Style example in ResourceDictionary:
<Style TargetType="ListView"
x:Key="UserDetail_ListView"
x:Name="UserDetail_ListView">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<Grid Padding="12"
Background="{ThemeResource SystemBaseLowColor}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<TextBlock Text="UserDetails"
Style="{ThemeResource CaptionTextBlockStyle}"/>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding Details}"
FontSize="12"
VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
StyleSelector defined in the xaml page:
<Page.Resources>
<viewModels:UserPage_StyleSelector x:Key="UserPage_StyleSelector"
WatchlistStyle="{StaticResource WatchList_ListView}"
UserDetailStyle="{StaticResource UserDetail_ListView}"/>
</Page.Resources>
The ResourceDictionary is defined in app.xaml.cs. Have I complicated this endeavour far too much by using the wrong approach?
ItemContainerStyle target type is ListViewItem, so you can't make ListView style in ItemContainerStyleSelector.
Derive from your requirement (headers of columns change with the different data sets), you need make GroupStyleSelector and ItemTemplateSelector for different header and columns.
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock
Margin="5"
FontSize="25"
Foreground="Gray"
Text="{Binding Name}"
/>
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
For more detail please refer this document.

How to add items to a combobox with IronPython and XAML

I have the following XAML
<ListView x:Name="listViewTarget" Height="560" Canvas.Left="10" Canvas.Top="101" Width="924" AllowDrop="True">
<ListView.View>
<GridView>
<GridViewColumn Width="350" Header="File or Email">
</GridViewColumn>
<GridViewColumn Width="500" Header="Awaiting Item" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox Width="350" x:Name="cboAI" HorizontalAlignment="Right">
</ComboBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
The ListView has two columns, in the second column I need a combobox of options.
I have a drag drop event associated with the ListView, when you drag a file onto the list the filenames are dynamically added successfully to the list view.
def listView_drag_drop(sender, e):
data = e.Data.GetData(DataFormats.FileDrop, False)
for s in data:
sender.Items.Add(s)
lstViewDropTarget = LogicalTreeHelper.FindLogicalNode(_tikitSender,listViewTarget')
lstViewDropTarget.Drop += listView_drag_drop
What I need to do inside that event is to also populate the combobox, which is currently empty.
I have tried to work out how to get a reference to the combobox to be able to set its ItemsSource property, but after days of effort I have failed.
I am new to IronPython and appreciate your help.
EDIT:
I tried many ways of getting a reference to the combo without success.
Now I am trying to use the Loaded event, but I get an error :
Failed to create a 'Loaded' from the text 'ComboBox_Loaded'
<ListView x:Name="listViewTarget" Height="560" Canvas.Left="10" Canvas.Top="101" Width="924" AllowDrop="True">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="test">
<Label x:Name="textFileName" Content="ABC" />
<TextBlock Text="DEF"/>
<ComboBox
HorizontalAlignment="Left"
Margin="10,10,0,0"
VerticalAlignment="Top"
Width="120"
Loaded="ComboBox_Loaded"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In Iron Python I define the event:
def ComboBox_Loaded(sender, e):
MessageBox.Show("Test")
EDIT 2:
Now I am trying to use the ListView CollectionChanged event
lstViewDropTarget.Items.CollectionChanged += ListView_ItemChanged
And here is the event code
def ListView_ItemChanged(sender, e):
MessageBox.Show(e.NewItems.Count.ToString())
That works messagebox shows '1'
So I can access the new item using e.NewItems[0]
But how do I access the controls?!
I try:
MessageBox.Show(e.NewItems[0].Children.Count.ToString())
and that fails to execute
so I try:
MessageBox.Show(e.NewItems[0].Content.Children.Count.ToString())
that also fails
I am very fed up, I cant believe its so very very hard
After trying many ideas and climbing the Iron Python learning curve I have a working solution. Whilst I have switched to a DataGrid, the method I have used would work with a ListView.
The first breakthrough came when I tried to bind the combobox to array within an iron python class. It just thought I would try and behold it worked!
There were further problems with SelectedItem and needing both a CellTemplate and a CellEditingTemplate, otherwise the selected item doesn't show when the combo loses focus.
I am quite pleased with the result, now I can just iterate through the DataGrid Items to save the results.
<DataGrid x:Name="gridUnknownDocuments" FontFamily="Segoe UI" FontWeight="Normal" FontSize="14" Height="294" Width="904" AutoGenerateColumns="False" SelectionUnit="Cell" Canvas.Left="20" Canvas.Top="39" >
<DataGrid.Columns>
<DataGridTextColumn Header="StepID" Binding="{Binding StepID}" IsReadOnly="True" />
<DataGridTextColumn Header="Document name" Binding="{Binding DocName}" IsReadOnly="True" />
<DataGridTemplateColumn Header="Awaiting Item">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Margin="2" VerticalAlignment="Center" HorizontalAlignment="Left" Text="{Binding SelectedAwaitingItem, Mode=TwoWay}" Width="Auto" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox Height="23"
ItemsSource="{Binding Path= Items}"
SelectedValue="{Binding Path=SelectedAwaitingItem, Mode=TwoWay}" Width="Auto">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGrid.Columns>
IronPython Class:
class UnknownDocuments(object):
def __init__(self, StepID, DocName,Items,FromTypes):
self.StepID = StepID
self.DocName = DocName
self.SelectedAwaitingItem = ""
self.Items = Items
self.FromTypes=FromTypes
self.SelectedFromType = ""
Populating the DataGrid (from SQL Server):
def PopUnknownDocuments():
# Populate unknown docs grid
strSql="EXEC upLean_GetUnknownDocuments #EntityRef='"+_tikitEntity+"', #MatterNo="+_tikitMatter.ToString()
gridUnknownDocs = LogicalTreeHelper.FindLogicalNode(_tikitSender, 'gridUnknownDocuments')
_tikitDbAccess.Open(strSql)
if _tikitDbAccess._dr is not None:
dr = _tikitDbAccess._dr
if dr.HasRows:
item = []
while dr.Read():
if not dr.IsDBNull(0):
item.append(UnknownDocuments(dr.GetString(0), dr.GetString(1),["NA","Awaiting A","Awaiting B"],["Internal","Client","Other Side"]))
gridUnknownDocs.ItemsSource = item
dr.Close()

How to bind to a list of images

I have changed my code to this:
the view:
<phone:Panorama.ItemTemplate>
<DataTemplate>
<ScrollViewer Width="800" HorizontalContentAlignment="Left" Margin="0,50,0,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox x:Name="list_of_images" ItemsSource="{Binding ImagesUrls}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Width="300" Height="300" Source="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ListBox>
<TextBlock Text="{Binding Title}"
Grid.Row="1"
Loaded="TextBlock_Loaded_1"
Margin="50,0,0,0"
FontSize="23"
TextWrapping="Wrap"
Width="360"
HorizontalAlignment="Left"
Foreground="Black"/>
<TextBox Text="{Binding ContactEmail}"
Grid.Row="2"
BorderBrush="Black"
Width="340"
HorizontalAlignment="Left"
BorderThickness="1"
Margin="40,0,0,0"
Foreground="Black" />
<TextBlock Text="{Binding Body}"
Grid.Row="3"
TextWrapping="Wrap"
Foreground="Black"
Margin="50,5,0,0"
Width="360"
HorizontalAlignment="Left"
FontSize="20" />
</Grid>
and I build a new object with different properties, with one of the properties being a list of strings which represents the imageurls, but I cannot get the images to show?
I have attached screenshots, what in my xaml must I change so that I can display the images, cause at the moment it doesn't show any images but it shows all the other details
code for populating collection:
ObservableCollection<ClassifiedAds> klasifiseerd_source = new ObservableCollection<ClassifiedAds>();
ImagesClassifieds new_Classifieds = new ImagesClassifieds();
ObservableCollection<string> gallery_images = new ObservableCollection<string>();
new_Classifieds.Title = klasifiseerd_source[0].Title;
new_Classifieds.ContactEmail = klasifiseerd_source[0].ContactEmail;
new_Classifieds.Body = klasifiseerd_source[0].Body;
foreach (var item in klasifiseerd_source[0].Gallery.Images)
{
var deserialized = JsonConvert.DeserializeObject<GalleryImages>(item.ToString());
gallery_images.Add(deserialized.ImageUrl);
//new_Classifieds.ImageUrls.Add(deserialized.ImageUrl);
}
new_Classifieds.ImageUrls = gallery_images;
// classifiedPanorama.ItemsSource = new_list;
new_Classifieds_list.Add(new_Classifieds);
classifiedPanorama.ItemsSource = new_Classifieds_list;
public class ImagesClassifieds
{
public string Title { get; set; }
public string ContactEmail { get; set; }
public string Body { get; set; }
public ObservableCollection<string> ImageUrls { get; set; }
}
here is the imageurl format, this works (in another par tof my app I simply bind to 1 image in this format and it works perfectly)
Depending on whether you want to just display a list of images or if you also want to be able to select them, you may either choose an ItemsControl or a ListBox. In both case you have to define a DataTemplate that controls how each item is displayed.
<ItemsControl ItemsSource="{Binding Images}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
or
<ListBox ItemsSource="{Binding Images}">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Then you should think about how you define your item class. Just in case you want to be able to dynamically add or remove images from the list and let the UI be automatically updated, you should use an ObservableCollection as container type. As there is a built-in type conversion from string to ImageSource (the type of the Image control's Source property), you may simply use an ObservableCollection<string>.
public class Gal
{
public ObservableCollection<string> Images { get; set; }
}
You may create an instance of that class like so:
var gallery = new Gal();
gallery.Images = new ObservableCollection<string>(
Directory.EnumerateFiles(#"C:\Users\Public\Pictures\Sample Pictures", "*.jpg"));
Now you may directly bind to that instance by simply setting the DataContext of your Window (or wherever the image list should be shown) to this instance:
DataContext = gallery;
Note the binding declaration in the ItemsControl or ListBox above was {Binding Images}. If you really have to have a property Gallery (which I assume is in MainWindow) and bind like {Binding Gallery.Images} you may set the DataContext like this:
DataContext = this;
so I basically created a loaded event on the listbox and added this code
private void list_of_images_Loaded_1(object sender, RoutedEventArgs e)
{
ListBox lst = (ListBox)sender;
lst.ItemsSource = new_Classifieds_list[0].ImageUrls;
}
which binds the itemssource of the listbox to the list of imageurls. since I cannot access the listbox from codebehind due to it being inside the panorama's itemstemplate

Windows StoreApp XAML: Changing ItemTemplate in GridView based on Data type

I am working on developing Windows Store App using C#/XAML. I have experience primarily in iOS and to some extent Android app development, but not yet comfortable with C#/XAML world.
Here is my issue in GridView based page (Based on the nice template VS2012 generates).
I have a gridview and its collection is bound to a data retrieved from network and it works fine.
But I want to change the grid item depending on the data.
For example: I have files and folders that I would like to show using different grid view items.
My Question: How would I use a different DataTemplate for the ItemTemplate depending on the data? For example, for "Folders", I will have only one textblock which is vertically centered and for File, I will have the 2 textblocks and visually different.
Am I going the right path or should I be doing completely different?
The XAML Portion is
<GridView
x:Name="itemGridView"
AutomationProperties.AutomationId="ItemGridView"
AutomationProperties.Name="Grouped Items"
Grid.RowSpan="3"
Padding="116,137,40,46"
ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
ItemTemplate="{StaticResource FileEntriesTemplate}"
ItemClick="ItemView_ItemClick"
IsItemClickEnabled="True"
SelectionMode="None"
IsSwipeEnabled="false">
The Template is
<DataTemplate x:Key="FileEntriesTemplate">
<Grid HorizontalAlignment="Left" Width="400" Height="80" Background="Beige">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Source="{Binding Image}" Stretch="Uniform" Grid.Column="0" Margin="10,0,0,0" AutomationProperties.Name="{Binding Title}"/>
<StackPanel Orientation="Vertical" Grid.Column="1" Background="Transparent">
<TextBlock Text="{Binding Title}" Foreground="Black" Style="{StaticResource LargeTitleTextStyle}" Margin="20,20,10,0"/>
<TextBlock Text="{Binding Subtitle}" Foreground="gray" Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap" Margin="20,10,0,30"/>
</StackPanel>
</Grid>
GridView exposes this through the ItemTemplateSelector property which is a class you can create that inherits from DataTemplateSelector. An example would be that I have a GridView that has Issues and Repositories bound to it and want to use different data templates for each.
My data template selector looks like:
public class IssueSummaryTemplateSelector : DataTemplateSelector
{
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
return item is IssueGroupViewModel ? IssueTemplate : RepositoryTemplate;
}
public DataTemplate RepositoryTemplate
{
get;
set;
}
public DataTemplate IssueTemplate
{
get;
set;
}
}
I then declare the selector as a Resource in xaml assigning the two templates I want to use for Repository and Issues.
<selectors:IssueSummaryTemplateSelector x:Key="IssueSummarySelector"
IssueTemplate="{StaticResource IssueGridZoomedOutTemplate}"
RepositoryTemplate="{StaticResource IssueGridRepositoryZoomedOutTemplate}"/>
You can then use it on your GridView.
<GridView ItemTemplateSelector="{StaticResource IssueSummarySelector}" />

Color Binding Not Working for Data Templates in Win8 App

I'm currently trying to add some sort of a Color Theme feature to a Win8 App I'm working at... I've though of making a binding from a vm, and everything works fine for static UI elements. But, I'm adding some notes (my model) into a DB, and they also appear on the screen into a GridView.
But in the declared DataTemplate for the GridView ItemTemplate, the Color Binding will not work at all...
My template looks like this :
<Grid Grid.Row="3" HorizontalAlignment="Left" Width="200" Height="200">
<Grid.RowDefinitions>
<RowDefinition Height="100"/>
<RowDefinition Height="60"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" Background="Lavender" Opacity="50"/>
<ScrollViewer Grid.Row="0">
<TextBlock Grid.Row="0" Text="{Binding Content}" Foreground="DodgerBlue" />
</ScrollViewer>
<Border Grid.Row="1" Background="DodgerBlue" Opacity="70"/>
<ScrollViewer Grid.Row="1">
<TextBlock Grid.Row="1" Text="{Binding Subject}" Foreground="LightBlue" />
</ScrollViewer>
<Border Grid.Row="2" Background="DodgerBlue" Opacity="70"/>
<TextBlock Grid.Row="2" Text="{Binding Importance}" Foreground="Black" FontSize="{StaticResource ComboBoxArrowThemeFontSize}" />
</Grid>
What I tried was simply instead of Foreground="DodgerBlue" to Foreground="{Binding ColorTheme}" but it had no effect, the SolidColorBrush was not acquired from vm....
Is there any workaround for this?
Many thanks in advance.
Altough I didn't really find out the actual explanation on why the below works (and I hope someone more experience, maybe could explain me), this post, seemed to have worked for me.
Not very sure, but was a matter of DataContext, so instead of trying a binding like this : Foreground={Binding ColorTheme}, I changed to this : Foreground={Binding DataContext.ColorTheme, ElementName=MyGridViewName}".
Color binding (or better: brush binding) should work just fine - in GridView.ItemTemplate scenario and elsewhere.
You haven't given enough info to pinpoint why it doesn't work in your case, but here is a small sample I've just tried out:
GridView to put in your page:
<GridView Width="600" Height="200" ItemsSource="{Binding GridItems}"
x:Name="GridViewName">
<GridView.ItemTemplate>
<DataTemplate>
<TextBlock Width="50" Height="50"
Foreground="{Binding Color}" Text="{Binding Text}" />
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
View model class bound as DataContext for your page:
public class ViewModel
{
public ViewModel()
{
GridItems = new List<GridItem>
{
new GridItem {Text = "First", Color = new SolidColorBrush(Colors.White)},
new GridItem {Text = "Second", Color = new SolidColorBrush(Colors.Yellow)},
new GridItem {Text = "Third", Color = new SolidColorBrush(Colors.Green)},
new GridItem {Text = "Fourth", Color = new SolidColorBrush(Colors.Red)},
};
}
}
GridItem class:
public class GridItem
{
public string Text { get; set; }
public Brush Color { get; set; }
}
The result:
EDIT:
I need to point out that inside data template DataContext is set to the current item of the bound collection (GridItems in my case) not to the page DataContext (ViewModel in my case).
This means that by setting {Binding Color} to a control property you're binding to GridItem.Color not to ViewModel.Color. To make the latter work you first need to access the parent DataContext using the ElementName syntax as you already suggested in your own answer: {Binding DataContext.ColorTheme, ElementName=GridViewName} - this binds to a named element on the page and allows you to access its properties, DataContext being ViewModel in this case.