[WPF] TabControl Bug?

WPF에서 Code Behind를 사용해서 TabItem을 추가했다가 Clear 했다가 하고 있는데 아래와 같은 에러가 발생했습니다.

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.TabControl', AncestorLevel='1''. BindingExpression:Path=TabStripPlacement; DataItem=null; target element is 'TabItem' (Name=''); target property is 'NoTarget' (type 'Object')

그래서 구글링을 해보니 아래와 같은 해결법이 있었습니다.

물론 답변 소스코드중에서 TabControl.Items.Remove(item); 이 부분은 Exception을 발생시키는 코드지만, 저것만 주석처리하면 바인딩 에러는 해결할 수 있었습니다.

Code Behind를 통해 TabItem을 추가하고, Clear하는 과정에서 TabItem의 Template 속성을 Null로 만드는 것이 어떤 관계가 있는지 모르겠지만 바인딩 오류는 해결할 수 있었습니다.

혹시 원인을 아시는 분은 답글로 알려주시면 감사드리겠습니다…!!

3개의 좋아요

에러가 발생하는 예제를 구현했습니다.

테스트 해보시려는 분께 참고되기를 바랍니다.

2개의 좋아요

코드를 실행해 본 결과, 재현되지 않고 둘 다 예외가 발생하지 않고 정상 동작합니다.

3개의 좋아요

오잉…혹시 readme 순서대로 하셨나요?? 제가 소스는 정상 동작하는 소스를 올려놨습니다. 한 분 테스트 해주신 분이 계신데 거기서도 동일한 에러가 나왔다고 합니다.

2개의 좋아요

아 바인딩 오류군요; 잘못봤습니다. 바인딩 오류는 말씀하신 것 처럼 4개씩 계속 늘어나네요

3개의 좋아요

네 탭의 개수를 변경해도 계속 4개 단위로 나오는걸보니 탭하고는 상관없이 tabitem과 tabcontrol 간에 뭔가가 있는것 같습니다…근데 그게 뭔지… tabstrap 으로 left right top bottom 같기도 한데…흠…

2개의 좋아요

일단 현상으로 XAML이던 CodeBehind이던 1개씩 추가할 때는 바인딩 오류가 나타나지 않습니다.

3개의 좋아요

마지막 (또는 전체)를 삭제할 때 발생하는 바인딩 오류 (버그) 인걸로 보입니다. 아래의 링크는 아직 승인되지는 않았지만, 버그로 보입니다.

3개의 좋아요

여러 케이스를 테스트 해봤습니다.


  1. TabItem을 선택한 적이 있는 경우
  • 삭제 순서, 방식과 무관하게 바인딩 오류가 발생하지 않음

  1. TabItem을 선택한 적이 없는 경우
  • TabItem의 Template이 기본값인 경우, TabControl의 Items를 Clear하거나 첫 번째 인덱스를 삭제할 때 바인딩 오류 발생

  • TabItem의 Template이 지정된 경우, TabControl의 Items를 Clear할 때는 오류가 발생하지 않지만 첫 번째 인덱스를 삭제할 때 바인딩 오류 발생


  1. TabControl을 상속받은 MyTabControl에서 ItemsChanged를 override한 후 주석 처리해 비활성화한 경우
  • code behind 방식에서는 Clear와 Remove 모두 바인딩 오류가 발생하지 않음
  • ViewModel 바인딩 방식에서는 Clear에서는 바인딩 오류가 발생하지 않지만 RemoveAt으로 첫 번째 인덱스를 삭제할 때 바인딩 오류 발생

알다가도 모르겠군요…

5개의 좋아요

버그인 것 같습니다.

일단 임시책이지만 해결 방법은 TabControl을 하나씩 생성하는 것 입니다.
하나씩 생성할 경우에는 저 오류가 나타나지 않습니다.

이로써 추측해보자면 TabControl에 새 Tab이 추가된 이후 내부적으로 어떤 작업(또는 이벤트)이 있는데 그쪽에서 다량의 아이템이 동시에 삽입될 때 문제가 발생한 것으로 보입니다.

5개의 좋아요

@Vincent 넵 맞습니다.

TabItem에 TabStripPlacement (Left, Right, Top, Bottom) 관련해서 MultiTrigger가 RelativeSource AncestorType 으로 수십 개 정의되어 있어요.

