Use UserControl in ListView UWP, Databinding Issue - c#

I wanted to create an App to manage various Project, so i created a class to represent those Projects. In order to display them i created a user Control which should display then and later also manage the interaction between User and Project-Object.
I tried to display a list of Projects in a Page but it does not work.
I am not sure what to bind to in the Data Template, none of the ways i tried worked.
I also used the live visual tree, and i think the Property Project of ProjectViewer is correctly set, but still the Textbook does not display the Name of the Project. (But I'm not so familiar with the live visual tree so i could be wrong).
If i use the User Control as a single instance and not in a list i can set the Project property and the Name gets displayed.
I think it is an issue with the DataBinding, but i couldn't work it out.
Any help would be appreciated since this is only the very first step of the App and I'm already stuck.
Here is a minimal example i created to reproduce the problem:
I have a class Project:
using System.Collections.Generic;
using Windows.UI.Xaml;
namespace ProjectTimeTracker
{
public class Project:DependencyObject
{
public string ProjName
{
get { return (string)GetValue(ProjNameProperty); }
set { SetValue(ProjNameProperty, value); }
}
public static readonly DependencyProperty ProjNameProperty =
DependencyProperty.Register(nameof(ProjName), typeof(string), typeof(Project), new PropertyMetadata("TestName"));
public Project(){}
}
}
And i created the UserControl ProjectViewer:
namespace ProjectTimeTracker.UI
{
public sealed partial class ProjectViewer : UserControl
{
public Project Project
{
get { return (Project)GetValue(ProjectProperty); }
set { SetValue(ProjectProperty, value); }
}
public static readonly DependencyProperty ProjectProperty =
DependencyProperty.Register(nameof(Project), typeof(Project), typeof(ProjectViewer), new PropertyMetadata(null));
public ProjectViewer()
{
this.InitializeComponent();
}
}
}
which has the following xaml code:
<UserControl
x:Class="ProjectTimeTracker.UI.ProjectViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ProjectTimeTracker.UI"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="100"
d:DesignWidth="400">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="38"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<RadioButton x:Name="RBSelector" HorizontalAlignment="Left" Margin="8,0,0,0" VerticalAlignment="Center" Grid.RowSpan="2"/>
<TextBlock x:Name="TBName" Grid.Row="0" Grid.Column="1" Text="{x:Bind Project.ProjName}" VerticalAlignment="Center" HorizontalAlignment="Left" Style="{ThemeResource TitleTextBlockStyle}" Width="110"/>
<TextBlock x:Name="TBTotalTime" Grid.Row="1" Grid.Column="1" Text="NYI" HorizontalAlignment="Left" VerticalAlignment="Center" Style="{ThemeResource CaptionTextBlockStyle}" Margin="0,-4,0,0"/>
</Grid>
</UserControl>
I try to use that User Control in the mainpage to create a list of Projects
using System.Collections.Generic;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace ProjectTimeTracker
{
public sealed partial class MainPage : Page
{
public List<Project> Projects
{
get { return (List<Project>)GetValue(ProjectsProperty); }
set { SetValue(ProjectsProperty, value); }
}
public static readonly DependencyProperty ProjectsProperty =
DependencyProperty.Register(nameof(Projects), typeof(List<Project>), typeof(MainPage), new PropertyMetadata(new List<Project>()));
public MainPage()
{
this.InitializeComponent();
Projects.Add(new Project() { ProjName="Projekt1"});
list.ItemsSource = Projects;
}
}
}
The xaml looks like this:
<Page xmlns:UI="using:ProjectTimeTracker.UI"
x:Class="ProjectTimeTracker.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ProjectTimeTracker"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<ListView x:Name="list" HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Project">
<UI:ProjectViewer Project="{x:Bind }"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>

Use UserControl in ListViev UWP, Databinding Issue
I have modified your code, please refer the following,
ProjectViewer.xaml.cs
public sealed partial class ProjectViewer : UserControl
{
public ProjectViewer()
{
this.InitializeComponent();
}
public Project ProjectSource
{
get { return (Project)GetValue(ProjectSourceProperty); }
set { SetValue(ProjectSourceProperty, value); }
}
public static readonly DependencyProperty ProjectSourceProperty =
DependencyProperty.Register("ProjectSource", typeof(Project), typeof(ProjectViewer), new PropertyMetadata(0, new PropertyChangedCallback(CallBack)));
private static void CallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var current = d as ProjectViewer;
if (e.NewValue != e.OldValue)
{
current.TBName.Text = (e.NewValue as Project).ProjName;
}
}
}
public class Project
{
public string ProjName { get; set; }
public Project()
{
}
}
ProjectViewer.xaml
<RadioButton
x:Name="RBSelector"
Grid.RowSpan="2"
Margin="8,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
/>
<TextBlock
x:Name="TBName"
Grid.Row="0"
Grid.Column="1"
Width="110"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Style="{ThemeResource TitleTextBlockStyle}"
/>
<TextBlock
x:Name="TBTotalTime"
Grid.Row="1"
Grid.Column="1"
Margin="0,-4,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Style="{ThemeResource CaptionTextBlockStyle}"
Text="NYI"
/>
Usage
public sealed partial class MainPage : Page
{
public List<Project> Projects = new List<Project>();
public MainPage()
{
this.InitializeComponent();
Projects.Add(new Project() { ProjName = "Projekt1" });
list.ItemsSource = Projects;
}
public string TestString { get; set; }
}
Xaml
<ListView
x:Name="list"
HorizontalAlignment="Center"
VerticalAlignment="Center"
>
<ListView.ItemTemplate>
<DataTemplate >
<local:ProjectViewer ProjectSource="{Binding }" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Related

