I need a part of my Page unmovable, so I thought using a Frame, something like:
<StackPanel>
<Label Style="{StaticResource OneThirdColumnLabel}">Administrar papéis: Alterar papéis</Label>
<ItemsControl x:Name="PapeisIc">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Frame x:Name="_papeisAddFrame" Source="PapeisAdd.xaml" Height="50"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
Then, the Frame would contain a Page with:
<ScrollViewer VerticalScrollBarVisibility="Visible">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
<Label Style="{StaticResource LaterLabel}">Papel:</Label>
<TextBox Text="{Binding Papel}" Style="{StaticResource StdTextBox}" Width="100" />
<Label Style="{StaticResource LaterLabel}">Descrição:</Label>
<TextBox Text="{Binding Descricao}" Style="{StaticResource StdTextBox}" Width="400" />
<Button x:Name="DeleteFieldBtn" Style="{StaticResource deleteFieldButtom}" ToolTip="Eliminar este papel"/>
</StackPanel>
</StackPanel>
</ScrollViewer>
In the code-behind, the line:
PapeisIc.ItemsSource = _papeis;
Binds a object that's an ObservableCollection of 3 items and I need it to display the 3 elements in the example.
What I get is:
So that, somehow it knows that there are 3 elements, but it doesn't bind it right...
I already tried to pass to the Page the data as a parameter, but it didn't work either.
How can I bind those values?
Just to make it clear, I'm trying to bind to an ObservableCollection of:
public class Role
{
public int Id { get; set; }
public string Papel { get; set; }
public string Descricao { get; set; }
}
The Frame control does not pass the DataContext down to the Page. The easiest way to solve this issue is to remove the Frame and move its content directly to the DataTemplate.
If you really need the Frame, you have to listen to the LoadCompleted and DataContextChanged events and propagate the DataContext manually to the content of the Frame. I show you how to do it with a TriggerAction using the Microsoft.Xaml.Behaviors.Wpf NuGet package.
public class UpdateContentDataContextAction : TriggerAction<Frame>
{
protected override void Invoke(object parameter)
{
if (AssociatedObject.Content is FrameworkElement frameworkElement)
frameworkElement.DataContext = AssociatedObject.DataContext;
}
}
<DataTemplate>
<Frame x:Name="_papeisAddFrame" Source="PapeisAdd.xaml" Height="50">
<b:Interaction.Triggers>
<b:EventTrigger EventName="LoadCompleted">
<local:UpdateContentDataContextAction/>
</b:EventTrigger>
<b:EventTrigger EventName="DataContextChanged">
<local:UpdateContentDataContextAction/>
</b:EventTrigger>
</b:Interaction.Triggers>
</Frame>
</DataTemplate>
If you want to do it in the code-behind, subscribe the events on the control and use the code of the trigger action.
<Frame x:Name="_papeisAddFrame" Source="PapeisAdd.xaml" Height="50" LoadCompleted="OnLoadCompleted" DataContextChanged="OnDataContextChanged">
private void OnLoadCompleted(object sender, NavigationEventArgs e)
{
PropagateDataContext((Frame)sender);
}
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
PropagateDataContext((Frame)sender);
}
private void PropagateDataContext(Frame frame)
{
if (frame.Content is FrameworkElement frameworkElement)
frameworkElement.DataContext = frame.DataContext;
}
Related
I have a Listbox which is bound to a DataTemplate that has another Listbox on it.
On DataTemplate there is a button that I want to use for adding items to DataTemplate ListBox, but I can't find a solution to do this.
Here is my listbox:
<Button Width="200" Content="Add Question" x:Name="btnAddQuestion" Click="btnAddQuestion_Click"/>
<StackPanel Orientation="Horizontal">
<ListBox Margin="5" x:Name="lvQuestions" ItemTemplate="{StaticResource TemplateQuestionTitle}">
</ListBox>
</StackPanel>
And this is DataTemplate:
<DataTemplate x:Key="TemplateQuestionTitle">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBox materialDesign:HintAssist.Hint="Enter question" MinWidth="200" Style="{StaticResource MaterialDesignFloatingHintTextBox}"/>
<Button Content="+" Command="{Binding Source={x:Reference ThisPage},Path=DataContext.Command}" />
</StackPanel>
<ListBox ItemsSource="{Binding MyItems}" MinHeight="50">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox>
</TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
This is code behind on my page:
public partial class UIBuilder:Window
{
private CommandVm _commandVm;
public UIBuilder()
{
InitializeComponent();
_commandVm = new CommandVm();
DataContext = _commandVm;
}
private void btnAddQuestion_Click(object sender, RoutedEventArgs e)
{
lvQuestions.Items.Add(null);
}
}
I have implemented this code on my ViewModel in order to add items to datatemplate ListBox:
public class CommandVm
{
public ObservableCollection<TextBox> MyItems { get; set; }
public CommandVm()
{
MyItems = new ObservableCollection<TextBox>();
Command = new RelayCommand<TextBox>(Execute);
}
private void Execute(TextBox textBox)
{
MyItems .Add(textBox);
}
public ICommand Command { get; set; }
}
I use to catch the Execute() function on button "+" click command, but my code doesn't add any ListBox item.
MyItems is a property of the parent view model which means that you should bind to it like this:
<ListBox ItemsSource="{Binding DataContext.MyItems,
RelativeSource={RelativeSource AncestorType=Window}}" MinHeight="50">
This also means that you are using one single collection of items for all questions. Besides this obvious design flaw, a view model should not contain any TextBox elements. This basically breaks what the MVVM pattern is all about.
What you should do to make this example MVVM compliant is to create a Question class that has a collection of items, e.g.:
public class Question
{
public Question()
{
AddAnswerCommand = new RelayCommand<object>(Execute);
}
private void Execute(object obj)
{
Items.Add(new Answer());
}
public ObservableCollection<Answer> Items { get; }
= new ObservableCollection<Answer>();
public ICommand AddAnswerCommand { get; }
}
public class Answer { }
The window's view model should then have a collection of questions:
public class CommandVm
{
public CommandVm()
{
AddQuestionCommand = new RelayCommand<object>(Execute);
}
public ObservableCollection<Question> Questions { get; }
= new ObservableCollection<Question>();
public ICommand AddQuestionCommand { get; }
private void Execute(object obj)
{
Questions.Add(new Question());
}
}
The view and the bindings could then be defined like this:
<Window.Resources>
<DataTemplate x:Key="TemplateQuestionTitle">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBox MinWidth="200" />
<Button Content="+" Command="{Binding AddAnswerCommand}" />
</StackPanel>
<ListBox ItemsSource="{Binding Items}" MinHeight="50">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Width="200" Content="Add Question" Command="{Binding AddQuestionCommand}"/>
<ListBox Margin="5"
ItemsSource="{Binding Questions}"
ItemTemplate="{StaticResource TemplateQuestionTitle}" />
</StackPanel>
This setup lets you add individual elements to each separate question.
I have a program with several features. Each feature has a ViewModel, a MainView and OptionsView. MainView is displaying what the feature does, while the OptionsView is a View allowing the user to change the settings of the feature. OptionsView is stored in the MainView.
I want to centralise the Options into a MainOptions view under a ListView. I can get a List or ObservableCollection of the MainViews for each feature, however i have trouble getting the OptionsView of each feature.
I can display OptionsView in the MainView just fine, however when i try to use DataTemplate in a ListView in order to bind the list of MainViews to it and get the OptionViews it doesn't display anything, but it doesn't crash or output errors.
XAML of OptionsMain :
<Grid>
<ListBox Name="OptionsList" ItemsSource="{Binding Path=FeaturesPages, Mode=TwoWay}" Background="Transparent">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Frame Name="GroupItemFrame" Content="{Binding Path=OptionsPage, Mode=TwoWay}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Code behind :
public partial class OptionsMain : Page, INotifyPropertyChanged
{
private ObservableCollection<Page> _optionsPages = new ObservableCollection<Page>();
public ObservableCollection<Page> OptionsPages
{
get { return _optionsPages; }
set
{
_optionsPages = value;
NotifyPropertyChanged("OptionsPages");
}
}
public OptionsMain(ObservableCollection<Page> pages)
{
foreach (Page p in pages)
{
OptionsPages.Add(p);
}
Console.WriteLine("List size : {0}", OptionsPages.Count);
InitializeComponent();
DataContext = this;
Any insight on what might be the problem? Is there a better way of doing this?
This is kind of a tricky issue, and I don't see many ways to accomplish this, but I do have a way. The problem is that you need to call Frame.Navigate to the page for each and every frame. You can't just assign the content unless it's actually a control with content. So I am going to suggest a work around/hack of forcing the frame to navigate as it's being loaded.
Mainpage.xaml:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListBox Name="OptionsList" Background="Transparent">
<ListBox.ItemTemplate>
<DataTemplate>
<Frame Name="GroupItemFrame" Loaded="GroupItemFrame_Loaded" Width="100" Height="100" BorderBrush="Red" BorderThickness="2" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Mainpage.xaml.cs:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
List<Page> MyPages = new List<Page>();
MyPages.Add(new DisplayNumberPage());
MyPages.Add(new DisplayNumberPage());
MyPages.Add(new DisplayNumberPage());
MyPages.Add(new DisplayNumberPage());
MyPages.Add(new DisplayNumberPage());
OptionsList.ItemsSource = MyPages;
}
int Index = 1;
private void GroupItemFrame_Loaded(object sender, RoutedEventArgs e)
{
Frame MyFrame = sender as Frame;
MyFrame.Navigate(typeof(DisplayNumberPage), Index);
Index++;
}
}
DisplayNumberPage.xaml:
<Grid Background="Black">
<TextBlock x:Name="DisplayNumber" FontSize="30" Text="100" Foreground="White"/>
</Grid>
DispayNumberPage.xaml.cs:
public sealed partial class DisplayNumberPage : Page
{
public DisplayNumberPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
DisplayNumber.Text = e.Parameter.ToString();
}
}
I'm adding this as an answer so it can be visible.
I found the optimal answer to my question after i used the answer marked as correct. They both work, it's just another way, simpler i'd argue, to achieve the same result:
<ListBox Name="OptionsList" ItemsSource="{Binding Path=OptionsPages, Mode=TwoWay}" Background="Transparent">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<StackPanel Name="OptionsItemStack" Orientation="Vertical">
<Label Name="GroupItemLabel" Content="{Binding Path=Title, Mode=OneWay}" Foreground="White"/>
<Frame Name="GroupItemFrame" Content="{Binding}"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
Notice the Content="{Binding}". It doesn't need a code behind to display the Page in the frame.
As stated, it was tested and works, no errors in the output either.
I want to create a ListBox inside a UserContorl, and than, using that userControl to show and "manage" that list in many pages.
For example i got a list of trucks, each object truck has some property like the name, the id...
Now i create my own UserControl
<UserControl
x:Class="Crud.View.ListboxInUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Crud.View"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Name="myUserControl"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<ListBox x:Name="aName" ItemsSource="{Binding ??}">
<StackPanel>
<StackPanel Orientation="Vertical" Margin="0,20,0,0">
<TextBlock Text="Id"/>
<TextBlock Text="{Binding Id}" />
</StackPanel>
<StackPanel Orientation="Vertical" Margin="0,20,0,0">
<TextBlock Text="Name"/>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</StackPanel>
</ListBox>
</Grid>
How can i bind the items in the code behind?
And how can i manage the "click" on the list?
In a Page.xaml i want to write something like
<LUC:ListboxInUserControl x:Name="MyListbox DataContext="{Binding}"/>
and in the code behind
private ObservableCollection<Truck> TestList { get; set; }
...
TestList = await TruckService.GetAll(); //a method to get the list
MyListbox.MyItemsSource = TestList;
Add listbox to your UserControl,
<ListBox x:Name="aName" SelectionChanged="aName_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Vertical" Margin="0,20,0,0">
<TextBlock Text="Id"/>
<TextBlock Text="{Binding Id}" />
</StackPanel>
<StackPanel Orientation="Vertical" Margin="0,20,0,0">
<TextBlock Text="Name"/>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Add event handler for getting selection changed and public property of listbox to bind objects in user control code behind,
public event EventHandler<EventArgs> SelectionChangedEvent;
public ListBoxInUserControl()
{
this.InitializeComponent();
}
private void aName_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectionChangedEvent(sender, new EventArgs());
}
private ListBox myVar;
public ListBox MyProperty
{
get { return aName; }
set { aName = value; }
}
Then you can add this usercontrol in your xaml,
<local:ListBoxInUserControl x:Name="uc_ListBoxInUserControl" SelectionChangedEvent="uc_ListBoxInUserControl_SelectionChangedEvent"> </local:ListBoxInUserControl>
In code behind you can bind data ,
uc_ListBoxInUserControl.MyProperty.ItemsSource = TestList;
and access selection changed event,
private void uc_ListBoxInUserControl_SelectionChangedEvent(object sender, EventArgs e)
{
}
I posted a similar question earlier, but I was having an issue with getting data from the ViewModel into the View. The issue lies with getting the data out of the object where it is stored when it is time to bind to the View. I created a class that declares 3 items which I use to help populate an ObservableCollection of items that will be bound to a ListBox in the view. I am not sure if I am going about this correctly, so to illustrate I will show below:
ListItem.cs (this is the custom class I defined to help populate the collection of items)
public string Favicon
{
get;
set;
}
public string Name
{
get;
set;
}
public string Address
{
get;
set;
}
MainPage.xaml.cs (here I want to save the data for each item to be added in the ObservableCollection)
void addToFavorites_Click(object sender, EventArgs e)
{
var favoriteItem = new ListItem { Favicon = "/Image/1.jpg", Name = "item1", Address = "some address" };
Settings.FavoritesList.Value.Add(favoriteItem);
}
Settings.cs (the settings class used to store the FavoritesList ObservableCollection)
public class Settings
{
public static Setting<ObservableCollection<ListItem>> FavoritesList = new Setting<ObservableCollection<ListItem>>("Favorites", new ObservableCollection<ListItem>());
}
Now I am attempting to call this stored ObservableCollection FavoritesList in my ViewModel so that I may bind it to a view in another page.
MainViewModel.cs
public ObservableCollection<ListItem> FavoriteItems { get; private set; }
public MainViewModel()
{
FavoriteItems = Settings.FavoritesList.Value;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And then on navigation to my FavoritesPage.xaml, I would like to bind the ViewModel to the View to be displayed in a listbox
FavoritesPage.xaml
<ListBox x:Name="FavoritesListBox" ItemsSource="{Binding FavoriteItems}" SelectionChanged="FavoritesListBox_SelectionChanged">
<StackPanel Orientation="Horizontal" Margin="12,0,12,0">
<Image x:Name="favicon" Source="{Binding Favicon}" Width="50" Height="50"/>
<StackPanel>
<TextBlock x:Name="favoritesName" Text="{Binding Name}" FontSize="{StaticResource PhoneFontSizeExtraLarge}"/>
<TextBlock x:Name="favoritesAddress" Text="{Binding Address}" Margin="12,0,0,0"/>
</StackPanel>
</StackPanel>
</ListBox>
FavoritesPage.xaml.cs
public FavoritesPage()
{
InitializeComponent();
// Set the data context of the listbox control to the sample data
DataContext = App.ViewModel;
}
Now for some reason I cannot set DataContext = App.ViewModel;. I believe I narrowed the problem to when I initially saved the values in the MainPage.xaml.cs using the ListItem class. I am unsure of how to populate the ListPicker from here? Am I doing something wrong somewhere, or should I do something different to set the datacontext correctly?
The setting of the DataContext doesn't look wrong, as long as App.ViewModel is correctly set to an instance of your MainViewModel class.
However, you are defining your ListBox XAML incorrectly.
In order to define how your items will be displayed in a ListBox, you must use the ItemsControl.ItemTemplate property.
<ListBox x:Name="FavoritesListBox" ItemsSource="{Binding FavoriteItems}" SelectionChanged="FavoritesListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="12,0,12,0">
<Image x:Name="favicon" Source="{Binding Favicon}" Width="50" Height="50"/>
<StackPanel>
<TextBlock x:Name="favoritesName" Text="{Binding Name}" FontSize="{StaticResource PhoneFontSizeExtraLarge}"/>
<TextBlock x:Name="favoritesAddress" Text="{Binding Address}" Margin="12,0,0,0"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In App.xaml.cs do:
private static MainViewModel viewModel = null;
public static MainViewModel ViewModel
{
get
{
// Delay creation of the view model until necessary
if (viewModel == null)
{
viewModel = new MainViewModel();
}
return viewModel;
}
}
In your Xaml do as Daniel recommended:
<ListBox x:Name="FavoritesListBox" ItemsSource="{Binding FavoriteItems}" SelectionChanged="FavoritesListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="12,0,12,0">
<Image x:Name="favicon" Source="{Binding Favicon}" Width="50" Height="50"/>
<StackPanel>
<TextBlock x:Name="favoritesName" Text="{Binding Name}" FontSize="{StaticResource PhoneFontSizeExtraLarge}"/>
<TextBlock x:Name="favoritesAddress" Text="{Binding Address}" Margin="12,0,0,0"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In your MainViewModel.cs do:
public ObservableCollection<ListItem> FavoriteItems
{
get;
private set;
}
Now your DataContext = App.ViewModel should work.
Implement the IS settings like shown here
Please review the code for the ListBox I am using
<ListBox Name="listBoxDefaultAcc" HorizontalAlignment="Left" VerticalAlignment="Top" Width="450" Height="410">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="60" Width="450">
<RadioButton Content="{Binding}" GroupName="defaultAcc" HorizontalAlignment="Left" VerticalAlignment="Center" Height="80" Width="450" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now I want to access the content property of the RadioButton from codebehind.
The ListBoxItems are getting filled dynamically from the codebehind with the following code:
listBoxDefaultAcc.ItemsSource = from acc in db.Table<Accounts>()
select acc.accName;
Please help me out with this.
You can use the VisualTreeHelper and drill down to the control. This is not recommended though.
Better is to only bind to the properties of the controls in you datatemplate and then retrieve the values by getting the binded values. Technically in this case, if you would want to change the content of the radiobutton then you would need to change the item in the itemssource
Can you explain what you are trying to archieve by getting the content of the radiobutton?
Edit**********
<ListBox Name="listBoxDefaultAcc" HorizontalAlignment="Left" VerticalAlignment="Top" Width="450" Height="410">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="60" Width="450">
<RadioButton Content="{Binding Name}" IsChecked="{Binding Selected, Mode=TwoWay}" GroupName="defaultAcc" HorizontalAlignment="Left" VerticalAlignment="Center" Height="80" Width="450" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
public partial class Home : Page
{
public Home()
{
InitializeComponent();
var items = new List<SomeClass>();
items.Add(new SomeClass() {Name = "a"});
items.Add(new SomeClass() {Name = "b"});
items.Add(new SomeClass() {Name = "c"});
listBoxDefaultAcc.ItemsSource = items;
}
// Executes when the user navigates to this page.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
}
private void testButton_Click(object sender, RoutedEventArgs e)
{
var items = (List<SomeClass>)listBoxDefaultAcc.ItemsSource;
var selectedItem = items.Where(x => x.Selected).FirstOrDefault();
}
class SomeClass
{
public string Name { get; set; }
public bool Selected { get; set; }
}
}
You should be using DataBinding. You should bind Content to a property, that represents content, of an object, you are setting as item.
This way, you dont have to care about ListBoxes or Templates or anything. You are simply manipulating objects, and theese changes get reflected in the GUI.