그래서 삭제하기 전에 Template을 제거하시면 문제 없었던 거죠. :smile:

Template을 새롭게 만드시면 됩니다!
보통 TabItem Template을 커스터마이징 하니까 자연스럽게 해결되는 문제 같아요.

<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="true"/>
        <Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Left"/>
    </MultiDataTrigger.Conditions>
    <Setter Property="Background" TargetName="mainBorder" Value="{StaticResource TabItem.MouseOver.Background}"/>
    <Setter Property="BorderBrush" TargetName="mainBorder" Value="{StaticResource TabItem.MouseOver.Border}"/>
    <Setter Property="BorderThickness" TargetName="innerBorder" Value="1,1,0,1"/>
    <Setter Property="BorderThickness" TargetName="mainBorder" Value="1,1,0,1"/>
</MultiDataTrigger>
<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="true"/>
        <Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Bottom"/>
    </MultiDataTrigger.Conditions>
    <Setter Property="Background" TargetName="mainBorder" Value="{StaticResource TabItem.MouseOver.Background}"/>
    <Setter Property="BorderBrush" TargetName="mainBorder" Value="{StaticResource TabItem.MouseOver.Border}"/>
    <Setter Property="BorderThickness" TargetName="innerBorder" Value="1,0,1,1"/>
    <Setter Property="BorderThickness" TargetName="mainBorder" Value="1,0,1,1"/>
</MultiDataTrigger>
<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="true"/>
        <Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Right"/>
    </MultiDataTrigger.Conditions>
    <Setter Property="Background" TargetName="mainBorder" Value="{StaticResource TabItem.MouseOver.Background}"/>
    <Setter Property="BorderBrush" TargetName="mainBorder" Value="{StaticResource TabItem.MouseOver.Border}"/>
    <Setter Property="BorderThickness" TargetName="innerBorder" Value="0,1,1,1"/>
    <Setter Property="BorderThickness" TargetName="mainBorder" Value="0,1,1,1"/>
</MultiDataTrigger>
<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="true"/>
        <Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Top"/>
    </MultiDataTrigger.Conditions>
    <Setter Property="Background" TargetName="mainBorder" Value="{StaticResource TabItem.MouseOver.Background}"/>
    <Setter Property="BorderBrush" TargetName="mainBorder" Value="{StaticResource TabItem.MouseOver.Border}"/>
    <Setter Property="BorderThickness" TargetName="innerBorder" Value="1,1,1,0"/>
    <Setter Property="BorderThickness" TargetName="mainBorder" Value="1,1,1,0"/>
</MultiDataTrigger>
<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="false"/>
        <Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Left"/>
    </MultiDataTrigger.Conditions>
    <Setter Property="Opacity" TargetName="contentPresenter" Value="0.56"/>
    <Setter Property="Background" TargetName="mainBorder" Value="{StaticResource TabItem.Disabled.Background}"/>
    <Setter Property="BorderBrush" TargetName="mainBorder" Value="{StaticResource TabItem.Disabled.Border}"/>
    <Setter Property="BorderThickness" TargetName="innerBorder" Value="1,1,0,1"/>
    <Setter Property="BorderThickness" TargetName="mainBorder" Value="1,1,0,1"/>
</MultiDataTrigger>
<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="false"/>
        <Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Bottom"/>
    </MultiDataTrigger.Conditions>
    <Setter Property="Opacity" TargetName="contentPresenter" Value="0.56"/>
    <Setter Property="Background" TargetName="mainBorder" Value="{StaticResource TabItem.Disabled.Background}"/>
    <Setter Property="BorderBrush" TargetName="mainBorder" Value="{StaticResource TabItem.Disabled.Border}"/>
    <Setter Property="BorderThickness" TargetName="innerBorder" Value="1,0,1,1"/>
    <Setter Property="BorderThickness" TargetName="mainBorder" Value="1,0,1,1"/>
</MultiDataTrigger>
<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="false"/>
        <Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Right"/>
    </MultiDataTrigger.Conditions>
    <Setter Property="Opacity" TargetName="contentPresenter" Value="0.56"/>
    <Setter Property="Background" TargetName="mainBorder" Value="{StaticResource TabItem.Disabled.Background}"/>
    <Setter Property="BorderBrush" TargetName="mainBorder" Value="{StaticResource TabItem.Disabled.Border}"/>
    <Setter Property="BorderThickness" TargetName="innerBorder" Value="0,1,1,1"/>
    <Setter Property="BorderThickness" TargetName="mainBorder" Value="0,1,1,1"/>