WPF After ICommand INotifyPropertyChanged is called, but not updating UI [MVVM]

When the View is initialized, their standard value "VM", definied in the ViewModel, is updated trough the Model and updated in the View. However, when the ICommand NavigationCommand is triggered, the code in the OnNavigationCommand method executes, and even the OnPropertyChanged (INotifyPropertyChanged) method is called in the Model. However, the textboxes in the UI still remains the same value: "VM". I have tried a lot, but can't seem to find the problem. Hope you can help.
View
<vw:View xmlns:cc="clr-namespace:HMI.CustomControl" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
x:Class="HMI.ZoneFView"
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:vw="http://inosoft.com/visiwin7"
xmlns:main="clr-namespace:HMI.Views.MainRegion"
xmlns:local="clr-namespace:HMI" xmlns:dialogregion="clr-namespace:HMI.Views.DialogRegion"
mc:Ignorable="d"
DataContext="{vw:AdapterBinding ViewModel}">
<Viewbox>
<Grid x:Name="LayoutRoot" Width="140" Height="220.5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3"/>
<ColumnDefinition Width="5*"/>
<ColumnDefinition Width="3"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="5" />
<RowDefinition Height="5*" />
<RowDefinition Height="5" />
</Grid.RowDefinitions>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding theModel.VisibilityFFUView}" Margin="57,16,7,62" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding theModel.VisibilityFFUView}" Margin="57,48,7,30" />
</Grid>
</Viewbox>
</vw:View>
View.cs
using HMI.Views.MainRegion;
using VisiWin.ApplicationFramework;
namespace HMI
{
[ExportView("ZoneFView")]
public partial class ZoneFView : VisiWin.Controls.View
{
public ZoneFView()
{
this.InitializeComponent();
}
}
}
ViewModel
using System;
using System.ComponentModel.Composition;
using System.Windows.Input;
using VisiWin.ApplicationFramework;
using VisiWin.Commands;
namespace HMI.Views.MainRegion
{
[ExportAdapter("ViewModel")]
[PartCreationPolicy(CreationPolicy.NonShared)]
class ViewModel : AdapterBase
{
#region Constructor
public Model Model { get; set; }
public ViewModel()
{
// If the VisiWin system is not in the runtime, the VW7 functionalities cannot be accessed
if (ApplicationService.IsInDesignMode)
{
return;
}
// Create the Action Commands
this.NavigationCommand = new ActionCommand(OnNavigationCommand);
Model = new Model()
{
VisibilityFFUView = "VM"
};
}
#endregion
#region CordisAdapterBase implementation
// Called when the view on which this adapter is located as DataContext is loaded
public override void OnViewAttached(IView view)
{
base.OnViewAttached(view);
}
public override void OnViewDetached(IView view)
{
base.OnViewDetached(view);
}
#endregion
#region NavigationCommand - Command from the view into the ViewModel
public ICommand NavigationCommand { get; set; }
// NavigationCommand event call
// Will be called if one of the buttons in the "AppbarView" view is clicked.
// The command of the model is linked to the button via the Command property.
private void OnNavigationCommand(object commandParameter)
{
if (commandParameter != null)
{
if (ApplicationService.IsInDesignMode) return;
string strParameter = commandParameter.ToString();
switch (strParameter)
{
case "FFU":
Model.VisibilityFFUView = "Turn on";
break;
case "FFUswitch":
Model.VisibilityFFUView = "Turn off";
break;
default:
break;
}
}
else
{
throw new ArgumentNullException(nameof(commandParameter));
}
}
#endregion
}
}
Model
using System.ComponentModel.Composition;
using VisiWin.ApplicationFramework;
namespace HMI.Views.MainRegion
{
[ExportAdapter("Model")]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class Model : ObserverableObject
{
private string _visibilityFFUView;
public string VisibilityFFUView
{
get { return _visibilityFFUView; }
set
{
_visibilityFFUView = value;
OnPropertyChanged();
}
}
}
}
ObserverableObject
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace HMI.Views.MainRegion
{
public class ObserverableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
UPDATE
I have added the NavigationView, and shifted both the DataContext of NavigationView and ZoneFView to XAML to reduce some code..
NavigationView
<vw:View xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
x:Class="HMI.Views.Common.AppbarView"
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:vw="http://inosoft.com/visiwin7" xmlns:local="clr-namespace:HMI"
mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="638"
DataContext="{vw:AdapterBinding ViewModel}">
<Grid x:Name="LayoutRoot" Background="{DynamicResource AppbarBackgroundBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="8" />
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{DynamicResource AccentBrush}" StrokeThickness="0" />
<StackPanel Grid.Column="0" Margin="10,10,0,0" VerticalAlignment="Top">
<vw:NavigationRadioButton HorizontalAlignment="Stretch" RegionName="MainRegion" ViewName="HomeView" IsChecked="True" Style="{DynamicResource AppbarNavigationRadioButtonStyle}" VerticalAlignment="Top" Height="52" Symbol="{DynamicResource appbar.tiles.nine}" LocalizableText="#Appbar.Dashboard" Margin="0,0,0,0" SymbolHorizontalAlignment="Left" />
<vw:NavigationRadioButton HorizontalAlignment="Stretch" RegionName="MainRegion" ViewName="FFUnitsView" IsChecked="false" Style="{DynamicResource AppbarNavigationRadioButtonStyle}" VerticalAlignment="Top" Height="52" Symbol="{DynamicResource appbar.interface.button}" LocalizableText="#Appbar.FFUnits" Margin="0,10,0,0" BorderThickness="1,1,0,1" CommandParameter="FFU" Command="{Binding NavigationCommand}">
</vw:NavigationRadioButton>
</StackPanel>
</Grid>
</vw:View>
Solution:
Like Clemens said I did use two instances of my DataContext. Therefore, when I did updated my property
I found this post on how to solve this problem: How can I create only one instance of a DataContext for multiple windows? Thanks for the input! Case closed.

Binding property of MainViewModel to SubView

I'm trying to figure out how to bind a property from my MainWindowViewModel to a ContentControl that is based on another View.
RelativeSource Binding seems not to work since the View is in another xaml file?
I tried with dependancy property but I didn't understand how to do this correctly I guess.
What I want to achieve here is that when I type in the TextBox of the MainWindow that all 3 views (contentcontrols) also view the updated data. It should be a demo to illustrate that in MVVM the ViewModel can change without knowledge of the Views and different Views react to it.
Sadly RelativeSource Binding and DependancyProperty didn't work for me or I missed a point.
MainWindow.xaml
<Window x:Class="MVVMDemo.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"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<TextBlock Text="MVVM Demo" HorizontalAlignment="Center" FontSize="20" FontWeight="Bold"/>
<TextBox Text="{Binding TestString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="200" Background="Black" Foreground="White" Margin="0 20 0 0"/>
<Grid Margin="0 20 0 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="300"/>
</Grid.RowDefinitions>
<ContentControl Grid.Column="0" Grid.Row="0" Content="{Binding ViewOne}"/>
<ContentControl Grid.Column="1" Grid.Row="0" Content="{Binding ViewTwo}"/>
<ContentControl Grid.Column="2" Grid.Row="0" Content="{Binding ViewThree}"/>
</Grid>
</StackPanel>
</Window>
MainWindowViewModel
public class MainWindowViewModel : ViewModelBase
{
/// <summary>
/// String to change the reaction of views
/// </summary>
private string _TestString;
public string TestString
{
get { return _TestString; }
set { _TestString = value; NotifyPropertyChanged(); }
}
private object _ViewOne { get; set; }
public object ViewOne
{
get { return _ViewOne; }
set { _ViewOne = value; NotifyPropertyChanged(); }
}
private object _ViewTwo { get; set; }
public object ViewTwo
{
get { return _ViewTwo; }
set { _ViewTwo = value; NotifyPropertyChanged(); }
}
private object _ViewThree { get; set; }
public object ViewThree
{
get { return _ViewThree; }
set { _ViewThree = value; NotifyPropertyChanged(); }
}
public MainWindowViewModel()
{
ViewOne = new ViewOne();
ViewTwo = new ViewTwo();
ViewThree = new ViewThree();
TestString = "ABC";
}
}
ViewOneViewModel
<UserControl x:Class="MVVMDemo.Views.ViewOne"
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:MVVMDemo.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="White">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=TestString, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" Foreground="Black"/>
<TextBlock Text="{Binding TestStringProperty}" Foreground="Black"/>
</StackPanel>
</Grid>
</UserControl>
ViewOneViewModel
public class ViewOneViewModel : ViewModelBase
{
// this doesn't help
public static readonly DependencyProperty TestStringProperty =
DependencyProperty.Register("TestString", typeof(string), typeof(MainWindowViewModel), new PropertyMetadata(null));
}
I believe your issue lies here:
<TextBlock Text="{Binding Path=TestString, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" Foreground="Black"/>
Binding Path=TestString should instead be Binding Path=DataContext.TestString as the RelativeSource is looking at the window now, not the window's model.

How to make a Save/Reset options page that has two sub-pages using WPF and C#

I would like to make a page that contains two sub-pages of options.
The problem I've encountered is that I can't access the objects from sub-pages of the main page. I should note that I use only DataContext to script behind.
And here is some of the code that will help you understand better what I mean:
StartPage.xaml
<Page x:Class="WpfApp.StartPage"
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:WpfApp"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="startPage">
<Grid Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<StackPanel>
<Button Content="Page 1" Command="{Binding FirstPageCommand}"/>
<Button Content="Page 2" Command="{Binding SecondPageCommand}"/>
</StackPanel>
<Frame x:Name="frame" Grid.Column="1" NavigationUIVisibility="Hidden"
Source="Page1.xaml"/>
<StackPanel Grid.Column="1" Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Content="Reset" Margin="10" Command="{Binding ResetCommand}"/>
<Button Content="Save" Margin="10" Command="{Binding SaveCommand}"/>
</StackPanel>
</Grid>
StartPage.xaml.cs
using System.Windows.Controls;
namespace WpfApp
{
/// <summary>
/// Interaction logic for StartPage.xaml
/// </summary>
public partial class StartPage : Page
{
public StartPage()
{
InitializeComponent();
DataContext = new StartPage_DataContext(this);
}
}
}
StartPage_DataContext.cs
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApp
{
public class StartPage_DataContext
{
private Page _Page;
public StartPage_DataContext(Page page)
{
_Page = page;
FirstPageCommand = new RelayCommand(() => FirstPage());
SecondPageCommand = new RelayCommand(() => SecondPage());
SaveCommand = new RelayCommand(() => Save());
ResetCommand = new RelayCommand(() => Reset());
}
private void FirstPage()
{
(_Page as StartPage).frame.NavigationService.Navigate(new Page1());
}
private void SecondPage()
{
(_Page as StartPage).frame.NavigationService.Navigate(new Page2());
}
private void Save()
{
//Here is where I need code for saving both "Page1" and "Page2" elements to Settings class.
//Exeple : Settings._firstCB = Page1.firstCB.IsCheked.Value;
// Settings._secondCB = Page2.firstCB.IsCheked.Value;
}
private void Reset()
{
//Here is where I need code for setting both "Page1" and "Page2" elements to some default values.
//Exemple : Page1.firstCB.IsCheked.Value = false;
// Page2.firstCB.IsCheked.Value = true;
}
public ICommand FirstPageCommand { get; private set; }
public ICommand SecondPageCommand { get; private set; }
public ICommand SaveCommand { get; private set; }
public ICommand ResetCommand { get; private set; }
}
}
Page1.xaml (page2 is similar the only difference is the naming of elements convention)
<Page x:Class="WpfApp.Page1"
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:WpfApp"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="Page1">
<Grid Background="White">
<StackPanel>
<CheckBox x:Name="firstCB" Content="First Combo Box"/>
<CheckBox x:Name="secondCB" Content="Second Combo Box"/>
<ComboBox x:Name="firstCombo">
<ComboBoxItem Content="First Item"/>
<ComboBoxItem Content="Second Item"/>
</ComboBox>
</StackPanel>
</Grid>
Not sure if you have view models for your sub pages,
If you do have, one way of accessing properties of those viewmodel for your check box would be as shown below.
var tt = (((_Page as StartPage).frame.NavigationService.Content as Page1).DataContext as Page1ViewModel).IsCBChecked;

WPF Updating UI using Multiple Classes

I am teaching myself... I cannot understand why the UI won't update when a second class is involved. I am missing something basic and I don't get it.
In the first Class:
I have two ObservableCollections bound to two WPF ListViews, which is bound correctly and works.
I have a Command bound to a Button to move items from one Collection to the other, which works as expected.
In the second Class (backcode) I have implemented "Drag and Drop". On Drop I try to call the same Method (which is in the first Class and is used by the Button/Command. The Command is also in the first class).
On "Drag and Drop" the items are moved from one collection to the other (confirmed with Console.Writeline), however the UI doesn't update like it does with the Button/Command.
I believe the problem is that with "Drag and Drop" I am calling the Method from another class. I thought I could do that, but I must not be doing it right?
I have included everything from 4 files (xaml, backcode, class, relayCommand) so hopefully it is easy to reproduce. Can anyone tell me why & how to get this to work???
<Window x:Class="MultipleClassDragAndDrop.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:MultipleClassDragAndDrop"
xmlns:ViewModel="clr-namespace:MultipleClassDragAndDrop.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="716" Width="500">
<Window.Resources>
<ViewModel:MultiColumnViewModel x:Key="MultiColumnViewModel"/>
</Window.Resources>
<Grid DataContext="{Binding Mode=OneWay, Source={StaticResource MultiColumnViewModel}}" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="700"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Orientation="Vertical">
<Button Content="Test Command" Command="{Binding Test_Command}"/>
</StackPanel>
<ListView Grid.Row="1" Grid.Column="0" x:Name="ListView1" Background="Black" MinWidth="165" Width="Auto" HorizontalContentAlignment="Center"
ItemsSource="{Binding ActiveJobListView1, UpdateSourceTrigger=PropertyChanged}" MouseMove="ListView1_MouseMove" >
<ListView.ItemTemplate>
<DataTemplate>
<GroupBox BorderThickness="0" Foreground="Black" FontWeight="Bold" Width="150" Background="LightPink" BorderBrush="Transparent">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" >
<TextBlock Text="{Binding JobID}" FontWeight="Bold" />
<TextBlock Text="{Binding CustomerName}" FontWeight="Bold" />
</StackPanel>
</GroupBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
<Grid Grid.Row="0" Grid.Column="2" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="700"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ListView Grid.Row="1" Grid.Column="0" x:Name="ListView2" Background="Black" MinHeight="300" MinWidth="165" Width="Auto" HorizontalContentAlignment="Center"
ItemsSource="{Binding ActiveJobListView2, UpdateSourceTrigger=PropertyChanged}"
MouseMove="ListView1_MouseMove"
AllowDrop="True" Drop="ListView2_Drop" >
<ListView.ItemTemplate>
<DataTemplate>
<GroupBox BorderThickness="0" Foreground="Black" FontWeight="Bold" Width="150" Background="LightBlue" BorderBrush="Transparent">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" >
<TextBlock Text="{Binding JobID}" FontWeight="Bold" />
<TextBlock Text="{Binding CustomerName}" FontWeight="Bold" />
</StackPanel>
</GroupBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Grid>
BackCode
using MultipleClassDragAndDrop.ViewModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
namespace MultipleClassDragAndDrop
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
MultiColumnViewModel objMultiColumnViewModel = new MultiColumnViewModel();
private void ListView1_MouseMove(object sender, MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton == MouseButtonState.Pressed)
{
int lb_itemIndex = ListView1.SelectedIndex;
// Package the data.
DataObject data = new DataObject();
data.SetData("Int", lb_itemIndex);
data.SetData("Object", this);
// Inititate the drag-and-drop operation.
DragDrop.DoDragDrop(this, data, DragDropEffects.Move);
}
}
private void ListView2_Drop(object sender, DragEventArgs e)
{
Debug.WriteLine($"\n\n{System.Reflection.MethodBase.GetCurrentMethod()}");
base.OnDrop(e);
int index = (int)e.Data.GetData("Int");
// Call A Method In A Different Class
objMultiColumnViewModel.AddAndRemove(index);
e.Handled = true;
}
}
}
My ViewModel Class
using MultipleClassDragAndDrop.ViewModel.Commands;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Windows.Input;
namespace MultipleClassDragAndDrop.ViewModel
{
public class ActiveJob : INotifyPropertyChanged
{
#region INotifyPropertyChanged
//INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
Debug.WriteLine($"NOTIFY PROPERTY CHANGED! {info}");
}
}
#endregion
public string _JobID;
public string JobID
{
get { return _JobID; }
set
{ _JobID = value; NotifyPropertyChanged("JobID"); }
}
public string _CustomerName;
public string CustomerName
{
get { return _CustomerName; }
set
{ _CustomerName = value; NotifyPropertyChanged("CustomerName"); }
}
}
public partial class MultiColumnViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged
//INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
Debug.WriteLine($"NOTIFY PROPERTY CHANGED! {info}");
}
}
#endregion
//Test Command
private ICommand _Test_Command;
public ICommand Test_Command
{
get
{
if (_Test_Command == null)
{
_Test_Command = new RelayCommand<object>(ExecuteTest_Command, CanExecuteTest_Command);
}
return _Test_Command;
}
}
public bool CanExecuteTest_Command(object parameter)
{
return true;
}
public void ExecuteTest_Command(object parameter)
{
Mouse.OverrideCursor = Cursors.Wait;
AddAndRemove(0);
Mouse.OverrideCursor = Cursors.Arrow;
}
public void AddAndRemove(int selectedIndex)
{
Debug.WriteLine($"\n\n{System.Reflection.MethodBase.GetCurrentMethod()} Index = {selectedIndex}\n");
ActiveJobListView2.Add(ActiveJobListView1[selectedIndex]);
ActiveJobListView1.RemoveAt(selectedIndex);
foreach (var item in ActiveJobListView1)
{
System.Console.WriteLine($"ActiveJobListView1: {item.JobID}, {item.CustomerName}");
}
System.Console.WriteLine($" ");
foreach (var item in ActiveJobListView2)
{
System.Console.WriteLine($"ActiveJobListView2: {item.JobID}, {item.CustomerName}");
}
}
public MultiColumnViewModel()
{
ActiveJobListView1 = new ObservableCollection<ActiveJob>();
ActiveJobListView2 = new ObservableCollection<ActiveJob>();
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB100", CustomerName = "Smith" });
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB101", CustomerName = "Jones" });
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB102", CustomerName = "Black" });
}
#region Properties
private ObservableCollection<ActiveJob> _ActiveJobListView1;
public ObservableCollection<ActiveJob> ActiveJobListView1
{
get { return _ActiveJobListView1; }
set
{
_ActiveJobListView1 = value;
NotifyPropertyChanged("ActiveJobListView1");
}
}
private ObservableCollection<ActiveJob> _ActiveJobListView2;
public ObservableCollection<ActiveJob> ActiveJobListView2
{
get { return _ActiveJobListView2; }
set
{
_ActiveJobListView2 = value;
NotifyPropertyChanged("ActiveJobListView2");
}
}
#endregion
}
}
When binding to a Collection, there are 3 kinds of ChangeNotification you need:
The Notification that informs the UI if something was added or removed from the Collection. That is the only kind of Notification ObservableCollection provides.
The Notification on the property exposing the ObservableCollection. Due to case 1 binding and the lack of a "add range", it is a bad idea to do bulk-modifications of a exposed List. Usually you create a new list and only then Expose it to the UI. In your case those would be the properties "ActiveJobListView1" and it's kind.
The Notification on every property of every type exposed in the collection. That would be "ActiveJob" in your case.
Some of those are often forgotten, with Case 2 being the most common case. I wrote a small introduction into WPF and the MVVM pattern a few years back. maybe it can help you here: https://social.msdn.microsoft.com/Forums/vstudio/en-US/b1a8bf14-4acd-4d77-9df8-bdb95b02dbe2/lets-talk-about-mvvm?forum=wpf
You have issues with different instances of the same class.
Change:
MultiColumnViewModel objMultiColumnViewModel = new MultiColumnViewModel();
To:
var objMultiColumnViewModel = this.DataContext as MultiColumnViewModel;
and it should work
EDIT:
What you are doing is strongly against MVVM principals.
EDIT-2
I had to do some modification to you code to make it work:
In your XAML:
<Window.DataContext>
<local:MultiColumnViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
In your MainWindow.cs:
public MainWindow()
{
InitializeComponent();
objMultiColumnViewModel = this.DataContext as MultiColumnViewModel;
}
private MultiColumnViewModel objMultiColumnViewModel;

