I want to simply display my UserControl in a separate window, for example by calling
var windowHandler = new WindowHandler();
windowHandler.Show(new SchoolViewModel);
How do I archive this? I have tried the following:
Set the DataTemplate in App.xaml:
<Application.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type viewModel:SchoolViewModel}">
<view:SchoolUserControl />
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
In code-behind call it:
private void Application_Startup(object sender, StartupEventArgs e)
{
var windowHandler = new WindowHandler();
windowHandler.ShowWindow(new SchoolViewModel(), 200, 200);
}
WindowHandler class:
public class WindowHandler
{
public void ShowWindow(object dataContext, int height, int width)
{
Window window = new Window()
{
DataContext = dataContext,
Width = width,
Height = height
};
window.Show();
}
}
It does show a window, but it's empty. Why is it empty? I also set the DataContext in the UserControl's code-behind:
public SchoolUserControl()
{
InitializeComponent();
DataContext = this;
}
Window is by default templated to show Window.Content and not Window.DataContext. So you should assign whatever you want to show as content:
public class WindowHandler
{
public void ShowWindow(object dataContext, int height, int width)
{
Window window = new Window()
{
Content = dataContext,
Width = width,
Height = height
};
window.Show();
}
}
Also, as others noted, you should remove this line:
DataContext = this;
from your SchoolUserControl, because otherwise you won't have access to the templated view-model from within the control. And since SchoolUserControl is part of a DataTemplate, the templated view-model will be automatically available from SchoolUserControl.DataContext.
Related
I want to be able to dynamically add different (self-created) "widgets" to my canvas and position them using Canvas.Top and Canvas.Left.
I have been able to add items using Canvas.Children.Add(), but I can't figure out how to create a binding to the Top and Left values.
Would it be a better idea to somehow bind the contents of the Canvas to a list and create all the bindings in XAML? Then again, how would I do that?
If you don't know the bindings in UWP, you can see this document.
Depending on your situation, you can consider using MVVM for binding, creating a ViewModel in Code-Behind and using Binding in the xaml to bind related properties.
CanvasPageViewModel.cs
public class CanvasPageViewModel : INotifyPropertyChanged
{
private double _rectTop;
public double RectTop
{
get => _rectTop;
set
{
_rectTop = value;
OnPropertyChanged();
}
}
private double _rectLeft;
public double RectLeft
{
get => _rectLeft;
set
{
_rectLeft = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
CanvasPage.xaml.cs
public CanvasPageViewModel viewModel = new CanvasPageViewModel();
public CanvasPage()
{
this.InitializeComponent();
this.DataContext = viewModel;
viewModel.RectTop = 20;
viewModel.RectLeft = 100;
}
CanvasPage.xaml
<Grid>
<Canvas Width="500" Height="500" Background="White">
<Rectangle Width="50" Height="50" Fill="Blue"
Canvas.Top="{Binding RectTop}"
Canvas.Left="{Binding RectLeft}"/>
</Canvas>
</Grid>
This is a simple example. If you want to modify the position of the control later, you can directly modify the data source and the UI will synchronize.
Update
If you need to manually add child elements of Canvas, you can use this method:
var myRect = new Rectangle()
{
Height = 50,
Width = 100,
Fill = new SolidColorBrush(Colors.Blue)
};
myCanvas.Children.Add(myRect);
But if you want to bind the created Rectangle element, as you said, bind the Canvas.Left property, you can write:
public CanvasPageViewModel viewModel = new CanvasPageViewModel();
public CanvasPage()
{
this.InitializeComponent();
var myRect = new Rectangle()
{
Height = 50,
Width = 100,
Fill = new SolidColorBrush(Colors.Blue)
};
Binding leftBinding = new Binding()
{
Path = new PropertyPath("RectLeft"),
Mode=BindingMode.OneWay
};
myRect.SetBinding(Canvas.LeftProperty, leftBinding);
myCanvas.Children.Add(myRect);
}
Best regards.
I'm making a UserControl to generate a list of attached files from Dependency Property as ItemSource. But the ItemSource (DependencyProperty) count is 0.
I tried debugging and realized that the ObservableCollection in ViewModel was bound after the Constructor of my UserControl is initialized.
I'm coding in MVVM pattern, I made a function to prepare some sample data for ObservableCollection in ViewModel and inside the MainWindow I bound the DataContext of my UserControl with that ViewModel then set the ItemSource for ObservableCollection
My ViewModel code-behind:
//The properties
ObservableCollection<FileAttachmentModel> filesAttachment;
public ObservableCollection<FileAttachmentModel> FilesAttachment
{
get { return filesAttachment; }
set { filesAttachment = value; OnPropertyChanged("FilesAttachment"); }
}
//The function prepare sample data
private ObservableCollection<FileAttachmentModel> PrepareData()
{
FilesAttachment.Add(new FileAttachmentModel() { FileName = "TrackA", FilePath = "D:\trackA.png" });
FilesAttachment.Add(new FileAttachmentModel() { FileName = "TrackB", FilePath = "D:\trackB.png" });
FilesAttachment.Add(new FileAttachmentModel() { FileName = "TrackC", FilePath = "D:\trackC.png" });
}
My UserControl xaml:
<UserControl x:Class="MailSender.Controls.FileAttachment.FileAttachment"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MailSender.Controls.FileAttachment"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Name="fileAttachmentUC"
>
<Grid>
<WrapPanel DataContext="{Binding ElementName=fileAttachmentUC,Path=DataContext,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" x:Name="wrapPanel">
</WrapPanel>
</Grid>
</UserControl>
My UserControl code-behind:
//the property ItemSource
public static readonly DependencyProperty ItemSourceProperty = DependencyProperty.Register("ItemSource", typeof(ObservableCollection<FileAttachmentModel>), typeof(FileAttachment),new UIPropertyMetadata());
//the wrapper property
public ObservableCollection<FileAttachmentModel> ItemSource
{
get { return (ObservableCollection<FileAttachmentModel>)GetValue(ItemSourceProperty); }
set { SetValue(ItemSourceProperty, value);
}
}
//the function to generate each file attachment and add them to the Wrappanel in UserControl
//I call this function inside constructor of UserControl and pass ItemSource as parameter
void GenerateFileItem(ObservableCollection<FileAttachmentModel> lstFileAttachment)
{
if (lstFileAttachment != null && lstFileAttachment.Count>0)
{
foreach (var item in lstFileAttachment)
{
StackPanel sp = new StackPanel() { Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Center };
TextBlock tbFileName = new TextBlock() { Text = item.FileName };
Button btFilePath = new Button() { Content = "X", Tag = item.FilePath };
btFilePath.Click += BtFilePath_Click;
sp.Children.Add(tbFileName);
sp.Children.Add(btFilePath);
sp.Style = Application.Current.FindResource("stackFileItem") as Style;
wrapPanel.Children.Add(sp);
}
}
}
In Usage:
<control:FileAttachment DataContext="{StaticResource vmMainWindow}" ItemSource="{Binding FilesAttachment,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
What I expect is to make a container for attached files like Outlook of Microsoft. Please help!
Thanks in advance!
You should call GenerateFileItem whenever the dependency property is set using a PropertyChangedCallback:
public static readonly DependencyProperty ItemSourceProperty = DependencyProperty.Register("ItemSource",
typeof(ObservableCollection<FileAttachmentModel>), typeof(FileAttachment), new PropertyMetadata(new PropertyChangedCallback(OnChanged));
//the wrapper property
public ObservableCollection<FileAttachmentModel> ItemSource
{
get { return (ObservableCollection<FileAttachmentModel>)GetValue(ItemSourceProperty); }
set { SetValue(ItemSourceProperty, value); }
}
private static void OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FileAttachment fa = (FileAttachment)d;
fa.GenerateFileItem(fa.ItemSource);
}
The ItemSource property cannot be set before the UserControl has been initialized.
What I was facing is the ObservableCollection I generate in ViewModel is initialized after the UserControl is initialized. The #mm8 solution has fixed my problem by waiting for the ObservableCollection in ViewModel is initialized first and then pass it by the property I bound in MainWindow. Then the UserControl will be initialized and get the ObservableCollection that is passed from ViewModel then generate the custom controls inside my "wrapPanel".
I'm trying to make my main window to remember and restore the position and size on startup. So I tried to bind my window's startup location to a properties in my viewmodel as following:
<Window x:Class="MyApp.Views.MainWindow"
...
Width="{Binding Width}"
Height="{Binding Height}"
WindowStartupLocation="{Binding WindowStartupLocation}"
WindowState="{Binding WindowState}"
MinHeight="600"
MinWidth="800"
Closing="OnWindowClosing"
Closed="OnWindowClosed"
ContentRendered="OnMainWindowReady"
...>
My viewmodel:
...
// Default settings
WindowState = (WindowState)FormWindowState.Normal;
this.WindowStartupLocation = WindowStartupLocation.CenterScreen;
Width = 800;
Height = 600;
// check if the saved bounds are nonzero and is visible on any screen
if (Properties.Settings.Default.WindowStartupLocation != Rectangle.Empty &&
IsVisibleOnAnyScreen(Properties.Settings.Default.WindowStartupLocation))
{
this.WindowStartupLocation = WindowStartupLocation.Manual;
this.WindowState = (WindowState)Properties.Settings.Default.WindowState;
Height = Properties.Settings.Default.WindowStartupLocation.Size.Height;
Width = Properties.Settings.Default.WindowStartupLocation.Size.Width;
Left = Properties.Settings.Default.WindowStartupLocation.Left;
Top = Properties.Settings.Default.WindowStartupLocation.Top;
}
...
When i run the application i get a System.Windows.Markup.XamlParseException and Additional information: A 'Binding' cannot be set on the 'WindowStartupLocation' property of type 'MainWindow'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
How should i correct this?
Try using attached behavior which lets you bind the WindowStartupLocation property:
namespace YourProject.PropertiesExtension
{
public static class WindowExt
{
public static readonly DependencyProperty WindowStartupLocationProperty;
public static void SetWindowStartupLocation(DependencyObject DepObject, WindowStartupLocation value)
{
DepObject.SetValue(WindowStartupLocationProperty, value);
}
public static WindowStartupLocation GetWindowStartupLocation(DependencyObject DepObject)
{
return (WindowStartupLocation)DepObject.GetValue(WindowStartupLocationProperty);
}
static WindowExt()
{
WindowStartupLocationProperty = DependencyProperty.RegisterAttached("WindowStartupLocation",
typeof(WindowStartupLocation),
typeof(WindowExt),
new UIPropertyMetadata(WindowStartupLocation.Manual, OnWindowStartupLocationChanged));
}
private static void OnWindowStartupLocationChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Window window = sender as Window;
if (window != null)
{
window.WindowStartupLocation = GetWindowStartupLocation(window);
}
}
}
}
The usage:
<Window
PropertiesExtension:WindowExt.WindowStartupLocation="{Binding ..}" />
As the error stated, WindowStartupLocation is not a dependency propety, which means you can't bind it. The solution can be either deriving from Window, and creating dependency property, or using attached behavior.
You cannot bind the WindowsStartupLocation, this line generates the error:
WindowStartupLocation="{Binding WindowStartupLocation}"
You can set it to some particular value, like that:
WindowStartupLocation="CenterScreen"
I want to bind the height of a control the sum of two other heights, so the UI looks nice various screen sizes.
<GridView
AutomationProperties.AutomationId="ItemDetails"
ItemsSource="{Binding data}"
IsSwipeEnabled="False"
SelectionMode="None" Height="{Binding Height, (ElementName=item - ElementName=itemTitle)}" >
<GridView.ItemTemplate>
<DataTemplate>
<dll:TaskItemControl/>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
The above XAML is invalid, but it demonstrates what I want to do. I have two elements, item and itemTitle. item is a ScrollView that gets set to the height of the screen and I want the GridView to be the same height as the ScrollView minus the height of the itemTitle.
Is there a way to do this in XAML?
Note: The reasons for doing this are beyond the scope of this question. So please don't comment about restricting the height of a control within a ScrollView.
This can be easily done in code behind by subscribing the SizeChanged events of the two elements and update the Height of the GridView whenever the handlers are called.
But you want a pure XAML solution, and this is where Behaviors come into play.
Use a Behavior.
First you need to add Blend SDK reference to your project.
Then you need to create a new class that implements IBehavior. This class needs three dependency properties just to reference the GridView, the item and the itemTitle. So you can subscribe to their SizeChanged events and calulate the Height accordingly.
I choose to attach this behavior to a top level Panel (most likely your LayoutRoot Grid) because I want to ensure that all the elements under it are rendered properly inside its Loaded event handler.
The full Behavior class would look something like this -
public class HeightBehavior : DependencyObject, IBehavior
{
public GridView GridView
{
get { return (GridView)GetValue(GridViewProperty); }
set { SetValue(GridViewProperty, value); }
}
public static readonly DependencyProperty GridViewProperty =
DependencyProperty.Register("GridView", typeof(GridView), typeof(HeightBehavior), new PropertyMetadata(null));
public FrameworkElement FirstItem
{
get { return (FrameworkElement)GetValue(FirstItemProperty); }
set { SetValue(FirstItemProperty, value); }
}
public static readonly DependencyProperty FirstItemProperty =
DependencyProperty.Register("FirstItem", typeof(FrameworkElement), typeof(HeightBehavior), new PropertyMetadata(null));
public FrameworkElement SecondItem
{
get { return (FrameworkElement)GetValue(SecondItemProperty); }
set { SetValue(SecondItemProperty, value); }
}
public static readonly DependencyProperty SecondItemProperty =
DependencyProperty.Register("SecondItem", typeof(FrameworkElement), typeof(HeightBehavior), new PropertyMetadata(null));
public DependencyObject AssociatedObject { get; set; }
public void Attach(DependencyObject associatedObject)
{
this.AssociatedObject = associatedObject;
var control = (Panel)this.AssociatedObject;
control.Loaded += AssociatedObject_Loaded;
}
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
this.FirstItem.SizeChanged += FirstItem_SizeChanged;
this.SecondItem.SizeChanged += SecondItem_SizeChanged;
// force to re-calculate the Height
this.FirstItem.Width += 0.5;
}
private void FirstItem_SizeChanged(object sender, SizeChangedEventArgs e)
{
this.SetAssociatedObjectsHeight();
}
private void SecondItem_SizeChanged(object sender, SizeChangedEventArgs e)
{
this.SetAssociatedObjectsHeight();
}
private void SetAssociatedObjectsHeight()
{
this.GridView.Height = this.FirstItem.ActualHeight - this.SecondItem.ActualHeight;
}
public void Detach()
{
this.FirstItem.SizeChanged -= FirstItem_SizeChanged;
this.SecondItem.SizeChanged -= SecondItem_SizeChanged;
var control = (Panel)this.AssociatedObject;
control.Loaded -= AssociatedObject_Loaded;
}
}
Then in my XAML, I attach it to my top level Grid, like this.
<Grid x:Name="LayoutRoot">
<Interactivity:Interaction.Behaviors>
<local:HeightBehavior GridView="{Binding ElementName=itemGridView}" FirstItem="{Binding ElementName=item}" SecondItem="{Binding ElementName=itemTitle}"/>
</Interactivity:Interaction.Behaviors>
Hope this helps.
How do you change the size of your Window in WPF programmatically using an MVVM approach?
I am setting the window height to 400 from XAML and on click of a button on the form trying to increase the height to 500.
In my button's ICommand I am using:
Application.Current.MainWindow.Height = 500;
But it's not doing anything.
Try setting the 'Application.Current.MainWindow' property in the Loaded event in the MainWindow.xaml.cs file:
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Application.Current.MainWindow = this;
}
UPDATE >>>
My friend, please remember, you said you wanted to use Application.Current.MainWindow... this is how you could use it. However, if you want to do this the MVVM way, then why don't you just bind the value to the Window.Width properties?
<Window Width="{Binding Width}" MinWidth="{Binding Width}" MaxWidth="{Binding Width}">
...
</Window>
Please note that binding to Window.Width is not enough for this to work.
I had a similar requirement to this, with the addition that I wanted to track some other window settings. So, I have this class:
public class WindowSettings
{
public int Width { get; set; }
public int Height { get; set; }
public int Left { get; set; }
public int Top { get; set; }
}
Then in my view model, I have:
public WindowSettings WindowSettings
{
get
{
return _windowSettings;
}
}
and in the xaml, this:
<Window ...
Height="{Binding WindowSettings.Height,Mode=TwoWay}"
Width="{Binding WindowSettings.Width, Mode=TwoWay}"
Left="{Binding WindowSettings.Left,Mode=TwoWay}"
Top="{Binding WindowSettings.Top,Mode=TwoWay}">
If I wanted to programmatically update the width, for example, I do this:
WindowSettings.Width = 456;
RaisePropertyChangedEvent("WindowSettings.Width");
(Naturally, my view model inherits from a base class which implements INotifyPropertyChanged.)
I did toy with the idea of providing an event on the WindowSettings class so that an update to a property could automatically cause the property change notification, but my requirement is to track the window size and set it on startup, so I didn't bother. Hope that helps.
Using VS 2017 and Caliburn Micro I have no issue with just setting the width. The way you are doing it isn't MVVM your VM doesn't need to know about the V or what's happening when the int Width changes.
public class ShellViewModel : Caliburn.Micro.PropertyChangedBase, IShell
{
public ShellViewModel()
{
Width = 500;
}
private int _width;
public int Width
{
get => _width;
set
{
_width = value;
NotifyOfPropertyChange(() => Width);
}
}
public void MyButton()
{
Width = 800;
}
}
In the Window XAML
Width="{Binding Width, Mode=TwoWay}"
In the Grid
<Button x:Name="MyButton" Content="Make 800 wide" />
it opens at 500 and when I click the button it stretches to 800.
I'm adding this answer because the most voted answer actually isn't correct. Binding to the MinHeight & MaxHeight, will result in a window that can't be resized using the grips.
<Window Width="{Binding Width}" MinWidth="{Binding Width}" MaxWidth="{Binding Width}">
...
</Window>
Instead add Mode=TwoWay to your binding.
<Window Width="{Binging Width, Mode=TwoWay}">
...
</Window>
Are you sure the window you want to resize is MainWindow? Or are you sure your command is successfully binding on your button? You can try the below code:
View:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:WpfApplication1.ViewModel"
Title="Wpf Application" Height="300" Width="400"
WindowStartupLocation="CenterScreen">
<Window.DataContext>
<viewModel:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Button Content="Resize" Command="{Binding ResizeCommand}" IsEnabled="{Binding ResizeCommand.CanExecute}" />
</Grid>
</Window>
ViewModel:
public class MainWindowViewModel
{
private RelayCommand _resizeCommand;
public RelayCommand ResizeCommand { get { return _resizeCommand; } }
public MainWindowViewModel()
{
this._resizeCommand = new RelayCommand(this.Resize);
}
private void Resize()
{
Application.Current.MainWindow.Height = 500;
}
}
This Works
Application.Current.MainWindow = this;
Application.Current.MainWindow.WindowState = WindowState.Normal;
Application.Current.MainWindow.Width = 800;
Application.Current.MainWindow.Height = 450;