Forçar uma dica de ferramenta WPF a permanecer na tela

119

Tenho uma dica de ferramenta para um rótulo e quero que fique aberto até que o usuário mova o mouse para um controle diferente.

Tentei as seguintes propriedades na dica de ferramenta:

StaysOpen="True"

e

ToolTipService.ShowDuration = "60000"

Mas, em ambos os casos, a dica de ferramenta é exibida apenas por exatamente 5 segundos.

Por que esses valores estão sendo ignorados?

TimothyP
fonte
Existe um valor máximo imposto em algum lugar para a ShowDurationpropriedade, acho que é algo parecido 30,000. Qualquer coisa maior do que isso e o padrão voltará a ser 5000.
Dennis
2
@Dennis: Testei isso com o WPF 3.5 e ToolTipService.ShowDuration="60000"funcionou. Ele não voltou para o padrão 5000.
M. Dudley
@emddudley: A dica de ferramenta realmente permanece aberta por 60000ms? Você pode definir a ToolTipService.ShowDurationpropriedade para qualquer valor> = 0 (para Int32.MaxValue), no entanto, a dica de ferramenta não permanecerá aberta por esse comprimento.
Dennis
2
@Dennis: Sim, ficou aberto por exatamente 60 segundos. Isso está no Windows 7.
M. Dudley
@emddudley: Essa pode ser a diferença. Isso era conhecimento de quando eu estava desenvolvendo no Windows XP.
Dennis

Respostas:

113

Basta colocar este código na seção de inicialização.

ToolTipService.ShowDurationProperty.OverrideMetadata(
    typeof(DependencyObject), new FrameworkPropertyMetadata(Int32.MaxValue));
John Whiter
fonte
Essa foi a única solução que funcionou para mim. Como você pode adaptar este código para definir a propriedade Placement como Top? new FrameworkPropertyMetadata("Top")não funciona.
InvalidBrainException
6
Eu marquei isso (depois de quase 6 anos anos, desculpe) como a resposta correta porque isso realmente funciona em todas as versões suportadas do Windows e o mantém aberto por 49 dias, que deve ser longo o suficiente: p
TimothyP
1
Eu também coloquei isso no meu evento Window_Loaded e funciona muito bem. A única coisa que você precisa fazer é certificar-se de se livrar de qualquer "ToolTipService.ShowDuration" que você definiu em seu XAML. As durações definidas em XAML substituirão o comportamento que este código está tentando alcançar. Agradecimentos a John Whiter por fornecer a solução.
nicko
1
FWIW, prefiro este precisamente porque é global - quero que todas as dicas de ferramentas em meu aplicativo persistam por mais tempo sem mais alarde. Fazer isso ainda permite que você aplique seletivamente um valor menor em locais específicos do contexto, também, exatamente como na outra resposta. (Mas, como sempre, isso só é válido se você for o aplicativo - se estiver escrevendo uma biblioteca de controle ou outra coisa, então você só deve usar soluções específicas do contexto; o estado global não é seu para brincar.)
Miral
1
Isso pode ser perigoso! Wpf usa internamente TimeSpan.FromMilliseconds () ao definir o intervalo do cronômetro que faz cálculos duplos. Isso significa que quando o valor é aplicado ao cronômetro usando a propriedade Interval, você pode obter ArgumentOutOfRangeException.
janeiro
190

TooltipService.ShowDuration funciona, mas você deve defini-lo no objeto que tem a dica de ferramenta, assim:

<Label ToolTipService.ShowDuration="12000" Name="lblShowTooltip" Content="Shows tooltip">
    <Label.ToolTip>
        <ToolTip>
            <TextBlock>Hello world!</TextBlock>
        </ToolTip>
    </Label.ToolTip>
</Label>

Eu diria que esse design foi escolhido porque permite a mesma dica de ferramenta com diferentes tempos limite em controles diferentes.

Martin Konicek
fonte
4
Ele também permite que você especifique o conteúdo do ToolTipdiretamente, sem explícito <ToolTip>, o que pode tornar a vinculação mais simples.
svick
15
Esta deve ser a resposta escolhida, pois é específica ao contexto e não global.
Vlad
8
A duração é em milissegundos. O padrão é 5000. O código acima especifica 12 segundos.
Contango
1
Se você usar a mesma instância de dica de ferramenta com vários controles, mais cedo ou mais tarde obterá uma exceção "filho já visual de um pai diferente".
springy76
1
O principal motivo pelo qual essa deve ser a resposta correta é que está no espírito da programação de alto nível real, diretamente no código XAML e fácil de perceber. A outra solução é meio hacky e prolixo, além do ponto que é global. Aposto que a maioria das pessoas que usaram isso se esqueceu de como faziam em uma semana.
j riv
15

Isso também estava me deixando louco esta noite. Criei uma ToolTipsubclasse para lidar com o problema. Para mim, no .NET 4.0, a ToolTip.StaysOpenpropriedade não fica "realmente" aberta.