</MultiDataTrigger>
<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="false"/>
        <Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Top"/>
    </MultiDataTrigger.Conditions>
    <Setter Property="Opacity" TargetName="contentPresenter" Value="0.56"/>
    <Setter Property="Background" TargetName="mainBorder" Value="{StaticResource TabItem.Disabled.Background}"/>
    <Setter Property="BorderBrush" TargetName="mainBorder" Value="{StaticResource TabItem.Disabled.Border}"/>
    <Setter Property="BorderThickness" TargetName="innerBorder" Value="1,1,1,0"/>
    <Setter Property="BorderThickness" TargetName="mainBorder" Value="1,1,1,0"/>
</MultiDataTrigger>
<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="false"/>
        <Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Left"/>
    </MultiDataTrigger.Conditions>
    <Setter Property="BorderThickness" TargetName="innerBorder" Value="1,1,0,1"/>
    <Setter Property="BorderThickness" TargetName="mainBorder" Value="1,1,0,1"/>
</MultiDataTrigger>
<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="true"/>
        <Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Left"/>
    </MultiDataTrigger.Conditions>
    <Setter Property="Panel.ZIndex" Value="1"/>
    <Setter Property="Margin" Value="-2,-2,0,-2"/>
    <Setter Property="Opacity" TargetName="innerBorder" Value="1"/>
    <Setter Property="BorderThickness" TargetName="innerBorder" Value="1,1,0,1"/>
    <Setter Property="BorderThickness" TargetName="mainBorder" Value="1,1,0,1"/>
</MultiDataTrigger>
<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="false"/>
        <Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Bottom"/>
    </MultiDataTrigger.Conditions>
    <Setter Property="BorderThickness" TargetName="innerBorder" Value="1,0,1,1"/>
    <Setter Property="BorderThickness" TargetName="mainBorder" Value="1,0,1,1"/>
</MultiDataTrigger>
<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="true"/>
        <Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Bottom"/>
    </MultiDataTrigger.Conditions>
    <Setter Property="Panel.ZIndex" Value="1"/>
    <Setter Property="Margin" Value="-2,0,-2,-2"/>
    <Setter Property="Opacity" TargetName="innerBorder" Value="1"/>
    <Setter Property="BorderThickness" TargetName="innerBorder" Value="1,0,1,1"/>
    <Setter Property="BorderThickness" TargetName="mainBorder" Value="1,0,1,1"/>
</MultiDataTrigger>
<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="false"/>
        <Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Right"/>
    </MultiDataTrigger.Conditions>
    <Setter Property="BorderThickness" TargetName="innerBorder" Value="0,1,1,1"/>
    <Setter Property="BorderThickness" TargetName="mainBorder" Value="0,1,1,1"/>
</MultiDataTrigger>
<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="true"/>
        <Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Right"/>
    </MultiDataTrigger.Conditions>
    <Setter Property="Panel.ZIndex" Value="1"/>
    <Setter Property="Margin" Value="0,-2,-2,-2"/>
    <Setter Property="Opacity" TargetName="innerBorder" Value="1"/>
    <Setter Property="BorderThickness" TargetName="innerBorder" Value="0,1,1,1"/>
    <Setter Property="BorderThickness" TargetName="mainBorder" Value="0,1,1,1"/>
</MultiDataTrigger>
<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="false"/>
        <Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Top"/>
    </MultiDataTrigger.Conditions>
    <Setter Property="BorderThickness" TargetName="innerBorder" Value="1,1,1,0"/>
    <Setter Property="BorderThickness" TargetName="mainBorder" Value="1,1,1,0"/>
</MultiDataTrigger>
<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="true"/>
        <Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Top"/>
    </MultiDataTrigger.Conditions>
    <Setter Property="Panel.ZIndex" Value="1"/>
    <Setter Property="Margin" Value="-2,-2,-2,0"/>
    <Setter Property="Opacity" TargetName="innerBorder" Value="1"/>
    <Setter Property="BorderThickness" TargetName="innerBorder" Value="1,1,1,0"/>
    <Setter Property="BorderThickness" TargetName="mainBorder" Value="1,1,1,0"/>
</MultiDataTrigger>
5개의 좋아요