Universal App XAML binding not working on UserControl when inside another UserControl

I'm currently working on a Windows Universal App. I'm still quite new to XAML in general but have had some experience with it.
The issue I'm having is around binding inside a UserControl. I've looked around and can't find an answer to my specific issue.
I have a XAML page that is linked to a ViewModel, this all works fine. Then on that page I'm using a UserControl that is basically just a panel with a header that contains some content. In the content of that panel I have another UserControl that basically just consists of a Label and TextBox.
When I bind things from my ViewModel to the ContentPanel UserControl everything works fine, it picks up my ViewModel context and binds correctly.
However, When I try to bind to the LabelledTextbox UserControl that is contained withing the ContentPanel the binding fails because it is just looking for the property that is on the ViewModel on the ContentPanel instead.
See below for the code I have
Page.xaml
<!--Page.xaml-->
<cc:ContentPanel PanelHeading="LEFT FOOT: Measurements" PanelHeadingBackground="{StaticResource OPCare.PanelHeader}">
<StackPanel>
<cc:LabelledTextbox LabelText="Malleoli Width" Text="test" />
<cc:LabelledTextbox LabelText="Met Head Width" />
</StackPanel>
</cc:ContentPanel>
ContentPanel.xaml
<!--ContentPanel UserControl-->
<UserControl
x:Class="OrthoticTabletApp.Controls.ContentPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:OrthoticTabletApp.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
x:Name="Parent">
<Grid DataContext="{Binding ElementName=Parent}">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Padding="10" Grid.Column="0" Grid.Row="0" Height="50" Background="{Binding Path=PanelHeadingBackground}">
<TextBlock Height="30" LineHeight="30" Text="{Binding Path=PanelHeading}" />
</Grid>
<Grid Padding="10" Grid.Column="0" Grid.Row="1" Background="White">
<ContentPresenter Content="{Binding Path=PanelBody}" />
</Grid>
</Grid>
</UserControl>
ContentPanel.xaml.cs
[ContentProperty(Name = "PanelBody")]
public sealed partial class ContentPanel : UserControl
{
public static readonly DependencyProperty PanelHeadingProperty = DependencyProperty.Register("PanelHeading", typeof(string), typeof(ContentPanel), new PropertyMetadata(""));
public string PanelHeading
{
get
{
return (string)GetValue(PanelHeadingProperty);
}
set
{
SetValue(PanelHeadingProperty, value);
}
}
public static readonly DependencyProperty PanelBodyProperty = DependencyProperty.Register("PanelBody", typeof(object), typeof(ContentPanel), new PropertyMetadata(null));
public object PanelBody
{
get
{
return (object)GetValue(PanelBodyProperty);
}
set
{
SetValue(PanelBodyProperty, value);
}
}
public Brush PanelHeadingBackground
{
get { return (Brush)GetValue(PanelHeadingBackgroundProperty); }
set { SetValue(PanelHeadingBackgroundProperty, value); }
}
public static readonly DependencyProperty PanelHeadingBackgroundProperty =
DependencyProperty.Register("PanelHeadingBackground", typeof(Brush), typeof(ContentPanel), new PropertyMetadata(null));
public ContentPanel()
{
this.InitializeComponent();
}
}
LabelledTextbox.xaml
<UserControl
x:Class="OrthoticTabletApp.Controls.LabelledTextbox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:OrthoticTabletApp.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="50"
d:DesignWidth="400"
x:Name="Parent">
<Grid DataContext="{Binding ElementName=Parent}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Padding="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="15" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Path=LabelText}" />
</Grid>
<Grid Grid.Column="1" Padding="10">
<TextBox Text="{Binding Path=Text}" />
</Grid>
</Grid>
</UserControl>
LabelledTextbox.xaml.cs
public sealed partial class LabelledTextbox : UserControl
{
public string LabelText
{
get { return (string)GetValue(LabelTextProperty); }
set { SetValue(LabelTextProperty, value); }
}
// Using a DependencyProperty as the backing store for LabelText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LabelTextProperty =
DependencyProperty.Register("LabelText", typeof(string), typeof(LabelledTextbox), new PropertyMetadata(null));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
// Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(LabelledTextbox), new PropertyMetadata(null));
public LabelledTextbox()
{
this.InitializeComponent();
}
}
However, When I try to bind to the LabelledTextbox UserControl that is contained withing the ContentPanel the binding fails because it is just looking for the property that is on the ViewModel on the ContentPanel instead.
If I correctly understood what you've done, some properties of your ContentPanel are bound to the data model in the page's viewmodel, and properties of your LabelledTextbox are bound to the data model in other viewmodel?
If so, you can specify the DataContext for LabelledTextbox for StackPanel which is inside the ContentPanel, just for example like this:
<cc:ContentPanel PanelHeading="{x:Bind Heading}" PanelHeadingBackground="Azure">
<StackPanel>
<StackPanel.DataContext>
<local:LabelledTextboxViewModel x:Name="VM" />
</StackPanel.DataContext>
<cc:LabelledTextbox LabelText="{x:Bind VM.Lable1}" Text="test" />
<cc:LabelledTextbox LabelText="{x:Bind VM.Lable2}" />
</StackPanel>
</local:ContentPanel>
In your page's viewmodel, you can make the data model for ContentPanel and initialize the data for example like this:
public BlankPage3ViewModel()
{
Heading = "LEFT FOOT: Measurements";
}
public string Heading { get; set; }
In the LabelledTextboxViewModel you can code for example like this:
public class LabelledTextboxViewModel
{
public LabelledTextboxViewModel()
{
Lable1 = "Malleoli Width";
Lable2 = "Met Head Width";
}
public string Lable1 { get; set; }
public string Lable2 { get; set; }
}
Usually when we follow the MVVM pattern to develop a project, the data model should not be included inside of the viewmodel, I'm here just for clear and easy delivery, the key point is that you can specify different DataContext for different controls in the same page.

Categories

Resources