Reputation: 1
I have a problem with XAML binding:
I have a Textbox, with binding, but when I print out my value, which is bound to the Text of the Textbox, it is an empty string.
What I've tried:
I've tried different UpdateSourceTriggers, and looked around Stackoverflow.
(BTW, I am relatively new to MVVM, this is my first bigger project)
My SearchView.xaml
<UserControl x:Class="MovieDatabase.MVVM.View.SearchView"
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:MovieDatabase.MVVM.View"
xmlns:behaviors="http://schemas.microsoft.com/xaml/behaviors"
xmlns:viewmodel="clr-namespace:MovieDatabase.MVVM.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
KeyUp="SearchMovie">
<UserControl.DataContext>
<viewmodel:SearchViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="600"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Top"
HorizontalAlignment="Stretch">
<TextBox Style="{StaticResource ModernSearchBar}"
Text="{Binding Path=SearchContent, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextChanged="TextBox_TextChanged"/>
<TextBlock Margin="10"
Text="Search for: "
Foreground="White"
FontSize="16"/>
<ComboBox Style="{StaticResource FlatCombobox}"
ItemsSource="{Binding SearchList}"
SelectedIndex="{Binding Path=SelectedSearchIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="100"/>
</StackPanel>
(My Problem is related to the first Textbox)
My SearchView.xaml.cs
using MovieDatabase.MVVM.ViewModel;
using System.Windows.Controls;
using System.Windows.Input;
namespace MovieDatabase.MVVM.View
{
public partial class SearchView : UserControl
{
public static SearchView Instance { get; set; }
public SearchView()
{
InitializeComponent();
DataContext = MainViewModel.SearchVM;
Instance = this;
}
private void SearchMovie(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
MainViewModel.SearchVM.Search();
}
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox textBox = sender as TextBox;
textBox.GetBindingExpression(TextBox.TextProperty).UpdateSource(); // Doesn't seem to help
}
}
}
My SearchViewModel.cs (not complete, the complete class would be too much)
using MovieDatabase.Core;
using MovieDatabase.MovieSpace;
using System.Collections.Generic;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows;
using MovieDatabase.Util;
using System.Windows.Media.Imaging;
using System;
namespace MovieDatabase.MVVM.ViewModel
{
public class SearchViewModel : ObservableObject
{
private string _searchContent;
public string SearchContent
{
get { return _searchContent; }
set
{
_searchContent = value;
OnPropertyChanged(nameof(SearchContent));
}
}
// When this method is called, SearchContent is ""
public void Search()
{
List<Movie> movies = new List<Movie>();
MessageBox.Show(SearchContent);
MainViewModel.logger.Log(Levels.INFO, $"User searching for {SearchContent} in Category {SearchList[SelectedSearchIndex]}");
if (SearchList[SelectedSearchIndex] == SearchList[0])
{
foreach (Movie movie in Movie.AllMovies)
{
if (movie.Info.title.Contains(SearchContent))
{
movies.Add(movie);
}
}
} else if (SearchList[SelectedSearchIndex] == SearchList[1])
{
foreach (Movie movie in Movie.AllMovies)
{
if (movie.Info.stars.Contains(SearchContent))
{
movies.Add(movie);
}
}
} else if (SearchList[SelectedSearchIndex] == SearchList[2])
{
foreach (Movie movie in Movie.AllMovies)
{
if (movie.Info.year == SearchContent)
{
movies.Add(movie);
}
}
} else if (SearchList[SelectedSearchIndex] == SearchList[3])
{
foreach (Movie movie in Movie.AllMovies)
{
if (movie.Info.genres.Contains(SearchContent))
{
movies.Add(movie);
}
}
}
if (movies.Count == 0)
{
return;
}
foreach (Movie mov in movies)
{
AddMovieToView(mov.Info.title);
}
}
}
}
My Observable Object Class
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace MovieDatabase.Core
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
Upvotes: 0
Views: 445
Reputation: 7908
You showed the incomplete code of the MainViewModel class.
It misses a very essential detail: the implementation of the SearchVM property.
The most obvious reason for the problem is that you create a new instance of the MainViewModel class in XAML.
And in Code Behind you refer to another instance of this class stored in the MainViewModel.SearchVM property.
Without more complete code, it's hard to tell exactly how to fix it. Try the following changes.
namespace MovieDatabase.MVVM.ViewModel
{
public class SearchViewModel : ObservableObject
{
publis static SearchViewModel Instance {get;}
= new SearchViewModel ();
// It's better to hide the constructor
private SearchViewModel()
{
// Some code
}
namespace MovieDatabase.MVVM.View
{
public partial class SearchView : UserControl
{
public static SearchView Instance { get;} = new SearchView();
private SearchView()
{
InitializeComponent();
// DataContext = MainViewModel.SearchVM;
// Instance = this;
}
private void SearchMovie(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
MainViewModel.Instance.Search();
}
}
// private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
// {
// TextBox textBox = sender as TextBox;
// textBox.GetBindingExpression(TextBox.TextProperty).UpdateSource(); // Doesn't seem to help
// }
}
}
<UserControl x:Class="MovieDatabase.MVVM.View.SearchView"
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:MovieDatabase.MVVM.View"
xmlns:behaviors="http://schemas.microsoft.com/xaml/behaviors"
xmlns:viewmodel="clr-namespace:MovieDatabase.MVVM.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
KeyUp="SearchMovie"
DataContext="{x:Static viewmodel:SearchViewModel.Instance}">
<!--<UserControl.DataContext>
<viewmodel:SearchViewModel/>
</UserControl.DataContext>-->
Unfortunately, this didn't help.
There are a lot of mistakes in the project.
Partially corrected them.
But even this partial fix is too big to post here.
Send me "invite a collaborator" and I will commit these changes to your Repository.
The main mistake is the incorrect setting of the template for the TextBox.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="{x:Type TextBox}"
x:Key="ModernSearchBar">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="5"/>
<Setter Property="Foreground" Value="LightGray"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border CornerRadius="10"
Background="#353340"
Width="200"
Height="40">
<Grid>
<Rectangle StrokeThickness="1"/>
<!--Instead of giving the TextBox a place to display its text,
you've overlaid another TextBox on top of it.
And property values must be set in Style Setters.-->
<!--<TextBox Margin="1"
Text="{TemplateBinding Text}"
BorderThickness="0"
Background="Transparent"
VerticalContentAlignment="Center"
Padding="5"
Foreground="#CFCFCF"
x:Name="SearchBar"/>-->
<!--This element is required in the TextBox template.
It is he who is looked for by the TextBox logic to display the text.-->
<ScrollViewer Margin="0" x:Name="PART_ContentHost" Padding="5"/>
<TextBlock IsHitTestVisible="False"
Text="Search"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="10,0,0,0"
FontSize="11"
Foreground="DarkGray"
Grid.Column="1">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Text.IsEmpty, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TextBox}}"
Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Hidden"/>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Upvotes: 1