WPF потерял привязку данных - PullRequest
1 голос
/ 13 марта 2009

Я новичок в WPF и его привязке к данным, но наткнулся на странное поведение, которое не смогу разрешить для себя.

В диалоге у меня есть список с пользователями и текстовое поле для имени пользователя. Оба связаны с UserLogonLogic, который публикует среди прочего свойство CurrentUser.

Я хочу, чтобы TextBox обновлял свой текст, когда я нажимаю на имя в ListBox. Я также хочу, чтобы SelectedItem в ListBox обновлялся, когда я вводил имя пользователя прямо в TextBox. Частичные имена в TextBox будут преобразованы в первое совпадающее значение в списке или пустое значение, если их нет.

Сначала TextBox обновляется каждый раз, когда я нажимаю на ListBox. Отладка показывает мне, что каждый раз при запуске PropertyChangeEvent для CurrentUser вызывается метод txtName_TextChanged. Только после того, как я что-то ввел в текстовое поле, DataBinding TextBox, похоже, теряется. Больше не будет никаких обновлений TextBox, когда я нажму на ListBox. Отладка теперь показывает мне, что метод txtName_TextChanged больше не вызывается после запуска CurrentUser PropertyChangeEvent.

У кого-нибудь есть идеи, где я мог ошибиться?

Большое спасибо

UserLogon.xaml:

    <ListBox Grid.Column="0" Grid.Row="1" Grid.RowSpan="4" MinWidth="100" Margin="5" Name="lstUser" MouseUp="lstUser_MouseUp"
             ItemsSource="{Binding Path=Users}" SelectedItem="{Binding Path=CurrentUser, Mode=TwoWay}"/>
    <TextBox Grid.Column="1" Grid.Row="1" Margin="3" Name="txtName" TextChanged="txtName_TextChanged"
             Text="{Binding Path=CurrentUser, Mode=OneWay}" />

UserLogon.xaml.cs:

    public UserLogon()
    {
        InitializeComponent();

        _logic = new UserLogonLogic();
        TopLevelContainer.DataContext = _logic;
    }

    private int _internalChange = 0;
    private void txtName_TextChanged(object sender, TextChangedEventArgs e)
    {
        if (_internalChange > 0)
        {
            return;
        }

        _internalChange++;
        string oldName = txtName.Text;
        User user = _logic.SelectByPartialUserName(oldName);
        string newName = (user == null) ? "" : user.Name;

        if (oldName != newName)
        {
            txtName.Text = (newName == "") ? oldName : newName;
            txtName.Select(oldName.Length, newName.Length);
        }
        _internalChange--;
    }

UserLogon.Logic.cs:

public class UserLogonLogic : INotifyPropertyChanged
{
    private User _currentUser;
    public User CurrentUser
    {
        get { return _currentUser; }
        set
        {
            if (value != CurrentUser)
            {
                _currentUser = value;
                OnPropertyChanged("CurrentUser");
            }
        }

    private IEnumerable<User> _users;
    public IEnumerable<User> Users
    {
        get
        {
            if (_users == null)
            {
                List<User> _users = Database.GetAllUsers();
            }
            return _users;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(string prop)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }
    }

    public User SelectByPartialUserName(string value)
    {
        if (value != "")
        {
            IEnumerable<User> allUser = GetAllUserByName(value);
            if (allUser.Count() > 0)
            {
                CurrentUser = allUser.First();
            }
            else
            {
                CurrentUser = null;
            }
        }
        else
        {
            CurrentUser = null;
        }

        return CurrentUser;
    }

    private IEnumerable<User> GetAllUserByName(string name)
    {
        return from user in Users
               where user.Name.ToLower().StartsWith(name.ToLower())
               select user;
    }
}

Ответы [ 2 ]

7 голосов
/ 13 марта 2009

Это работа для хорошей модели. Определите два свойства для вашей модели представления:

  • SelectedUser : User
  • UserEntry : string

Свяжите ListBox SelectedItem со свойством SelectedUser, а TextBox Text со свойством UserEntry. Затем в вашей модели представления вы можете выполнить синхронизацию: - если SelectedUser изменяется, установите UserEntry для этого пользователя Name - если UserEntry изменяется, выполните интеллектуальный поиск по всем пользователям и установите для SelectedUser либо null, если совпадений не найдено, либо первое совпадение User

Вот полный и рабочий образец. Хотел бы я сейчас легко прикрепить zip-файл.

Сначала ViewModel.cs :

public abstract class ViewModel : INotifyPropertyChanged
{
    private readonly Dispatcher _dispatcher;

    protected ViewModel()
    {
        if (Application.Current != null)
        {
            _dispatcher = Application.Current.Dispatcher;
        }
        else
        {
            _dispatcher = Dispatcher.CurrentDispatcher;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected Dispatcher Dispatcher
    {
        get { return _dispatcher; }
    }

    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        var handler = PropertyChanged;

        if (handler != null)
        {
            handler(this, e);
        }
    }

    protected void OnPropertyChanged(string propertyName)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }
}

User.cs

public class User : ViewModel
{
    private readonly string _name;

    public User(string name)
    {
        _name = name;
    }

    public string Name
    {
        get { return _name; }
    }
}

LogonViewModel.cs

public class LogonViewModel : ViewModel
{
    private readonly ICollection<User> _users;
    private User _selectedUser;
    private string _userEntry;

    public LogonViewModel()
    {
        _users = new List<User>();
        //fake data
        _users.Add(new User("Kent"));
        _users.Add(new User("Tempany"));
    }

    public ICollection<User> Users
    {
        get { return _users; }
    }

    public User SelectedUser
    {
        get { return _selectedUser; }
        set
        {
            if (_selectedUser != value)
            {
                _selectedUser = value;
                OnPropertyChanged("SelectedUser");
                UserEntry = value == null ? null : value.Name;
            }
        }
    }

    public string UserEntry
    {
        get { return _userEntry; }
        set
        {
            if (_userEntry != value)
            {
                _userEntry = value;
                OnPropertyChanged("UserEntry");
                DoSearch();
            }
        }
    }

    private void DoSearch()
    {
        //do whatever fuzzy logic you want here - I'm just doing a simple match
        SelectedUser = Users.FirstOrDefault(user => user.Name.StartsWith(UserEntry, StringComparison.OrdinalIgnoreCase));
    }
}

UserLogon.xaml

<UserControl x:Class="WpfApplication1.UserLogon"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="300" Width="300">
    <StackPanel>
        <ListBox ItemsSource="{Binding Users}" SelectedItem="{Binding SelectedUser}" DisplayMemberPath="Name"/>
        <TextBox Text="{Binding UserEntry, UpdateSourceTrigger=PropertyChanged}"/>
    </StackPanel>
</UserControl>

UserLogon.xaml.cs

public partial class UserLogon : UserControl
{
    public UserLogon()
    {
        InitializeComponent();
        //would normally map view model to view with a DataTemplate, not manually like this
        DataContext = new LogonViewModel();
    }
}
1 голос
/ 13 марта 2009

разве ваше текстовое поле не должно иметь привязку twoway?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...