I'll take a stab at what you want to do.
You're trying to dynamically change the size of an image from a selection list in another window. You're using Data Binding, so I'm going to guess that this is a MVVM project.
Firstly, being a MVVM project, ViewModels (VMs) can't directly reference UI objects like Windows however VMs can create other VMs. So we need a proxy service to launch a Selector Window using the associate VM:
public interface IViewModel
{
}
internal interface IWindowService
{
void ShowWindow(IViewModel viewModel);
}
internal class WindowService : IWindowService
{
public void ShowWindow(IViewModel viewModel)
=> (new Window
{
Content = viewModel,
SizeToContent = SizeToContent.WidthAndHeight,
WindowStartupLocation = WindowStartupLocation.CenterScreen
}).Show();
}
For this to work, we need to set the DataTemplate in the
App.XAML
:
<Application
x:Class="WpfListImageSizer.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfListImageSizer"
xmlns:vw="clr-namespace:WpfListImageSizer"
StartupUri="MainWindow.xaml">
<Application.Resources>
<DataTemplate DataType="{x:Type vm:SizeSelectorViewModel}">
<vw:SelectorView/>
</DataTemplate>
</Application.Resources>
</Application>
The DataTemplate links the
SelectorView
to the
SizeSelectorViewModel
.
Next, as we are using Data Binding, we need to implement the
INotifyPropertyChanged
event handler for the data model and the VMs. So it is easier to create a base class that implements it:
public abstract class ObservableBase : INotifyPropertyChanged
{
public void Set<TValue>(ref TValue field, TValue newValue, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<TValue>.Default.Equals(field, default(TValue)) || !field.Equals(newValue))
{
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public bool IsInDesignMode => DesignerProperties.GetIsInDesignMode(new DependencyObject());
}
I've also added the
IsInDesignMode
into the base class. This is used to prevent the VS (Visual Studio) Designer from executing specific code in our project - VMs in this case.
Now we need our data models - one for the Image Size selection, and another for our dummy data to test with:
public class ImageSizeModel : ObservableBase
{
private string title;
public string Title
{
get => title;
set => Set(ref title, value);
}
private double width;
public double Width
{
get => width;
set => Set(ref width, value);
}
private double height;
public double Height
{
get => height;
set => Set(ref height, value);
}
}
public class PersonModel : ObservableBase
{
private string firstname;
public string Firstname
{
get => firstname;
set => Set(ref firstname, value);
}
private string lastname;
public string Lastname
{
get => lastname;
set => Set(ref lastname, value);
}
private string photoUrl;
public string PhotoUrl
{
get => photoUrl;
set => Set(ref photoUrl, value);
}
}
Now for our view models. First, we need the Selector VM:
public class SizeSelectorViewModel : ObservableBase, IViewModel
{
public SizeSelectorViewModel()
{
}
public SizeSelectorViewModel(ObservableCollection<ImageSizeModel> availableSizes, ImageSizeModel model = null)
{
Models = availableSizes;
SelectedModel = model ?? (Models[0]);
}
private ImageSizeModel selectedModel;
public ImageSizeModel SelectedModel
{
get => selectedModel;
set => Set(ref selectedModel, value);
}
public ObservableCollection<ImageSizeModel> Models { get; }
= new ObservableCollection<ImageSizeModel>();
}
And the View for the Selector VM:
<UserControl
x:Class="WpfListImageSizer.SelectorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:m="clr-namespace:WpfListImageSizer"
Height="300" Width="200">
<UserControl.Resources>
<DataTemplate DataType="{x:Type m:ImageSizeModel}">
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{1} W x {2} H - ({0})">
<Binding Path="Title" />
<Binding Path="Width" />
<Binding Path="Height" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource="{Binding Models}"
SelectedItem="{Binding SelectedModel, Mode=TwoWay}"/>
</Grid>
</UserControl>
And now for the VM for the MainWindow:
public class MainViewModel : ObservableBase, IViewModel
{
public MainViewModel()
{
if (IsInDesignMode) return;
ImageSize = sizes[1];
selector = new SizeSelectorViewModel(sizes, ImageSize);
selector.PropertyChanged += Selector_PropertyChanged;
windowService.ShowWindow(selector);
}
private void Selector_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(SizeSelectorViewModel.SelectedModel))
ImageSize = selector.SelectedModel;
}
private SizeSelectorViewModel selector;
private IWindowService windowService = new WindowService();
private readonly ObservableCollection<ImageSizeModel> sizes
= new ObservableCollection<ImageSizeModel>
{
new ImageSizeModel{ Width = 100, Height = 160, Title = "Small" },
new ImageSizeModel{ Width = 140, Height = 220, Title = "Medium" },
new ImageSizeModel{ Width = 180, Height = 280, Title = "Large" },
};
private ImageSizeModel imageSize;
public ImageSizeModel ImageSize
{
get => imageSize;
set => Set(ref imageSize, value);
}
public ObservableCollection<PersonModel> Staff { get; }
= new ObservableCollection<PersonModel>
{
new PersonModel
{
Firstname = "Johnny", Lastname = "Appleseed",
PhotoUrl="https://upload.wikimedia.org/wikipedia/commons/thumb/4/44/Dunn_Official_Headshot.jpg/220px-Dunn_Official_Headshot.jpg"
},
new PersonModel
{
Firstname = "Fred", Lastname = "Smith",
PhotoUrl = "http://vickmark.com/wp-content/uploads/2012/10/headshot-aon-g-web.jpg"
},
new PersonModel
{
Firstname = "Jose", Lastname = "Cordero",
PhotoUrl = "http://news.uga.edu/media/images/Jose-Cordero-230x348.jpg"
},
new PersonModel
{
Firstname = "Rod", Lastname = "Goodman",
PhotoUrl = "http://www.rodgoodmanphoto.com/img/s/v-3/p1313838186-3.jpg"
}
};
}
And the MainWindow itself:
<Window
x:Class="WpfListImageSizer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfListImageSizer"
xmlns:m="clr-namespace:WpfListImageSizer"
Title="MainWindow" Height="350" Width="525" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<Style x:Key="NameStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="FontWeight" Value="Light"/>
<Setter Property="FontSize" Value="32"/>
<Setter Property="Margin" Value="0 0 0 10"/>
</Style>
<Style x:Key="DetailsStyle" TargetType="{x:Type StackPanel}">
<Setter Property="Margin" Value="20 0 0 0"/>
<Setter Property="Grid.Column" Value="1"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style x:Key="FrameStyle" TargetType="{x:Type Border}">
<Setter Property="Margin" Value="10"/>
<Setter Property="Padding" Value="10"/>
<Setter Property="Background" Value="GhostWhite"/>
</Style>
<Style x:Key="PhotoStyle" TargetType="{x:Type Image}">
<Setter Property="Width" Value="{Binding ElementName=Host, Path=DataContext.ImageSize.Width}"/>
<Setter Property="Height" Value="{Binding ElementName=Host, Path=DataContext.ImageSize.Height}"/>
</Style>
<DataTemplate DataType="{x:Type m:PersonModel}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Style="{StaticResource FrameStyle}">
<Image Source="{Binding PhotoUrl}" Style="{StaticResource PhotoStyle}"/>
</Border>
<StackPanel Style="{StaticResource DetailsStyle}">
<TextBlock Text="{Binding Firstname}" Style="{StaticResource NameStyle}"/>
<TextBlock Text="{Binding Lastname}" Style="{StaticResource NameStyle}"/>
</StackPanel>
</Grid>
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</Window.Resources>
<Grid>
<ListBox x:Name="Host" ItemsSource="{Binding Staff}"
VirtualizingPanel.ScrollUnit="Pixel"/>
</Grid>
</Window>
Right, all the code is out of the way.
How it works:
1. MainViewModel preps the image size data and the default size. It then creates a Selector VM and passes the data to it and listens for any changes. We then pass the SElector VM to the Windows Service. The Windows service creates a new window, sets the DataContext to the Selector VM.
2. As we have defined in the App.Xaml the association of the VM with the VM, the Window's Content is loaded with the associated View, the Listbox is filled, and the default selection is made.
3. When a selection is made in the Selector Window, the data bound SelectedModel is changed, the PropertyChanged event is triggered, the MainViewModel catches the event, updates the local image size property and the Xaml binding is updated. The Style for the Image is bound to the MainViewModel and updates the UI.
Whew, Task accomplished... Enjoy! :)
PS: I have not put any error handling into the code. I leave that to you! ;)