Na classe abaixo, use a nova propriedade ToolTipEx.IsReallyOpen, em vez de propriedade ToolTip.IsOpen. Você obterá o controle que deseja. Por meio da Debug.Print()chamada, você pode observar na janela Saída do depurador quantas vezes this.IsOpen = falseé chamado! Tanto para StaysOpen, ou devo dizer "StaysOpen"? Aproveitar.

public class ToolTipEx : ToolTip
{
    static ToolTipEx()
    {
        IsReallyOpenProperty =
            DependencyProperty.Register(
                "IsReallyOpen",
                typeof(bool),
                typeof(ToolTipEx),
                new FrameworkPropertyMetadata(
                    defaultValue: false,
                    flags: FrameworkPropertyMetadataOptions.None,
                    propertyChangedCallback: StaticOnIsReallyOpenedChanged));
    }

    public static readonly DependencyProperty IsReallyOpenProperty;

    protected static void StaticOnIsReallyOpenedChanged(
        DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        ToolTipEx self = (ToolTipEx)o;
        self.OnIsReallyOpenedChanged((bool)e.OldValue, (bool)e.NewValue);
    }

    protected void OnIsReallyOpenedChanged(bool oldValue, bool newValue)
    {
        this.IsOpen = newValue;
    }

    public bool IsReallyOpen
    {
        get
        {
            bool b = (bool)this.GetValue(IsReallyOpenProperty);
            return b;
        }
        set { this.SetValue(IsReallyOpenProperty, value); }
    }

    protected override void OnClosed(RoutedEventArgs e)
    {
        System.Diagnostics.Debug.Print(String.Format(
            "OnClosed: IsReallyOpen: {0}, StaysOpen: {1}", this.IsReallyOpen, this.StaysOpen));
        if (this.IsReallyOpen && this.StaysOpen)
        {
            e.Handled = true;
            // We cannot set this.IsOpen directly here.  Instead, send an event asynchronously.
            // DispatcherPriority.Send is the highest priority possible.
            Dispatcher.CurrentDispatcher.BeginInvoke(
                (Action)(() => this.IsOpen = true),
                DispatcherPriority.Send);
        }
        else
        {
            base.OnClosed(e);
        }
    }
}

Pequeno discurso retórico: por que a Microsoft não tornou as DependencyPropertypropriedades (getters / setters) virtuais para que possamos aceitar / rejeitar / ajustar as alterações nas subclasses? Ou fazer um virtual OnXYZPropertyChangedpara cada um DependencyProperty? Ugh.

---Editar---

Minha solução acima parece estranha no editor XAML - a dica de ferramenta está sempre aparecendo, bloqueando algum texto no Visual Studio!

Esta é a melhor maneira de resolver este problema:

Algum XAML:

<!-- Need to add this at top of your XAML file:
     xmlns:System="clr-namespace:System;assembly=mscorlib"
-->
<ToolTip StaysOpen="True" Placement="Bottom" HorizontalOffset="10"
        ToolTipService.InitialShowDelay="0" ToolTipService.BetweenShowDelay="0"
        ToolTipService.ShowDuration="{x:Static Member=System:Int32.MaxValue}"
>This is my tooltip text.</ToolTip>

Algum código:

// Alternatively, you can attach an event listener to FrameworkElement.Loaded
public override void OnApplyTemplate()
{
    base.OnApplyTemplate();

    // Be gentle here: If someone creates a (future) subclass or changes your control template,
    // you might not have tooltip anymore.
    ToolTip toolTip = this.ToolTip as ToolTip;
    if (null != toolTip)
    {
        // If I don't set this explicitly, placement is strange.
        toolTip.PlacementTarget = this;
        toolTip.Closed += new RoutedEventHandler(OnToolTipClosed);
    }
}

protected void OnToolTipClosed(object sender, RoutedEventArgs e)
{
    // You may want to add additional focus-related tests here.
    if (this.IsKeyboardFocusWithin)
    {
        // We cannot set this.IsOpen directly here.  Instead, send an event asynchronously.
        // DispatcherPriority.Send is the highest priority possible.
        Dispatcher.CurrentDispatcher.BeginInvoke(
            (Action)delegate
                {
                    // Again: Be gentle when using this.ToolTip.
                    ToolTip toolTip = this.ToolTip as ToolTip;
                    if (null != toolTip)
                    {
                        toolTip.IsOpen = true;
                    }
                },
            DispatcherPriority.Send);
    }
}

Conclusão: Algo está diferente nas aulas ToolTipe ContextMenu. Ambos têm classes de "serviço", como ToolTipServicee ContextMenuService, que gerenciam certas propriedades, e ambos usam Popupcomo um controle pai "secreto" durante a exibição. Finalmente, percebi que TODOS os exemplos de dicas de ferramentas XAML na Web não usam classes ToolTipdiretamente. Em vez disso, eles incorporam a StackPanelcom TextBlocks. Coisas que te fazem dizer: "hmmm ..."

Kevinarpe
fonte
1
Você deve obter mais votos em sua resposta apenas por sua eficácia. +1 de mim.
Hannish de
ToolTipService precisa ser colocado no elemento pai, veja a resposta de Martin Konicek acima.
Jeson Martajaya
8

