I am new to WPF and MVVM and trying to follow this design, I have created a window with multiple user controls (10 of each) on it. These user controls will hold a value that should be able to be entered by the User and sent back to the Database.
The issue I have is I am creating the User Controls Pragmatically in a canvass and do not know how to use these instances to set the values on the control from my View Model where I have a SaveMethod that is binded to a Save Button to save the data into the Database. Thanks for the help.
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ClientRatesViewModel viewModel = new ClientRatesViewModel();
DataContext = viewModel;
viewModel.GetChargeUnits();
int previousTopPreRate = 10;
foreach (var rate in viewModel.ClientRatesPreAwr)
{
PreAwr preAwr = new PreAwr();
preAwr.tbPreAwrRate.Text = rate.ClientRatesPreAwr;
PreRatesCanvas.Children.Add(preAwr);
preAwr.Width = 500;
Canvas.SetLeft(preAwr, 10);
Canvas.SetTop(preAwr, previousTopPreRate + 10);
previousTopPreRate += +30;
}
int previousTopPostRate = 10;
foreach (var rate in viewModel.ClientRatesPostAwr)
{
PostAWR postAwr = new PostAWR();
postAwr.tbPostAwrRate.Text = rate.ClientRatesPostAwr;
PostRatesCanvas.Children.Add(postAwr);
postAwr.Width = 500;
Canvas.SetLeft(postAwr, 10);
Canvas.SetTop(postAwr, previousTopPostRate + 10);
previousTopPostRate += +30;
}
}
}
ItemsControl XAML:
<ItemsControl Name="icPreAwr" Margin="10,46,10,10">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,0,0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ClientRatesPreAwr }" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Technically your are beginning right because you are defining the view and the datacontext. It is recommended to use XAML but if you are beginning there is no issue and no contradiction with MVVM because your code behind is View Code behind.
But here begins the problems, for instance instead of using a foreach, you should use a ItemsControl with an itemtemplate that will be a PreAwr and ItemsSource ClientRatesPreAwr. That will feed your itemscontrol and fill with the PreAwr by itseld PreAwr UserControl has a tbPreAwrRate set the content to {Binding ClientRatesPreAwr} and it will fill with that value.
If you need to make this by code, the properties of the controls are dependency properties and you can bind them by code using
https://msdn.microsoft.com/en-us/library/cc838207%28v=vs.95%29.aspx
I hope it helps you, I strongly recommend you to make the step to design in XAML if the project rules can be done in that way
Indeed my article could be helpful for you http://bit.ly/1CoYRkQ
For anyone reading this I managed to achieve this by doing the following in my XAML.
<ListBox ItemsSource="{Binding ClientRatesPreAwr}"
KeyboardNavigation.TabNavigation="Continue" Margin="0,58,0,69">
<ItemsControl.ItemContainerStyle>
<Style TargetType="ListBoxItem">
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="2" Focusable="False">
<UserControls:PreAwr />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
Related
I have a grid with some data and one of the cells contains rather long strings (and there could be quite a few). So to not use too much of the available window space, I'd like those strings to be scrollable. Vertically works, but whatever I try, I can't get a horizontal scrollbar.
This is my xaml:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="150" Width="250">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="100"/>
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" Grid.Column="0">
<ItemsControl ItemsSource="{Binding Path=BoundTexts}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Text}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</Grid>
</Window>
and here's the code behind to test.
using System.Collections.Generic;
using System.Windows;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<BoundClass> temp = new List<BoundClass>();
for (int i = 0; i < 10; i++)
{
string t = ""; // just to create some long strings.
for (int j = 0; j < 10; j++) // I know it can be done better.
{
t += $"{j*10:D2}********";
}
temp.Add(new BoundClass(t));
}
BoundTexts = temp.ToArray();
DataContext = this;
}
public BoundClass[] BoundTexts { get; set; }
}
public class BoundClass
{
public string Text { get; set;}
public BoundClass(string text)
{
Text = text;
}
}
}
I know there are a few similar questions on here, but as far as I have seen, they are all shrouded in templates and other complex topics. Also some are answered by "make sure you have a restraining container around it", I think I do by the grid.
Setting ScrollViewer.HorizontalScrollBarVisibility and ScrollViewer.VerticalScrollBarVisibility to "Auto" should do the trick.
Default value for VerticalScrollBarVisibility is ScrollBarVisibility.Visible BUT for HorizontalScrollBarVisibility is ScrollBarVisibility.Disabled. So it should be even enough to set only HorizontalScrollBarVisibility = "Auto"
<ScrollViewer Grid.Row="0" Grid.Column="0" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
The proper way to add a ScrollViewer to an ItemsControl is to put it into the ControlTemplate to wrap the ItemsPresenter. This way the ScrollViewer behaves correctly.
If you don't do it this way (and instead wrap the ItemsControl) the ScrollViewer will try to scroll the ItemsControl and not the items!
But you want the ScrollViewer to detect when items of the ItemsControl overflow. Naturally, the ItemsControl itself will stretch to occupy the available space - it won't overflow and will be displayed correctly (fully visible). But the items do overflow the space of the ItemsControl. That's why wrapping the ItemsControl won't work as expected.
Additionally, you must explicitly enable the ScrollViewer.HorizontalScrollBarVisibility. It's important to know that setting it to Auto will impact the performance. It's recommended to set it to Hidden or Visible.
<ItemsControl Height="200">
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<ScrollViewer CanContentScroll="True"
HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible">
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<!-- Consider to enable virtualization by adding a VirtualizingStackPanel as host Panel.
Important: don't forget to set ScrollViewer.CanContentScroll to 'True' -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Is it possible to add and bind user controls dynamically? Maybe I'll show sample code to show what I exactly mean.
MainWindow:
<UniformGrid
Rows="11"
Columns="11"
DataContext="{StaticResource vm}">
<local:DynamicUserControl
ButClickControl="{Binding Path=UserControlObjects[0].ButClickCommand}"
SomeDataInUserControl="{Binding Path=UserControlObjects[0].SomeData, Mode=OneWay}" />
<local:DynamicUserControl
ButClickControl="{Binding Path=UserControlObjects[1].ButClickCommand}"
SomeDataInUserControl="{Binding Path=UserControlObjects[1].SomeData, Mode=OneWay}" />
<local:DynamicUserControl
ButClickControl="{Binding Path=UserControlObjects[2].ButClickCommand}"
SomeDataInUserControl="{Binding Path=UserControlObjects[2].SomeData, Mode=OneWay}" />
.....
</UniformGrid>
In ViewModel there is an array of UserControlObjects. But in this array I will have over 100 elements, so it is not the best option to write all elements one by one. Is there any way to add DynamicUserControls not in XAML but somewhere in code in loop with keeping the MVVM pattern and binding?
Use an ItemsControl with the UniformGrid as ItemsPanel and the DynamicUserControl in the ItemTemplate:
<ItemsControl DataContext="{StaticResource vm}"
ItemsSource="{Binding UserControlObjects}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="11" Columns="11"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:DynamicUserControl
ButClickControl="{Binding ButClickCommand}"
SomeDataInUserControl="{Binding SomeData}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In my opinion, you would want to keep any controls out of your view model. You could however keep the individual view models that back the controls in a list within the main view model. For example, create the view model that will provide the data for the “dynamic” controls.
class SubViewModel
{
public string Name { get; private set; } = string.Empty;
public SubViewModel(string aName)
{
Name = aName;
}
}
And in the main view model you can do whatever you would do to dynamically create instances. In this case, I am just creating then in a for loop.
class MainWindowViewModel
{
public ObservableCollection<SubViewModel> SubViewModels
{
get
{
return mSubViewModels;
}
} private ObservableCollection<SubViewModel> mSubViewModels = new ObservableCollection<SubViewModel>();
public MainWindowViewModel()
{
for(int i = 0; i < 30; i++)
{
SubViewModels.Add(new SubViewModel($"Control: {i}"));
}
}
}
Then in the view, you can utilize an ItemsControl with an UniformGrid based ItemsPanelTemplate, and then whatever you want for the data template, whether you define it there explicitly, or make a user control (like your local:DynamicUserControl) to clean things up. In this sample, the data template it explicitly defined.
<Window x:Class="ListOfViewsSample.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:ListOfViewsSample"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<ItemsControl ItemsSource="{Binding SubViewModels}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Background="LightGray" Margin="10">
<Label Content="{Binding Name}" Margin="5" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
which results in the following:
If don’t want the multiple dynamic views to be the same, you can look into data template selectors to display something different based on the specified view model, but based in your question I think you were looking for a list of the same control/data. Hope this helps!
The usual way of doing this is:
Create an ItemsControl for the dynamic items you want to create
Override the ItemsPanel to whatever you need (UniformGrid in your case)
Bind it to a list of view models, with one view model per control
Define DataTemplates to map each view model type to its corresponding view type
I'm trying to bind dynamically, in code behind, a VM property (an obseravble collection) to image in listBox that sits inside a usercontrol I show on my window,
but it's not working.
This is the XAML of the user control:
<WrapPanel x:Name="panel" HorizontalAlignment="Center" Focusable="False" FocusManager.IsFocusScope="False">
<ListBox x:Name="MazeListBox" ItemsSource="{Binding}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid x:Name="MazeUniformGrid" Columns="{Binding VM_MazeCols}" Rows="{Binding VM_MazeRows}"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Image x:Name="Img" Margin="-6" Source="{Binding}" Focusable="False"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</WrapPanel>
This are the usercontrols in XAML of the Outer Window:
<Controls:MazeBoard1 x:Name="you" Margin="0,0,650,0" Grid.Column="0" Height="Auto" Width="Auto" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Controls:MazeBoard1 x:Name="other" Margin="650,0,0,0" Grid.Column="0" Height="Auto" Width="Auto" Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center"/>
This is how I bind dynamically in the cs of the window:
Binding youBinding = new Binding
{
Path = new PropertyPath("VM_MazeDisplay"),
};
Binding otherBinding = new Binding
{
Path = new PropertyPath("VM_OtherMazeDisplay"),
};
you.MazeListBox.SetBinding( ContentControl.ContentProperty,youBinding);
other.MazeListBox.SetBinding(ContentControl.ContentProperty, otherBinding);
I'll appreciate your help.
Thank you
Everything looks weird on your XAML...but if you do the following:
you.MazeListBox.SetBinding(ListBox.DataContextProperty, youBinding)
or
you.SetBinding(UserControl.DataContextProperty, youBinding)
or even you.MazeListBox.SetBinding(ListBox.ItemsSourceProperty, youBinding) (you will have to remove the binding into your xaml).
You should have the expected results.
However, why doing a binding in this point and not just only setting the DataContext? Something like you.DataContext = VM_MazeDisplay (assuming the instance of the VM is called that way).
Also, why do you put your ListBox into a WrapPanel?
I have an MVVM app. I want a collection of buttons to be represented from the ViewModel and be dynamic.
Which means I want to populate the window with controls from the ViewModel.
I tried creating a content control and binding it's Content property to a Grid which I will put buttons in. The binding did not work, it remains empty.
I tried binding it to a simple string, still nothing. I should mention that other simple bindings do work, so that's why it's weird.
The creation of the UserControl:
<TabControl HorizontalAlignment="Left" Height="696" Margin="429,0,0,32" VerticalAlignment="Bottom" Width="552" ItemsSource="{Binding TabCollection}">
<TabControl.Resources>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<cattab:CategoryTab/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>`
The binding in the UserControl:
<ContentControl HorizontalAlignment="Left" Height="286" Margin="98,152,0,-396" VerticalAlignment="Top" Width="313">
<ContentControl Content="{Binding Favorites}" Margin="0,30,0,0" VerticalAlignment="Top"/>
</ContentControl>`
MainViewModel:
//**** Initilize TabCollection with fake data (temporary)
TabCollection.Add(new CategoryTabViewModel { Header = "בדיקה11" });
TabCollection.Add(new CategoryTabViewModel { Header = "בדיקה2" });
UserControl ViewModel:
public CategoryTabViewModel()
{
SearchText = "bbbaaaa";
Favorites.Add(new Button());
}
The binding of SearchText works, on Favorites it's not
Try to use an ItemsControl
Here's a good tutorial: http://www.wpf-tutorial.com/list-controls/itemscontrol/
<ItemsControl HorizontalAlignment="Left" Height="286" Margin="98,152,0,-396" VerticalAlignment="Top" Width="313" Content="{Binding Favorites}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controlsToolkit:WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Use a WrapPanel to manage your layout: http://www.wpftutorial.net/WrapPanel.html
After reading your updates I can say that your code is not MVVM compliant because your ViewModel layer is aware of the View layer (you create Buttons in your ViewModel)
What you need to do:
XAML of your CategoryTab
<ItemsControl ItemsSource="{Binding Favorites, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button/><!--Show whatever you want here-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If you want to create new Button every time an object is added to Favorites you will need to make Favorites an ObservableCollection.
Im a beginner in WPF programming, coming from .NET 2.0 C#.
Im trying to make a horizontal StackPanel which should be filled with data from a table in a database. The problem is that I want it to display an image with some text from the table below and then stack those two items horizontally.
Here's some pseudo-code to display what I want to do:
<StackPanel Orientation="horizontal" ItemsSource="{Binding Path=myTable}">
<StackPanel>
<Image Source="User.png"/>
<Label HorizontalAlignment="Center" Content="{Binding Path=UserName}"></Label>
</StackPanel>
</StackPanel>
I simply cannot figure oout how to do this.
Julien's answer is correct for your written description, however, looking at your XAML, it appears you are looking for something like the following:
<DataTemplate x:Key="UserDataTemplate">
<StackPanel>
<Image Source="User.png"/>
<Label HorizontalAlignment="Center" Content="{Binding Path=UserName}" />
</StackPanel>
</DataTemplate>
<ItemsControl x:Name="UserList" ItemTemplate="{StaticResource UserDataTemplate}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
You definately need an ItemsControl (or some derivation of) to bind your source to. Then you can change the the orientation by setting it's items panel (which I believe is a VirtualizingStackPanel with Vertical orientation by default) so just set it to a VirtualizingStackPanel with Horizontal Orientation. Then you can set the ItemsTemplate for each of your items to the layout you desire (an image stacked on top of text bound from your database).
Basically, you want to use a control capable of displaying an enumeration of objects. The control capable of this is the class ItemsControl and all of its descendants (Selector, ListBox, ListView, etc).
Bind the ItemsSource property of this control to a list of objects you want, here a list of users you've fetched from the database. Set the ItemTemplate of the control to a DataTemplate that will be used to display each item in the list.
Sample code:
In a Resources section (for example Window.Resources):
<DataTemplate x:Key="UserDataTemplate">
<StackPanel Orientation="Horizontal">
<Image Source="User.png"/>
<Label HorizontalAlignment="Center" Content="{Binding Path=UserName}" />
</StackPanel>
</DataTemplate>
In your Window/Page/UserControl:
<ItemsControl x:Name="UserList" ItemTemplate="{StaticResource UserDataTemplate}" />
In your code behind:
UserList.ItemsSource = ... // here, an enumeration of your Users, fetched from your db