#c# #wpf
#c# #wpf
Вопрос:
У меня есть приложение WPF, в котором есть определенные элементы listview.Как и на изображении, есть 3 элемента: A, B, C. изначально, когда пользователь не выбирает ни одного элемента, текстовое поле будет скрыто.
Элементы ListView, когда текстовое поле скрыто
Когда пользователь нажимает на любой элемент, я хочу, чтобы отображалось текстовое поле с описанием этого элемента.
Текстовое поле отображается при выборе элементов из listview
Комментарии:
1. Смотрите Привязку к коллекциям , раздел Мастер-Подробный сценарий привязки .
Ответ №1:
Попробуйте это в коде (MainWindow.xaml.cs):
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfApp7
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MyViewModel m_MyViewModel;
public MainWindow()
{
InitializeComponent();
m_MyViewModel = new MyViewModel();
myGrid.DataContext = MyVM;
}
public MyViewModel MyVM
{
get
{
return m_MyViewModel;
}
}
}
public class MyViewModel : ViewModelBase
{
public List<string> MyCollection
{
get
{
return new List<string> { "A", "B", "C" };
}
}
private bool isListViewItemSelected;
public bool IsListViewItemSelected
{
get
{
return isListViewItemSelected;
}
set
{
isListViewItemSelected = value;
RaisePropertyChanged("IsListViewItemSelected");
}
}
private string selectedItem;
public string SelectedItem
{
get { return selectedItem; }
set
{
if (value != selectedItem)
{
selectedItem = value;
if (selectedItem == null)
{
IsListViewItemSelected = false;
}
else
{
IsListViewItemSelected = true;
}
RaisePropertyChanged("SelectedItem");
RaisePropertyChanged("SelectedTxtString");
}
}
}
public string SelectedTxtString
{
get
{
//return SelectedItem;
return """ SelectedItem "" is selected!";
}
}
}
public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
{
#region DisplayName
/// <summary>
/// Returns the user-friendly name of this object.
/// Child classes can set this property to a new value,
/// or override it to determine the value on-demand.
/// </summary>
public virtual string DisplayName { get; protected set; }
#endregion // DisplayName
#region Debugging Aides
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
#endregion // Debugging Aides
#region INotifyPropertyChanged Members
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
protected virtual void RaisePropertyChanged(string propertyName)
{
VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion // INotifyPropertyChanged Members
#region IDisposable Members
/// <summary>
/// Invoked when this object is being removed from the application
/// and will be subject to garbage collection.
/// </summary>
public void Dispose()
{
this.OnDispose();
}
/// <summary>
/// Child classes can override this method to perform
/// clean-up logic, such as removing event handlers.
/// </summary>
protected virtual void OnDispose()
{
}
#endregion // IDisposable Members
}
}
И это в MainWindow.xaml
<Window x:Class="WpfApp7.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:WpfApp7"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<Grid x:Name="myGrid">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ListView ItemsSource="{Binding MyCollection, Mode=OneWay}" SelectedItem="{Binding SelectedItem}">
</ListView>
<TextBox Grid.Row="1" Text="{Binding SelectedTxtString, Mode=OneWay}"
Visibility="{Binding IsListViewItemSelected, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</Grid>
Комментарии:
1. Обратите внимание, что для вашего
IsListViewItemSelected
значения всегда установлено значениеtrue
, независимо от того,SelectedItem
равно null или нет. Кроме того, установкаUpdateSourceTrigger=PropertyChanged
односторонней привязки (например, привязки текста и видимости текстового поля в вашем примере) бессмысленна. UpdateSourceTrigger влияет только на привязки TwoWay к OneWayToSource и не имеет ничего общего с событием PropertyChanged.2. Также бессмысленно самостоятельно писать BoolToVisibilityConverter. В фреймворке уже есть одно.
3. Я написал это так, потому что в вопросе не было отмены выбора всех задействованных элементов ListViewItems… Но вы, конечно, правы. Должна быть проверка на то, что SelectedItem равен null…
4. О, я не знал, что в .NET есть реализация этого конвертера… Спасибо за совет!
5. И спасибо за комментарии по поводу UpdateSourceTrigger! Они действительно помогают мне понять процесс привязки!
Ответ №2:
Используйте привязку данных, чтобы привязать флаг видимости текстового поля к текущему выбранному элементу ListView.
В вашем контроллере или ViewModel реализуйте свойство для выбранного элемента
public object SelectedItem { get; set; }
Привяжите его в ListView к свойству SelectedItem
<ListView
...
SelectedItem={Bining Path=SelectedItem}/>
Используйте второе свойство, чтобы определить, выбран ли элемент ListView
public Visibility TextBoxVisibility=> SelectedItem != null
? System.Windows.Visibility.Visible
: System.Windows.Visibility.Hidden;
В вашем xaml привяжите свойство видимости к свойству IsSelected
<TextBox
...
Visibility={Bining Path=TextBoxVisibility}/>
надеюсь, это поможет.
Комментарии:
1. Не забудьте отправить уведомление об изменении свойства для свойств SelectedItem модели представления и TextBoxVisibility. Также подумайте о том, чтобы вообще не использовать дополнительное свойство, а просто привязать видимость текстового поля также к SelectedItem с помощью конвертера привязки или, альтернативно, иметь стиль с DataTrigger для SelectedItem, который задает видимость.
2. да, верно. Внутри свойства, конечно, должно быть OnPropertyChanged(nameof(SelectedItem)), иначе пользовательский интерфейс не будет знать, когда изменилось значение
3. Не только
OnPropertyChanged(nameof(SelectedItem))
но такжеOnPropertyChanged(nameof(TextBoxVisibility))
, потому что видимость TextBoxVisibility должна быть переоценена при изменении SelectedItem.4. Я могу показать описание в текстовом поле, но при этом всегда остается место для текстового поля. Текстовое поле не скрыто, скрыто только содержимое внутри поля. Я также хочу скрыть текстовое поле, и оно должно быть видно только тогда, когда пользователь нажимает на элемент. Не могли бы вы, пожалуйста, помочь с этим.