Provavelmente, você deseja usar o Popup em vez do Tooltip, pois o Tooltip pressupõe que você o está usando na forma de padrões de IU predefinidos.

Não sei por que StaysOpen não funciona, mas ShowDuration funciona conforme documentado no MSDN - é a quantidade de tempo que a dica de ferramenta é exibida QUANDO é exibida. Defina-o para um valor pequeno (por exemplo, 500 mseg) para ver a diferença.

O truque no seu caso é manter o estado de "último controle pairado", mas uma vez que você tenha isso, deve ser bastante trivial alterar o destino do canal e o conteúdo dinamicamente (manualmente ou por meio de vinculação) se você estiver usando um Popup, ou ocultando o último Popup visível se você estiver usando vários.

Existem algumas pegadinhas com os Popups no que diz respeito ao redimensionamento e movimentação de janelas (Popups não se movem com os contêineres), então você pode querer ter isso em mente enquanto ajusta o comportamento. Veja este link para mais detalhes.

HTH.

micahtan
fonte
3
Também tome cuidado, pois os pop-ups estão sempre acima de todos os objetos da área de trabalho - mesmo se você alternar para outro programa, o pop-up ficará visível e obscurecerá parte do outro programa.
Jeff B
É exatamente por isso que não gosto de usar pop-ups .... porque eles não encolhem com o programa e ficam por cima de todos os outros programas. Além disso, redimensionar / mover o aplicativo principal não move o pop-up com ele por padrão.
Rachel
FWIW, esta convenção de IU é lixo de qualquer maneira . Poucas coisas são mais irritantes do que uma dica de ferramenta que desaparece enquanto estou lendo.
Roman Starkov
7

Se você deseja especificar que apenas certos elementos em seu Windowtêm ToolTipduração efetivamente indefinida, você pode definir um Styleem seu Window.Resourcespara esses elementos. Aqui está um Stylepara Buttonque tenha tal ToolTip:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    ...>
    ...
    <Window.Resources>
        <Style x:Key="ButtonToolTipIndefinate" TargetType="{x:Type Button}">
            <Setter Property="ToolTipService.ShowDuration"
                    Value="{x:Static Member=sys:Int32.MaxValue}"/>
        </Style>
        ...
    </Window.Resources>
    ...
    <Button Style="{DynamicResource ButtonToolTipIndefinate}"
            ToolTip="This should stay open"/>
    <Button ToolTip="This Should disappear after the default time.">
    ...

Também se pode adicionar Style.Resourcesao Stylepara mudar a aparência do ToolTipque mostra, por exemplo:

<Style x:Key="ButtonToolTipTransparentIndefinate" TargetType="{x:Type Button}">
    <Style.Resources>
        <Style x:Key="{x:Type ToolTip}" TargetType="{x:Type ToolTip}">
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="BorderBrush" Value="Transparent"/>
            <Setter Property="HasDropShadow" Value="False"/>
        </Style>
    </Style.Resources>
    <Setter Property="ToolTipService.ShowDuration"
            Value="{x:Static Member=sys:Int32.MaxValue}"/>
</Style>

Nota: Quando fiz isso, também usei BasedOnno Stylepara que tudo o mais definido para a versão do meu controle personalizado com um normal ToolTipfosse aplicado.

Jonathan Allan
fonte
5

Eu estava lutando com o WPF Tooltip outro dia. Não parece ser possível impedir que apareça e desapareça por si mesmo, então no final acabei recorrendo ao tratamento do Openedevento. Por exemplo, eu queria impedir que ele abrisse, a menos que tivesse algum conteúdo, então manipulei o Openedevento e fiz o seguinte:

tooltip.IsOpen = (tooltip.Content != null);

É um hack, mas funcionou.

Presumivelmente, você poderia manipular o Closedevento da mesma forma e dizer para abrir novamente, mantendo-o assim visível.

Daniel Earwicker
fonte
ToolTip tem uma propriedade chamada HasContent que você pode usar no lugar
benPearce,
2

Apenas para fins de integridade: no código, é assim:

ToolTipService.SetShowDuration(element, 60000);
Ulrich Beckert
fonte
0

Além disso, se você quiser colocar qualquer outro controle em sua dica de ferramenta, ele não poderá ser focalizado, pois uma dica de ferramenta em si pode obter o foco. Então, como disse micahtan, sua melhor chance é um Popup.

Carlo
fonte
0

Resolvi meu problema com o mesmo código.

ToolTipService.ShowDurationProperty.OverrideMetadata (typeof (DependencyObject), novo FrameworkPropertyMetadata (Int32.MaxValue));

Aravind S
fonte
-4
ToolTipService.ShowDurationProperty.OverrideMetadata(
    typeof(DependencyObject), new FrameworkPropertyMetadata(Int32.MaxValue));

Está funcionando para mim. Copie esta linha em seu construtor de classe.

Vibhuti Sharma
fonte
3
esta é copiar e colar a resposta aceita com o segundo maior número de
votos